Add settings permissions check on FE (#11425)

## Context
Now that we can update role settings permissions, we need to reflect
that on the FE as well (hiding/showing nav items + redirection logic).
Feature flag check here is not really needed because since not having
any setting permission will result in the same behavior as Permission
V1.
This PR updates the resolvers to return settings permissions of the
current user
This commit is contained in:
Weiko
2025-04-07 17:15:33 +02:00
committed by GitHub
parent d4db399933
commit f403c551d7
8 changed files with 51 additions and 18 deletions

View File

@ -190,7 +190,7 @@ export abstract class GraphqlQueryBaseResolverService<
const userHasPermission =
await this.permissionsService.userHasWorkspaceSettingPermission({
userWorkspaceId: authContext.userWorkspaceId,
_setting: permissionRequired,
setting: permissionRequired,
workspaceId: authContext.workspace.id,
isExecutedByApiKey: isDefined(authContext.apiKey),
});

View File

@ -163,7 +163,7 @@ export class BillingResolver {
await this.permissionsService.userHasWorkspaceSettingPermission({
userWorkspaceId,
workspaceId,
_setting: SettingPermissionType.WORKSPACE,
setting: SettingPermissionType.WORKSPACE,
isExecutedByApiKey,
});

View File

@ -441,7 +441,7 @@ export class WorkspaceService extends TypeOrmQueryService<Workspace> {
const userHasPermission =
await this.permissionsService.userHasWorkspaceSettingPermission({
userWorkspaceId,
_setting: SettingPermissionType.SECURITY,
setting: SettingPermissionType.SECURITY,
workspaceId: workspaceId,
isExecutedByApiKey: isDefined(apiKey),
});
@ -480,7 +480,7 @@ export class WorkspaceService extends TypeOrmQueryService<Workspace> {
await this.permissionsService.userHasWorkspaceSettingPermission({
userWorkspaceId,
workspaceId,
_setting: SettingPermissionType.WORKSPACE,
setting: SettingPermissionType.WORKSPACE,
isExecutedByApiKey: isDefined(apiKey),
});

View File

@ -32,7 +32,7 @@ export const SettingsPermissionsGuard = (
const hasPermission =
await this.permissionsService.userHasWorkspaceSettingPermission({
userWorkspaceId,
_setting: requiredPermission,
setting: requiredPermission,
workspaceId,
isExecutedByApiKey: isDefined(ctx.getContext().req.apiKey),
});

View File

@ -7,6 +7,7 @@ import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-works
import { PermissionsService } from 'src/engine/metadata-modules/permissions/permissions.service';
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
import { UserWorkspaceRoleEntity } from 'src/engine/metadata-modules/role/user-workspace-role.entity';
import { SettingPermissionEntity } from 'src/engine/metadata-modules/setting-permission/setting-permission.entity';
import { UserRoleModule } from 'src/engine/metadata-modules/user-role/user-role.module';
@Module({
@ -16,6 +17,7 @@ import { UserRoleModule } from 'src/engine/metadata-modules/user-role/user-role.
TypeOrmModule.forFeature([UserWorkspace], 'core'),
EnvironmentModule,
UserRoleModule,
TypeOrmModule.forFeature([SettingPermissionEntity], 'metadata'),
],
providers: [PermissionsService],
exports: [PermissionsService],

View File

@ -1,7 +1,9 @@
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { PermissionsOnAllObjectRecords } from 'twenty-shared/constants';
import { isDefined } from 'twenty-shared/utils';
import { Repository } from 'typeorm';
import {
AuthException,
@ -15,6 +17,7 @@ import {
PermissionsExceptionMessage,
} from 'src/engine/metadata-modules/permissions/permissions.exception';
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
import { SettingPermissionEntity } from 'src/engine/metadata-modules/setting-permission/setting-permission.entity';
import { UserRoleService } from 'src/engine/metadata-modules/user-role/user-role.service';
@Injectable()
@ -22,6 +25,8 @@ export class PermissionsService {
constructor(
private readonly environmentService: EnvironmentService,
private readonly userRoleService: UserRoleService,
@InjectRepository(SettingPermissionEntity, 'metadata')
private readonly settingPermissionRepository: Repository<SettingPermissionEntity>,
) {}
public async getUserWorkspacePermissions({
@ -43,14 +48,27 @@ export class PermissionsService {
let hasPermissionOnSettingFeature = false;
if (roleOfUserWorkspace?.canUpdateAllSettings === true) {
if (!isDefined(roleOfUserWorkspace)) {
throw new PermissionsException(
PermissionsExceptionMessage.NO_ROLE_FOUND_FOR_USER_WORKSPACE,
PermissionsExceptionCode.NO_ROLE_FOUND_FOR_USER_WORKSPACE,
);
}
if (roleOfUserWorkspace.canUpdateAllSettings === true) {
hasPermissionOnSettingFeature = true;
}
const settingPermissions = roleOfUserWorkspace.settingPermissions ?? [];
const settingsPermissionsMap = Object.keys(SettingPermissionType).reduce(
(acc, feature) => ({
...acc,
[feature]: hasPermissionOnSettingFeature,
[feature]:
hasPermissionOnSettingFeature ||
settingPermissions.some(
(settingPermission) => settingPermission.setting === feature,
),
}),
{} as Record<SettingPermissionType, boolean>,
);
@ -60,13 +78,13 @@ export class PermissionsService {
boolean
> = {
[PermissionsOnAllObjectRecords.READ_ALL_OBJECT_RECORDS]:
roleOfUserWorkspace?.canReadAllObjectRecords ?? false,
roleOfUserWorkspace.canReadAllObjectRecords ?? false,
[PermissionsOnAllObjectRecords.UPDATE_ALL_OBJECT_RECORDS]:
roleOfUserWorkspace?.canUpdateAllObjectRecords ?? false,
roleOfUserWorkspace.canUpdateAllObjectRecords ?? false,
[PermissionsOnAllObjectRecords.SOFT_DELETE_ALL_OBJECT_RECORDS]:
roleOfUserWorkspace?.canSoftDeleteAllObjectRecords ?? false,
roleOfUserWorkspace.canSoftDeleteAllObjectRecords ?? false,
[PermissionsOnAllObjectRecords.DESTROY_ALL_OBJECT_RECORDS]:
roleOfUserWorkspace?.canDestroyAllObjectRecords ?? false,
roleOfUserWorkspace.canDestroyAllObjectRecords ?? false,
};
return {
@ -78,12 +96,12 @@ export class PermissionsService {
public async userHasWorkspaceSettingPermission({
userWorkspaceId,
workspaceId,
_setting,
setting,
isExecutedByApiKey,
}: {
userWorkspaceId?: string;
workspaceId: string;
_setting: SettingPermissionType;
setting: SettingPermissionType;
isExecutedByApiKey: boolean;
}): Promise<boolean> {
if (isExecutedByApiKey) {
@ -104,11 +122,22 @@ export class PermissionsService {
})
.then((roles) => roles?.get(userWorkspaceId) ?? []);
if (roleOfUserWorkspace?.canUpdateAllSettings === true) {
if (!isDefined(roleOfUserWorkspace)) {
throw new PermissionsException(
PermissionsExceptionMessage.NO_ROLE_FOUND_FOR_USER_WORKSPACE,
PermissionsExceptionCode.NO_ROLE_FOUND_FOR_USER_WORKSPACE,
);
}
if (roleOfUserWorkspace.canUpdateAllSettings === true) {
return true;
}
return false;
const settingPermissions = roleOfUserWorkspace.settingPermissions ?? [];
return settingPermissions.some(
(settingPermission) => settingPermission.setting === setting,
);
}
public async userHasObjectRecordsPermission({

View File

@ -1,7 +1,7 @@
import { InjectRepository } from '@nestjs/typeorm';
import { In, Not, Repository } from 'typeorm';
import { isDefined } from 'twenty-shared/utils';
import { In, Not, Repository } from 'typeorm';
import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
import { ADMIN_ROLE_LABEL } from 'src/engine/metadata-modules/permissions/constants/admin-role-label.constants';
@ -75,7 +75,9 @@ export class UserRoleService {
workspaceId,
},
relations: {
role: true,
role: {
settingPermissions: true,
},
},
});

View File

@ -54,7 +54,7 @@ export class WorkspaceMemberPreQueryHookService {
await this.permissionsService.userHasWorkspaceSettingPermission({
userWorkspaceId,
workspaceId,
_setting: SettingPermissionType.WORKSPACE_MEMBERS,
setting: SettingPermissionType.WORKSPACE_MEMBERS,
isExecutedByApiKey: isDefined(apiKey),
})
) {