File size: 5,080 Bytes
aec3094 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 | import { AcceptInvitationRequestDto, InviteUsersRequestDto } from '@n8n/api-types';
import { Logger } from '@n8n/backend-common';
import type { User } from '@n8n/db';
import { UserRepository } from '@n8n/db';
import { Post, GlobalScope, RestController, Body, Param } from '@n8n/decorators';
import { Response } from 'express';
import { AuthService } from '@/auth/auth.service';
import config from '@/config';
import { RESPONSE_ERROR_MESSAGES } from '@/constants';
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
import { ForbiddenError } from '@/errors/response-errors/forbidden.error';
import { EventService } from '@/events/event.service';
import { ExternalHooks } from '@/external-hooks';
import { License } from '@/license';
import { PostHogClient } from '@/posthog';
import { AuthenticatedRequest, AuthlessRequest } from '@/requests';
import { PasswordUtility } from '@/services/password.utility';
import { UserService } from '@/services/user.service';
import { isSamlLicensedAndEnabled } from '@/sso.ee/saml/saml-helpers';
@RestController('/invitations')
export class InvitationController {
constructor(
private readonly logger: Logger,
private readonly externalHooks: ExternalHooks,
private readonly authService: AuthService,
private readonly userService: UserService,
private readonly license: License,
private readonly passwordUtility: PasswordUtility,
private readonly userRepository: UserRepository,
private readonly postHog: PostHogClient,
private readonly eventService: EventService,
) {}
/**
* Send email invite(s) to one or multiple users and create user shell(s).
*/
@Post('/', { rateLimit: { limit: 10 } })
@GlobalScope('user:create')
async inviteUser(
req: AuthenticatedRequest,
_res: Response,
@Body invitations: InviteUsersRequestDto,
) {
if (invitations.length === 0) return [];
const isWithinUsersLimit = this.license.isWithinUsersLimit();
if (isSamlLicensedAndEnabled()) {
this.logger.debug(
'SAML is enabled, so users are managed by the Identity Provider and cannot be added through invites',
);
throw new BadRequestError(
'SAML is enabled, so users are managed by the Identity Provider and cannot be added through invites',
);
}
if (!isWithinUsersLimit) {
this.logger.debug(
'Request to send email invite(s) to user(s) failed because the user limit quota has been reached',
);
throw new ForbiddenError(RESPONSE_ERROR_MESSAGES.USERS_QUOTA_REACHED);
}
if (!config.getEnv('userManagement.isInstanceOwnerSetUp')) {
this.logger.debug(
'Request to send email invite(s) to user(s) failed because the owner account is not set up',
);
throw new BadRequestError('You must set up your own account before inviting others');
}
const attributes = invitations.map(({ email, role }) => {
if (role === 'global:admin' && !this.license.isAdvancedPermissionsLicensed()) {
throw new ForbiddenError(
'Cannot invite admin user without advanced permissions. Please upgrade to a license that includes this feature.',
);
}
return { email, role };
});
const { usersInvited, usersCreated } = await this.userService.inviteUsers(req.user, attributes);
await this.externalHooks.run('user.invited', [usersCreated]);
return usersInvited;
}
/**
* Fill out user shell with first name, last name, and password.
*/
@Post('/:id/accept', { skipAuth: true })
async acceptInvitation(
req: AuthlessRequest,
res: Response,
@Body payload: AcceptInvitationRequestDto,
@Param('id') inviteeId: string,
) {
const { inviterId, firstName, lastName, password } = payload;
const users = await this.userRepository.findManyByIds([inviterId, inviteeId]);
if (users.length !== 2) {
this.logger.debug(
'Request to fill out a user shell failed because the inviter ID and/or invitee ID were not found in database',
{
inviterId,
inviteeId,
},
);
throw new BadRequestError('Invalid payload or URL');
}
const invitee = users.find((user) => user.id === inviteeId) as User;
if (invitee.password) {
this.logger.debug(
'Request to fill out a user shell failed because the invite had already been accepted',
{ inviteeId },
);
throw new BadRequestError('This invite has been accepted already');
}
invitee.firstName = firstName;
invitee.lastName = lastName;
invitee.password = await this.passwordUtility.hash(password);
const updatedUser = await this.userRepository.save(invitee, { transaction: false });
this.authService.issueCookie(res, updatedUser, req.browserId);
this.eventService.emit('user-signed-up', {
user: updatedUser,
userType: 'email',
wasDisabledLdapUser: false,
});
const publicInvitee = await this.userService.toPublic(invitee);
await this.externalHooks.run('user.profile.update', [invitee.email, publicInvitee]);
await this.externalHooks.run('user.password.update', [invitee.email, invitee.password]);
return await this.userService.toPublic(updatedUser, {
posthog: this.postHog,
withScopes: true,
});
}
}
|