[fix] [permissions] Fix role assignment at sign-up (#11045)
In [a previous PR](https://github.com/twentyhq/twenty/pull/11023) I fixed the issue where users where re-assigned the default role when signin up using SSO. The "fix" actually introduced another error, calling `assignRoleToUserWorkspace` only when a user is signing up to twenty, forgetting about the case where an existing twenty user signs up to a different workspace. They should still be assigned the role in that case. To fix this and improve clarity, I moved assignRoleToUserWorkspace to addUserToWorkspace and renamed addUserToWorkspace to addUserToWorkspaceIfUserNotInWorkspace since this is at each sign in but does nothing if user is already in the workspace. I think ideally we should refactor this part to improve readability and understandability, maybe with different flows for each case: signIn and signUp, to twenty or to a workspace
This commit is contained in:
@@ -9,8 +9,8 @@ import {
|
||||
AuthException,
|
||||
AuthExceptionCode,
|
||||
} from 'src/engine/core-modules/auth/auth.exception';
|
||||
import { SignInUpService } from 'src/engine/core-modules/auth/services/sign-in-up.service';
|
||||
import { AuthSsoService } from 'src/engine/core-modules/auth/services/auth-sso.service';
|
||||
import { SignInUpService } from 'src/engine/core-modules/auth/services/sign-in-up.service';
|
||||
import { AccessTokenService } from 'src/engine/core-modules/auth/token/services/access-token.service';
|
||||
import { RefreshTokenService } from 'src/engine/core-modules/auth/token/services/refresh-token.service';
|
||||
import { ExistingUserOrNewUser } from 'src/engine/core-modules/auth/types/signInUp.type';
|
||||
@@ -101,7 +101,8 @@ describe('AuthService', () => {
|
||||
useValue: {
|
||||
checkUserWorkspaceExists:
|
||||
userWorkspaceServiceCheckUserWorkspaceExistsMock,
|
||||
addUserToWorkspace: userWorkspaceAddUserToWorkspaceMock,
|
||||
addUserToWorkspaceIfUserNotInWorkspace:
|
||||
userWorkspaceAddUserToWorkspaceMock,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -37,8 +37,8 @@ import {
|
||||
UserNotExists,
|
||||
} from 'src/engine/core-modules/auth/dto/user-exists.entity';
|
||||
import { WorkspaceInviteHashValid } from 'src/engine/core-modules/auth/dto/workspace-invite-hash-valid.entity';
|
||||
import { SignInUpService } from 'src/engine/core-modules/auth/services/sign-in-up.service';
|
||||
import { AuthSsoService } from 'src/engine/core-modules/auth/services/auth-sso.service';
|
||||
import { SignInUpService } from 'src/engine/core-modules/auth/services/sign-in-up.service';
|
||||
import { AccessTokenService } from 'src/engine/core-modules/auth/token/services/access-token.service';
|
||||
import { RefreshTokenService } from 'src/engine/core-modules/auth/token/services/refresh-token.service';
|
||||
import {
|
||||
@@ -106,7 +106,10 @@ export class AuthService {
|
||||
workspacePersonalInviteToken: invitation.value,
|
||||
email: user.email,
|
||||
});
|
||||
await this.userWorkspaceService.addUserToWorkspace(user, workspace);
|
||||
await this.userWorkspaceService.addUserToWorkspaceIfUserNotInWorkspace(
|
||||
user,
|
||||
workspace,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
+19
-96
@@ -44,8 +44,6 @@ describe('SignInUpService', () => {
|
||||
let userWorkspaceService: UserWorkspaceService;
|
||||
let environmentService: EnvironmentService;
|
||||
let domainManagerService: DomainManagerService;
|
||||
let userRoleService: UserRoleService;
|
||||
let featureFlagService: FeatureFlagService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
@@ -83,7 +81,7 @@ describe('SignInUpService', () => {
|
||||
{
|
||||
provide: UserWorkspaceService,
|
||||
useValue: {
|
||||
addUserToWorkspace: jest.fn(),
|
||||
addUserToWorkspaceIfUserNotInWorkspace: jest.fn(),
|
||||
checkUserWorkspaceExists: jest.fn(),
|
||||
create: jest.fn(),
|
||||
},
|
||||
@@ -148,8 +146,6 @@ describe('SignInUpService', () => {
|
||||
environmentService = module.get<EnvironmentService>(EnvironmentService);
|
||||
domainManagerService =
|
||||
module.get<DomainManagerService>(DomainManagerService);
|
||||
userRoleService = module.get<UserRoleService>(UserRoleService);
|
||||
featureFlagService = module.get<FeatureFlagService>(FeatureFlagService);
|
||||
});
|
||||
|
||||
it('should handle signInUp with valid personal invitation', async () => {
|
||||
@@ -179,10 +175,9 @@ describe('SignInUpService', () => {
|
||||
.spyOn(workspaceInvitationService, 'invalidateWorkspaceInvitation')
|
||||
.mockResolvedValue(undefined);
|
||||
|
||||
jest.spyOn(userWorkspaceService, 'addUserToWorkspace').mockResolvedValue({
|
||||
user: {} as User,
|
||||
userWorkspace: {} as UserWorkspace,
|
||||
});
|
||||
jest
|
||||
.spyOn(userWorkspaceService, 'addUserToWorkspaceIfUserNotInWorkspace')
|
||||
.mockResolvedValue(undefined);
|
||||
|
||||
const result = await service.signInUp(params);
|
||||
|
||||
@@ -200,6 +195,9 @@ describe('SignInUpService', () => {
|
||||
(params.workspace as Workspace).id,
|
||||
'test@example.com',
|
||||
);
|
||||
expect(
|
||||
userWorkspaceService.addUserToWorkspaceIfUserNotInWorkspace,
|
||||
).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should handle signInUp on existing workspace without invitation', async () => {
|
||||
@@ -217,16 +215,17 @@ describe('SignInUpService', () => {
|
||||
},
|
||||
};
|
||||
|
||||
jest.spyOn(userWorkspaceService, 'addUserToWorkspace').mockResolvedValue({
|
||||
user: {} as User,
|
||||
userWorkspace: {} as UserWorkspace,
|
||||
});
|
||||
jest
|
||||
.spyOn(userWorkspaceService, 'addUserToWorkspaceIfUserNotInWorkspace')
|
||||
.mockResolvedValue(undefined);
|
||||
|
||||
const result = await service.signInUp(params);
|
||||
|
||||
expect(result.workspace).toEqual(params.workspace);
|
||||
expect(result.user).toBeDefined();
|
||||
expect(userWorkspaceService.addUserToWorkspace).toHaveBeenCalled();
|
||||
expect(
|
||||
userWorkspaceService.addUserToWorkspaceIfUserNotInWorkspace,
|
||||
).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should handle signUp on new workspace for a new user', async () => {
|
||||
@@ -294,10 +293,9 @@ describe('SignInUpService', () => {
|
||||
};
|
||||
|
||||
jest.spyOn(environmentService, 'get').mockReturnValue(false);
|
||||
jest.spyOn(userWorkspaceService, 'addUserToWorkspace').mockResolvedValue({
|
||||
user: {} as User,
|
||||
userWorkspace: {} as UserWorkspace,
|
||||
});
|
||||
jest
|
||||
.spyOn(userWorkspaceService, 'addUserToWorkspaceIfUserNotInWorkspace')
|
||||
.mockResolvedValue(undefined);
|
||||
jest
|
||||
.spyOn(userWorkspaceService, 'checkUserWorkspaceExists')
|
||||
.mockResolvedValue({} as UserWorkspace);
|
||||
@@ -306,7 +304,9 @@ describe('SignInUpService', () => {
|
||||
|
||||
expect(result.workspace).toEqual(params.workspace);
|
||||
expect(result.user).toBeDefined();
|
||||
expect(userWorkspaceService.addUserToWorkspace).toHaveBeenCalled();
|
||||
expect(
|
||||
userWorkspaceService.addUserToWorkspaceIfUserNotInWorkspace,
|
||||
).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should throw - handle signUp on workspace in pending state', async () => {
|
||||
@@ -365,81 +365,4 @@ describe('SignInUpService', () => {
|
||||
expect(WorkspaceRepository.create).toHaveBeenCalled();
|
||||
expect(WorkspaceRepository.save).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not assign default role when permissions are enabled and user exists', async () => {
|
||||
const params: SignInUpBaseParams &
|
||||
ExistingUserOrPartialUserWithPicture &
|
||||
AuthProviderWithPasswordType = {
|
||||
workspace: {
|
||||
id: 'workspaceId',
|
||||
defaultRoleId: 'defaultRoleId',
|
||||
activationStatus: WorkspaceActivationStatus.ACTIVE,
|
||||
} as Workspace,
|
||||
authParams: { provider: 'password', password: 'validPassword' },
|
||||
userData: {
|
||||
type: 'existingUser',
|
||||
existingUser: { email: 'test@example.com' } as User,
|
||||
},
|
||||
};
|
||||
|
||||
const mockUserWorkspace = { id: 'userWorkspaceId' };
|
||||
|
||||
jest.spyOn(featureFlagService, 'isFeatureEnabled').mockResolvedValue(true);
|
||||
jest.spyOn(userWorkspaceService, 'addUserToWorkspace').mockResolvedValue({
|
||||
user: {} as User,
|
||||
userWorkspace: mockUserWorkspace as UserWorkspace,
|
||||
});
|
||||
|
||||
await service.signInUp(params);
|
||||
|
||||
expect(params.workspace).toBeDefined();
|
||||
expect(userRoleService.assignRoleToUserWorkspace).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should assign default role when permissions are enabled and user does not exist', async () => {
|
||||
const params: SignInUpBaseParams &
|
||||
ExistingUserOrPartialUserWithPicture &
|
||||
AuthProviderWithPasswordType = {
|
||||
workspace: {
|
||||
id: 'workspaceId',
|
||||
defaultRoleId: 'defaultRoleId',
|
||||
activationStatus: WorkspaceActivationStatus.ACTIVE,
|
||||
} as Workspace,
|
||||
authParams: { provider: 'password', password: 'validPassword' },
|
||||
userData: {
|
||||
type: 'newUserWithPicture',
|
||||
newUserWithPicture: {
|
||||
email: 'newuser@example.com',
|
||||
picture: 'pictureUrl',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const mockUserWorkspace = { id: 'userWorkspaceId' };
|
||||
|
||||
jest.spyOn(featureFlagService, 'isFeatureEnabled').mockResolvedValue(true);
|
||||
|
||||
jest.spyOn(fileUploadService, 'uploadImage').mockResolvedValue({
|
||||
id: '',
|
||||
mimeType: '',
|
||||
paths: ['path/to/image'],
|
||||
});
|
||||
jest.spyOn(UserRepository, 'create').mockReturnValue({} as User);
|
||||
jest
|
||||
.spyOn(UserRepository, 'save')
|
||||
.mockResolvedValue({ id: 'newUserId' } as User);
|
||||
jest.spyOn(userWorkspaceService, 'addUserToWorkspace').mockResolvedValue({
|
||||
user: {} as User,
|
||||
userWorkspace: mockUserWorkspace as UserWorkspace,
|
||||
});
|
||||
|
||||
await service.signInUp(params);
|
||||
|
||||
expect(params.workspace).toBeDefined();
|
||||
expect(userRoleService.assignRoleToUserWorkspace).toHaveBeenCalledWith({
|
||||
workspaceId: params.workspace!.id,
|
||||
userWorkspaceId: mockUserWorkspace.id,
|
||||
roleId: params.workspace!.defaultRoleId,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+33
-25
@@ -226,37 +226,45 @@ export class SignInUpService {
|
||||
) {
|
||||
await this.throwIfWorkspaceIsNotReadyForSignInUp(params.workspace, params);
|
||||
|
||||
const currentUser =
|
||||
params.userData.type === 'newUserWithPicture'
|
||||
? await this.saveNewUser(
|
||||
params.userData.newUserWithPicture,
|
||||
params.workspace.id,
|
||||
{ canAccessFullAdminPanel: false, canImpersonate: false },
|
||||
)
|
||||
: params.userData.existingUser;
|
||||
const isNewUser = params.userData.type === 'newUserWithPicture';
|
||||
|
||||
const { user: updatedUser, userWorkspace } =
|
||||
await this.userWorkspaceService.addUserToWorkspace(
|
||||
currentUser,
|
||||
if (isNewUser) {
|
||||
const userData = params.userData as {
|
||||
type: 'newUserWithPicture';
|
||||
newUserWithPicture: PartialUserWithPicture;
|
||||
};
|
||||
|
||||
const user = await this.saveNewUser(
|
||||
userData.newUserWithPicture,
|
||||
params.workspace.id,
|
||||
{
|
||||
canAccessFullAdminPanel: false,
|
||||
canImpersonate: false,
|
||||
},
|
||||
);
|
||||
|
||||
await this.activateOnboardingForUser(user, params.workspace);
|
||||
|
||||
await this.userWorkspaceService.addUserToWorkspaceIfUserNotInWorkspace(
|
||||
user,
|
||||
params.workspace,
|
||||
);
|
||||
|
||||
const user = Object.assign(currentUser, updatedUser);
|
||||
|
||||
const isSignUp = params.userData.type === 'newUserWithPicture';
|
||||
|
||||
if (isSignUp) {
|
||||
await this.activateOnboardingForUser(user, params.workspace);
|
||||
|
||||
if (params.workspace.defaultRoleId) {
|
||||
await this.userRoleService.assignRoleToUserWorkspace({
|
||||
workspaceId: params.workspace.id,
|
||||
userWorkspaceId: userWorkspace.id,
|
||||
roleId: params.workspace.defaultRoleId,
|
||||
});
|
||||
}
|
||||
return user;
|
||||
}
|
||||
|
||||
const userData = params.userData as {
|
||||
type: 'existingUser';
|
||||
existingUser: User;
|
||||
};
|
||||
|
||||
const user = userData.existingUser;
|
||||
|
||||
await this.userWorkspaceService.addUserToWorkspaceIfUserNotInWorkspace(
|
||||
user,
|
||||
params.workspace,
|
||||
);
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user