diff --git a/server/src/services/auth.service.ts b/server/src/services/auth.service.ts index 243c3fef54..7b1cc54f70 100644 --- a/server/src/services/auth.service.ts +++ b/server/src/services/auth.service.ts @@ -256,53 +256,68 @@ export class AuthService { async signup(appSignUpDto: AppSignupDto, response: Response) { const { name, email, password, organizationId } = appSignUpDto; - // Check if the configs allows user signups - if (this.configService.get('DISABLE_SIGNUPS') === 'true') { - throw new NotAcceptableException(); - } - - const existingUser = await this.usersService.findByEmail(email); - let signingUpOrganization: Organization; - - if (organizationId) { - signingUpOrganization = await this.organizationsService.get(organizationId); - if (!signingUpOrganization) { - throw new NotFoundException('Could not found organization details. Please verify the orgnization id'); + return dbTransactionWrap(async (manager: EntityManager) => { + // Check if the configs allows user signups + if (this.configService.get('DISABLE_SIGNUPS') === 'true') { + throw new NotAcceptableException(); } - /* Check if the workspace allows user signup or not */ - const { enableSignUp, domain } = signingUpOrganization; - if (!enableSignUp) { - throw new ForbiddenException('Workspace signup has been disabled. Please contact the workspace admin.'); - } - if (!isValidDomain(email, domain)) { - throw new ForbiddenException('You cannot sign up using the email address - Domain verification failed.'); - } - } - const names = { firstName: '', lastName: '' }; - if (name) { - const [firstName, ...rest] = name.split(' '); - names['firstName'] = firstName; - if (rest.length != 0) { - const lastName = rest.join(' '); - names['lastName'] = lastName; - } - } - const { firstName, lastName } = names; - const userParams = { email, password, firstName, lastName }; + const existingUser = await this.usersService.findByEmail(email); + let signingUpOrganization: Organization; - if (existingUser) { - return await this.whatIfTheSignUpIsAtTheWorkspaceLevel(existingUser, signingUpOrganization, userParams, response); - } else { - return await this.createUserOrPersonalWorkspace(userParams, existingUser, signingUpOrganization, response); - } + if (organizationId) { + signingUpOrganization = await this.organizationsService.get(organizationId); + if (!signingUpOrganization) { + throw new NotFoundException('Could not found organization details. Please verify the orgnization id'); + } + /* Check if the workspace allows user signup or not */ + const { enableSignUp, domain } = signingUpOrganization; + if (!enableSignUp) { + throw new ForbiddenException('Workspace signup has been disabled. Please contact the workspace admin.'); + } + if (!isValidDomain(email, domain)) { + throw new ForbiddenException('You cannot sign up using the email address - Domain verification failed.'); + } + } + + const names = { firstName: '', lastName: '' }; + if (name) { + const [firstName, ...rest] = name.split(' '); + names['firstName'] = firstName; + if (rest.length != 0) { + const lastName = rest.join(' '); + names['lastName'] = lastName; + } + } + const { firstName, lastName } = names; + const userParams = { email, password, firstName, lastName }; + + if (existingUser) { + return await this.whatIfTheSignUpIsAtTheWorkspaceLevel( + existingUser, + signingUpOrganization, + userParams, + response, + manager + ); + } else { + return await this.createUserOrPersonalWorkspace( + userParams, + existingUser, + signingUpOrganization, + response, + manager + ); + } + }); } createUserOrPersonalWorkspace = async ( userParams: { email: string; password: string; firstName: string; lastName: string }, existingUser: User, signingUpOrganization: Organization = null, - response?: Response + response?: Response, + manager?: EntityManager ) => { return await dbTransactionWrap(async (manager: EntityManager) => { const { email, password, firstName, lastName } = userParams; @@ -358,7 +373,7 @@ export class AuthService { .catch((err) => console.error(err)); return {}; } - }); + }, manager); }; async processOrganizationSignup( @@ -416,7 +431,8 @@ export class AuthService { existingUser: User, signingUpOrganization: Organization, userParams: { firstName: string; lastName: string; password: string }, - response: Response + response: Response, + manager?: EntityManager ) => { const { firstName, lastName, password } = userParams; const organizationId: string = signingUpOrganization?.id; @@ -437,15 +453,17 @@ export class AuthService { ); /* User who missed the organization invite flow */ - const activeAccountButnotActiveInWorkspaces = !!alreadyInvitedUserByAdmin && !existingUser.invitationToken; + const activeAccountButnotActiveInWorkspace = !!alreadyInvitedUserByAdmin && !existingUser.invitationToken; const invitedButNotActivated = !!alreadyInvitedUserByAdmin && !!existingUser.invitationToken; const activeUserWantsToSignUpToWorkspace = hasActiveWorkspaces && !!organizationId && !isAlreadyActiveInWorkspace; const personalWorkspaceCount = await this.organizationUsersService.personalWorkspaceCount(existingUser.id); const didInstanceSignUpAlreadyButNotActive = !!existingUser?.invitationToken && personalWorkspaceCount > 0; const activatedAccountNoActiveWorkspaces = !existingUser?.invitationToken && hasSomeWorkspaceInvites; /* Personal workspace case */ - const adminInvitedButUserWantsInstanceSignup = !!existingUser?.invitationToken && hasSomeWorkspaceInvites; + const adminInvitedButUserWantsInstanceSignup = + !!existingUser?.invitationToken && hasSomeWorkspaceInvites && !organizationId; const isUserAlreadyExisted = !!isAlreadyActiveInWorkspace || hasActiveWorkspaces; + const workspaceSignupForInstanceSignedUpUserButNotActive = !!existingUser?.invitationToken && !!organizationId; switch (true) { case invitedButNotActivated: { @@ -453,98 +471,110 @@ export class AuthService { Organization Signup and admin already send an invite to the user activate account and send org invite url */ - await this.usersService.updateUser(existingUser.id, { - invitationToken: null, - password, - ...(firstName && { - firstName, - }) /* we should reset the name if the user is not activated his account before */, - lastName: lastName ?? '', - ...getUserStatusAndSource(lifecycleEvents.USER_REDEEM), - }); - return await this.processOrganizationSignup(response, existingUser, { - invitationToken: alreadyInvitedUserByAdmin.invitationToken, - organizationId, - }); + return this.activateUserAndInviteToTheWorkspace( + existingUser, + { invitationToken: alreadyInvitedUserByAdmin.invitationToken, organizationId }, + userParams, + response, + manager + ); } - case activeAccountButnotActiveInWorkspaces: { + case activeAccountButnotActiveInWorkspace: { /* Send the org invite again */ - this.sendOrgInvite( - { email: existingUser.email, firstName }, - signingUpOrganization.name, - alreadyInvitedUserByAdmin.invitationToken + const defaultOrganization = await this.organizationsService.fetchOrganization( + existingUser.defaultOrganizationId + ); + return await this.processOrganizationSignup( + response, + existingUser, + { + invitationToken: alreadyInvitedUserByAdmin.invitationToken, + organizationId, + }, + manager, + defaultOrganization ); break; } case activeUserWantsToSignUpToWorkspace: { /* Create new organizations_user entry and send an invite */ - return await dbTransactionWrap(async (manager: EntityManager) => { - await this.usersService.attachUserGroup(['all_users'], organizationId, existingUser.id, manager); - const organizationUser = await this.organizationUsersService.create( - existingUser, - signingUpOrganization, - true, - manager - ); - const defaultOrganization = await this.organizationsService.fetchOrganization( - existingUser.defaultOrganizationId - ); - return await this.processOrganizationSignup( - response, - existingUser, - { - invitationToken: organizationUser.invitationToken, - organizationId, - }, - manager, - defaultOrganization - ); - }); - break; + const organizationUser = await this.addUserToTheWorkspace( + existingUser, + signingUpOrganization, + manager, + response + ); + const defaultOrganization = await this.organizationsService.fetchOrganization( + existingUser.defaultOrganizationId + ); + return await this.processOrganizationSignup( + response, + existingUser, + { + invitationToken: organizationUser.invitationToken, + organizationId, + }, + manager, + defaultOrganization + ); } case adminInvitedButUserWantsInstanceSignup: { + const errorMessage = 'The user is already registered. Please check your inbox for the activation link'; if (personalWorkspaceCount === 0) { await this.createUserOrPersonalWorkspace( { email: existingUser.email, password, firstName, lastName }, existingUser, null, - response + response, + manager ); } else { - /* Update username and password, resend the email */ - await this.usersService.updateUser(existingUser.id, { - ...(firstName && { firstName }), - lastName: lastName ?? '', - password, - source: SOURCE.SIGNUP, - }); this.emailService .sendWelcomeEmail(existingUser.email, existingUser.firstName, existingUser.invitationToken) .catch((err) => console.error(err)); + throw new NotAcceptableException(errorMessage); } - + break; + } + case workspaceSignupForInstanceSignedUpUserButNotActive: { + const organizationUser = await this.addUserToTheWorkspace( + existingUser, + signingUpOrganization, + manager, + response + ); + return this.activateUserAndInviteToTheWorkspace( + existingUser, + { invitationToken: organizationUser.invitationToken, organizationId }, + userParams, + response, + manager + ); break; } case activatedAccountNoActiveWorkspaces: case didInstanceSignUpAlreadyButNotActive: { /* Resend intance invitation */ - const errorMessage = organizationId - ? 'Please finish setting up your account before signing in to this workspace. Check your inbox for the activation link.' - : 'The user is already registered. Please check your inbox for the activation link'; + const errorMessage = 'The user is already registered. Please check your inbox for the activation link'; if (!organizationId) { - const pickOneWorkspace = existingUser.organizationUsers.find( - (ou) => ou.status === WORKSPACE_USER_STATUS.INVITED - ); - if (pickOneWorkspace) { - const signingUpOrganization = await this.organizationsService.fetchOrganization( - pickOneWorkspace.organizationId - ); - this.sendOrgInvite( - { email: existingUser.email, firstName }, - signingUpOrganization.name, - pickOneWorkspace.invitationToken - ); + if (activatedAccountNoActiveWorkspaces) { + /* Send invite to the personal workspace */ + const personalWorkspaces = await this.organizationUsersService.personalWorkspaces(existingUser.id); + if (personalWorkspaces?.length) { + const personalWorkspaceUser = personalWorkspaces[0]; + if (personalWorkspaceUser) { + const signingUpOrganization = await this.organizationsService.fetchOrganization( + personalWorkspaceUser.organizationId + ); + this.sendOrgInvite( + { email: existingUser.email, firstName }, + signingUpOrganization.name, + personalWorkspaceUser.invitationToken + ); + return; + } + } } } @@ -563,6 +593,49 @@ export class AuthService { } }; + async activateUserAndInviteToTheWorkspace( + existingUser: User, + organizationDetails: { invitationToken: string; organizationId: string }, + userParams: { firstName: string; lastName: string; password: string }, + response: Response, + manager: EntityManager + ) { + const { firstName, lastName, password } = userParams; + const { invitationToken, organizationId } = organizationDetails; + await this.usersService.updateUser( + existingUser.id, + { + invitationToken: null, + password, + ...(firstName && { + firstName, + }) /* we should reset the name if the user is not activated his account before */, + lastName: lastName ?? '', + ...getUserStatusAndSource(lifecycleEvents.USER_REDEEM), + }, + manager + ); + return await this.processOrganizationSignup( + response, + existingUser, + { + invitationToken, + organizationId, + }, + manager + ); + } + + async addUserToTheWorkspace( + existingUser: User, + signingUpOrganization: Organization, + manager: EntityManager, + response: Response + ) { + await this.usersService.attachUserGroup(['all_users'], signingUpOrganization.id, existingUser.id, manager); + return this.organizationUsersService.create(existingUser, signingUpOrganization, true, manager); + } + async activateAccountWithToken(activateAccountWithToken: ActivateAccountWithTokenDto, response: any) { const { email, password, organizationToken } = activateAccountWithToken; const signupUser = await this.usersService.findByEmail(email); @@ -835,7 +908,18 @@ export class AuthService { } await this.usersService.updateUser(user.id, { defaultOrganizationId: organizationUser.organizationId }, manager); const organization = await this.organizationsService.get(organizationUser.organizationId); + const activeWorkspacesCount = await this.organizationUsersService.getActiveWorkspacesCount(user.id); await this.organizationUsersService.activateOrganization(organizationUser, manager); + const personalWorkspacesCount = await this.organizationUsersService.personalWorkspaceCount(user.id); + if (personalWorkspacesCount === 1 && activeWorkspacesCount === 0) { + /* User already signed up thorugh instance signup page. but now needs to signup through workspace signup page */ + /* Activate the personal workspace */ + const organizationUser = await manager.findOne(OrganizationUser, { + where: { organizationId: user.defaultOrganizationId }, + relations: ['user', 'organization'], + }); + await this.organizationUsersService.activateOrganization(organizationUser, manager); + } return this.generateLoginResultPayload(response, user, organization, null, null, loggedInUser, manager); }); } diff --git a/server/src/services/organization_users.service.ts b/server/src/services/organization_users.service.ts index 919e9f3acf..6b66d50801 100644 --- a/server/src/services/organization_users.service.ts +++ b/server/src/services/organization_users.service.ts @@ -102,7 +102,7 @@ export class OrganizationUsersService { return user; } - async getActiveWorkspacesCount(userId) { + async getActiveWorkspacesCount(userId: string) { return await this.organizationUsersRepository.count({ where: { userId, @@ -176,12 +176,18 @@ export class OrganizationUsersService { } async personalWorkspaceCount(userId: string): Promise { + const personalWorkspacesCount = await this.personalWorkspaces(userId); + return personalWorkspacesCount?.length; + } + + async personalWorkspaces(userId: string): Promise { const personalWorkspaces: Partial = await this.organizationUsersRepository.find({ - select: ['organizationId'], + select: ['organizationId', 'invitationToken'], where: { userId }, }); - let personalWorkspacesCount = 0; - for (const { organizationId } of personalWorkspaces) { + const personalWorkspaceArray: OrganizationUser[] = []; + for (const workspace of personalWorkspaces) { + const { organizationId } = workspace; const workspaceOwner = await this.organizationUsersRepository.find({ where: { organizationId }, order: { createdAt: 'ASC' }, @@ -189,10 +195,11 @@ export class OrganizationUsersService { }); if (workspaceOwner[0]?.userId === userId) { /* First user of the workspace = created by the user */ - personalWorkspacesCount++; + personalWorkspaceArray.push(workspace); } } - return personalWorkspacesCount; + + return personalWorkspaceArray; } async lastActiveAdmin(organizationId: string): Promise {