From d85b8bef4e659817f76659bbb28998b961b86bfa Mon Sep 17 00:00:00 2001 From: Antoine Moreaux Date: Thu, 6 Feb 2025 14:05:19 +0100 Subject: [PATCH] fix(signin): allow to signin in pending creation workspace (#10052) --- .../auth/services/sign-in-up.service.spec.ts | 67 +++++++++++++++++++ .../auth/services/sign-in-up.service.ts | 36 +++++++--- .../workspace/workspace.validate.ts | 14 ---- 3 files changed, 95 insertions(+), 22 deletions(-) diff --git a/packages/twenty-server/src/engine/core-modules/auth/services/sign-in-up.service.spec.ts b/packages/twenty-server/src/engine/core-modules/auth/services/sign-in-up.service.spec.ts index 8d92084a2..4df8a9045 100644 --- a/packages/twenty-server/src/engine/core-modules/auth/services/sign-in-up.service.spec.ts +++ b/packages/twenty-server/src/engine/core-modules/auth/services/sign-in-up.service.spec.ts @@ -21,6 +21,11 @@ import { UserService } from 'src/engine/core-modules/user/services/user.service' import { User } from 'src/engine/core-modules/user/user.entity'; import { WorkspaceInvitationService } from 'src/engine/core-modules/workspace-invitation/services/workspace-invitation.service'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; +import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity'; +import { + AuthException, + AuthExceptionCode, +} from 'src/engine/core-modules/auth/auth.exception'; jest.mock('src/utils/image', () => { return { @@ -75,6 +80,7 @@ describe('SignInUpService', () => { provide: UserWorkspaceService, useValue: { addUserToWorkspace: jest.fn(), + checkUserWorkspaceExists: jest.fn(), create: jest.fn(), }, }, @@ -248,4 +254,65 @@ describe('SignInUpService', () => { expect(UserRepository.save).toHaveBeenCalled(); expect(fileUploadService.uploadImage).toHaveBeenCalled(); }); + + it('should handle signIn on workspace in pending state', async () => { + const params: SignInUpBaseParams & + ExistingUserOrPartialUserWithPicture & + AuthProviderWithPasswordType = { + workspace: { + id: 'workspaceId', + activationStatus: WorkspaceActivationStatus.PENDING_CREATION, + } as Workspace, + authParams: { provider: 'password', password: 'validPassword' }, + userData: { + type: 'existingUser', + existingUser: { email: 'test@example.com' } as User, + }, + }; + + jest.spyOn(environmentService, 'get').mockReturnValue(false); + jest + .spyOn(userWorkspaceService, 'addUserToWorkspace') + .mockResolvedValue({} as User); + jest + .spyOn(userWorkspaceService, 'checkUserWorkspaceExists') + .mockResolvedValue({} as UserWorkspace); + + const result = await service.signInUp(params); + + expect(result.workspace).toEqual(params.workspace); + expect(result.user).toBeDefined(); + expect(userWorkspaceService.addUserToWorkspace).toHaveBeenCalled(); + }); + + it('should throw - handle signUp on workspace in pending state', async () => { + const params: SignInUpBaseParams & + ExistingUserOrPartialUserWithPicture & + AuthProviderWithPasswordType = { + workspace: { + id: 'workspaceId', + activationStatus: WorkspaceActivationStatus.PENDING_CREATION, + } as Workspace, + authParams: { provider: 'password', password: 'validPassword' }, + userData: { + type: 'existingUser', + existingUser: { email: 'test@example.com' } as User, + }, + }; + + jest.spyOn(environmentService, 'get').mockReturnValue(false); + jest + .spyOn(userWorkspaceService, 'addUserToWorkspace') + .mockResolvedValue({} as User); + jest + .spyOn(userWorkspaceService, 'checkUserWorkspaceExists') + .mockResolvedValue(null); + + await expect(() => service.signInUp(params)).rejects.toThrow( + new AuthException( + 'Workspace is not ready to welcome new members', + AuthExceptionCode.FORBIDDEN_EXCEPTION, + ), + ); + }); }); diff --git a/packages/twenty-server/src/engine/core-modules/auth/services/sign-in-up.service.ts b/packages/twenty-server/src/engine/core-modules/auth/services/sign-in-up.service.ts index 7030dd306..477f48c67 100644 --- a/packages/twenty-server/src/engine/core-modules/auth/services/sign-in-up.service.ts +++ b/packages/twenty-server/src/engine/core-modules/auth/services/sign-in-up.service.ts @@ -38,7 +38,6 @@ import { UserService } from 'src/engine/core-modules/user/services/user.service' import { User } from 'src/engine/core-modules/user/user.entity'; import { WorkspaceInvitationService } from 'src/engine/core-modules/workspace-invitation/services/workspace-invitation.service'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; -import { workspaceValidator } from 'src/engine/core-modules/workspace/workspace.validate'; import { getDomainNameByEmail } from 'src/utils/get-domain-name-by-email'; import { getImageBufferFromUrl } from 'src/utils/image'; import { isWorkEmail } from 'src/utils/is-work-email'; @@ -214,18 +213,39 @@ export class SignInUpService { return await this.userRepository.save(userToCreate); } + private async throwIfWorkspaceIsNotReadyForSignInUp( + workspace: Workspace, + user: ExistingUserOrPartialUserWithPicture, + ) { + if (workspace.activationStatus === WorkspaceActivationStatus.ACTIVE) return; + + if (user.userData.type !== 'existingUser') { + throw new AuthException( + 'Workspace is not ready to welcome new members', + AuthExceptionCode.FORBIDDEN_EXCEPTION, + ); + } + + const userWorkspaceExists = + await this.userWorkspaceService.checkUserWorkspaceExists( + user.userData.existingUser.id, + workspace.id, + ); + + if (!userWorkspaceExists) { + throw new AuthException( + 'User is not part of the workspace', + AuthExceptionCode.FORBIDDEN_EXCEPTION, + ); + } + } + async signInUpOnExistingWorkspace( params: { workspace: Workspace; } & ExistingUserOrPartialUserWithPicture, ) { - workspaceValidator.assertIsActive( - params.workspace, - new AuthException( - 'Workspace is not ready to welcome new members', - AuthExceptionCode.FORBIDDEN_EXCEPTION, - ), - ); + await this.throwIfWorkspaceIsNotReadyForSignInUp(params.workspace, params); const currentUser = params.userData.type === 'newUserWithPicture' diff --git a/packages/twenty-server/src/engine/core-modules/workspace/workspace.validate.ts b/packages/twenty-server/src/engine/core-modules/workspace/workspace.validate.ts index f3aab2775..f943f0654 100644 --- a/packages/twenty-server/src/engine/core-modules/workspace/workspace.validate.ts +++ b/packages/twenty-server/src/engine/core-modules/workspace/workspace.validate.ts @@ -1,5 +1,3 @@ -import { WorkspaceActivationStatus } from 'twenty-shared'; - import { AuthException, AuthExceptionCode, @@ -24,16 +22,6 @@ const assertIsDefinedOrThrow = ( } }; -const assertIsActive = ( - workspace: Workspace, - exceptionToThrow: CustomException, -): asserts workspace is Workspace & { - activationStatus: WorkspaceActivationStatus.ACTIVE; -} => { - if (workspace.activationStatus === WorkspaceActivationStatus.ACTIVE) return; - throw exceptionToThrow; -}; - const isAuthEnabledOrThrow = ( provider: WorkspaceAuthProvider, workspace: Workspace, @@ -52,10 +40,8 @@ const isAuthEnabledOrThrow = ( export const workspaceValidator: { assertIsDefinedOrThrow: typeof assertIsDefinedOrThrow; - assertIsActive: typeof assertIsActive; isAuthEnabledOrThrow: typeof isAuthEnabledOrThrow; } = { assertIsDefinedOrThrow: assertIsDefinedOrThrow, - assertIsActive: assertIsActive, isAuthEnabledOrThrow: isAuthEnabledOrThrow, };