Add default role to workspace (#10444)
## Context Adding a defaultRole to each workspace, this role will be automatically added when a member joins a workspace via invite link or public link (seeds work differently though). Took the occasion to refactor a bit the frontend components, splitting them in smaller components for more readability. ## Test <img width="948" alt="Screenshot 2025-02-24 at 14 54 02" src="https://github.com/user-attachments/assets/13ef1452-d3c9-4385-940c-2ced0f0b05ef" />
This commit is contained in:
@ -15,9 +15,9 @@ import { ApiKeyService } from 'src/engine/core-modules/auth/services/api-key.ser
|
||||
import { GoogleAPIsService } from 'src/engine/core-modules/auth/services/google-apis.service';
|
||||
import { MicrosoftAPIsService } from 'src/engine/core-modules/auth/services/microsoft-apis.service';
|
||||
// import { OAuthService } from 'src/engine/core-modules/auth/services/oauth.service';
|
||||
import { AuthSsoService } from 'src/engine/core-modules/auth/services/auth-sso.service';
|
||||
import { ResetPasswordService } from 'src/engine/core-modules/auth/services/reset-password.service';
|
||||
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 { SamlAuthStrategy } from 'src/engine/core-modules/auth/strategies/saml.auth.strategy';
|
||||
import { AccessTokenService } from 'src/engine/core-modules/auth/token/services/access-token.service';
|
||||
import { LoginTokenService } from 'src/engine/core-modules/auth/token/services/login-token.service';
|
||||
@ -46,6 +46,7 @@ import { WorkspaceModule } from 'src/engine/core-modules/workspace/workspace.mod
|
||||
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module';
|
||||
import { UserRoleModule } from 'src/engine/metadata-modules/user-role/user-role.module';
|
||||
import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module';
|
||||
import { WorkspaceManagerModule } from 'src/engine/workspace-manager/workspace-manager.module';
|
||||
import { ConnectedAccountModule } from 'src/modules/connected-account/connected-account.module';
|
||||
@ -91,6 +92,7 @@ import { JwtAuthStrategy } from './strategies/jwt.auth.strategy';
|
||||
GuardRedirectModule,
|
||||
HealthModule,
|
||||
PermissionsModule,
|
||||
UserRoleModule,
|
||||
],
|
||||
controllers: [
|
||||
GoogleAuthController,
|
||||
|
||||
@ -6,6 +6,10 @@ import { WorkspaceActivationStatus } from 'twenty-shared';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { AppToken } from 'src/engine/core-modules/app-token/app-token.entity';
|
||||
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 {
|
||||
AuthProviderWithPasswordType,
|
||||
@ -14,18 +18,16 @@ import {
|
||||
} from 'src/engine/core-modules/auth/types/signInUp.type';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
|
||||
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
||||
import { FileUploadService } from 'src/engine/core-modules/file/file-upload/services/file-upload.service';
|
||||
import { OnboardingService } from 'src/engine/core-modules/onboarding/onboarding.service';
|
||||
import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
|
||||
import { UserWorkspaceService } from 'src/engine/core-modules/user-workspace/user-workspace.service';
|
||||
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';
|
||||
import { UserRoleService } from 'src/engine/metadata-modules/user-role/user-role.service';
|
||||
|
||||
jest.mock('src/utils/image', () => {
|
||||
return {
|
||||
@ -42,6 +44,8 @@ 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({
|
||||
@ -117,6 +121,18 @@ describe('SignInUpService', () => {
|
||||
generateSubdomain: jest.fn(),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: UserRoleService,
|
||||
useValue: {
|
||||
assignRoleToUserWorkspace: jest.fn(),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: FeatureFlagService,
|
||||
useValue: {
|
||||
isFeatureEnabled: jest.fn(),
|
||||
},
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
@ -132,6 +148,8 @@ 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 () => {
|
||||
@ -161,9 +179,10 @@ describe('SignInUpService', () => {
|
||||
.spyOn(workspaceInvitationService, 'invalidateWorkspaceInvitation')
|
||||
.mockResolvedValue(undefined);
|
||||
|
||||
jest
|
||||
.spyOn(userWorkspaceService, 'addUserToWorkspace')
|
||||
.mockResolvedValue({} as User);
|
||||
jest.spyOn(userWorkspaceService, 'addUserToWorkspace').mockResolvedValue({
|
||||
user: {} as User,
|
||||
userWorkspace: {} as UserWorkspace,
|
||||
});
|
||||
|
||||
const result = await service.signInUp(params);
|
||||
|
||||
@ -198,9 +217,10 @@ describe('SignInUpService', () => {
|
||||
},
|
||||
};
|
||||
|
||||
jest
|
||||
.spyOn(userWorkspaceService, 'addUserToWorkspace')
|
||||
.mockResolvedValue({} as User);
|
||||
jest.spyOn(userWorkspaceService, 'addUserToWorkspace').mockResolvedValue({
|
||||
user: {} as User,
|
||||
userWorkspace: {} as UserWorkspace,
|
||||
});
|
||||
|
||||
const result = await service.signInUp(params);
|
||||
|
||||
@ -271,9 +291,10 @@ describe('SignInUpService', () => {
|
||||
};
|
||||
|
||||
jest.spyOn(environmentService, 'get').mockReturnValue(false);
|
||||
jest
|
||||
.spyOn(userWorkspaceService, 'addUserToWorkspace')
|
||||
.mockResolvedValue({} as User);
|
||||
jest.spyOn(userWorkspaceService, 'addUserToWorkspace').mockResolvedValue({
|
||||
user: {} as User,
|
||||
userWorkspace: {} as UserWorkspace,
|
||||
});
|
||||
jest
|
||||
.spyOn(userWorkspaceService, 'checkUserWorkspaceExists')
|
||||
.mockResolvedValue({} as UserWorkspace);
|
||||
@ -312,4 +333,38 @@ describe('SignInUpService', () => {
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it('should assign default role when permissions are enabled', 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).toHaveBeenCalledWith({
|
||||
workspaceId: params.workspace!.id,
|
||||
userWorkspaceId: mockUserWorkspace.id,
|
||||
roleId: params.workspace!.defaultRoleId,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -31,6 +31,8 @@ import {
|
||||
} from 'src/engine/core-modules/auth/types/signInUp.type';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
|
||||
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
||||
import { FileUploadService } from 'src/engine/core-modules/file/file-upload/services/file-upload.service';
|
||||
import { OnboardingService } from 'src/engine/core-modules/onboarding/onboarding.service';
|
||||
import { UserWorkspaceService } from 'src/engine/core-modules/user-workspace/user-workspace.service';
|
||||
@ -38,6 +40,7 @@ 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 { UserRoleService } from 'src/engine/metadata-modules/user-role/user-role.service';
|
||||
import { getDomainNameByEmail } from 'src/utils/get-domain-name-by-email';
|
||||
import { getImageBufferFromUrl } from 'src/utils/image';
|
||||
import { isWorkEmail } from 'src/utils/is-work-email';
|
||||
@ -58,6 +61,8 @@ export class SignInUpService {
|
||||
private readonly environmentService: EnvironmentService,
|
||||
private readonly domainManagerService: DomainManagerService,
|
||||
private readonly userService: UserService,
|
||||
private readonly userRoleService: UserRoleService,
|
||||
private readonly featureFlagService: FeatureFlagService,
|
||||
) {}
|
||||
|
||||
async computeParamsForNewUser(
|
||||
@ -256,10 +261,11 @@ export class SignInUpService {
|
||||
)
|
||||
: params.userData.existingUser;
|
||||
|
||||
const updatedUser = await this.userWorkspaceService.addUserToWorkspace(
|
||||
currentUser,
|
||||
params.workspace,
|
||||
);
|
||||
const { user: updatedUser, userWorkspace } =
|
||||
await this.userWorkspaceService.addUserToWorkspace(
|
||||
currentUser,
|
||||
params.workspace,
|
||||
);
|
||||
|
||||
const user = Object.assign(currentUser, updatedUser);
|
||||
|
||||
@ -267,6 +273,19 @@ export class SignInUpService {
|
||||
await this.activateOnboardingForUser(user, params.workspace);
|
||||
}
|
||||
|
||||
const isPermissionsEnabled = await this.featureFlagService.isFeatureEnabled(
|
||||
FeatureFlagKey.IsPermissionsEnabled,
|
||||
params.workspace.id,
|
||||
);
|
||||
|
||||
if (isPermissionsEnabled && params.workspace.defaultRoleId) {
|
||||
await this.userRoleService.assignRoleToUserWorkspace({
|
||||
workspaceId: params.workspace.id,
|
||||
userWorkspaceId: userWorkspace.id,
|
||||
roleId: params.workspace.defaultRoleId,
|
||||
});
|
||||
}
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user