fix(signin): allow to signin in pending creation workspace (#10052)

This commit is contained in:
Antoine Moreaux
2025-02-06 14:05:19 +01:00
committed by GitHub
parent e849378726
commit d85b8bef4e
3 changed files with 95 additions and 22 deletions

View File

@ -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,
),
);
});
});

View File

@ -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'

View File

@ -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,
};