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 = const userHasPermission =
await this.permissionsService.userHasWorkspaceSettingPermission({ await this.permissionsService.userHasWorkspaceSettingPermission({
userWorkspaceId: authContext.userWorkspaceId, userWorkspaceId: authContext.userWorkspaceId,
_setting: permissionRequired, setting: permissionRequired,
workspaceId: authContext.workspace.id, workspaceId: authContext.workspace.id,
isExecutedByApiKey: isDefined(authContext.apiKey), isExecutedByApiKey: isDefined(authContext.apiKey),
}); });

View File

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

View File

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

View File

@ -32,7 +32,7 @@ export const SettingsPermissionsGuard = (
const hasPermission = const hasPermission =
await this.permissionsService.userHasWorkspaceSettingPermission({ await this.permissionsService.userHasWorkspaceSettingPermission({
userWorkspaceId, userWorkspaceId,
_setting: requiredPermission, setting: requiredPermission,
workspaceId, workspaceId,
isExecutedByApiKey: isDefined(ctx.getContext().req.apiKey), 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 { PermissionsService } from 'src/engine/metadata-modules/permissions/permissions.service';
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity'; import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
import { UserWorkspaceRoleEntity } from 'src/engine/metadata-modules/role/user-workspace-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'; import { UserRoleModule } from 'src/engine/metadata-modules/user-role/user-role.module';
@Module({ @Module({
@ -16,6 +17,7 @@ import { UserRoleModule } from 'src/engine/metadata-modules/user-role/user-role.
TypeOrmModule.forFeature([UserWorkspace], 'core'), TypeOrmModule.forFeature([UserWorkspace], 'core'),
EnvironmentModule, EnvironmentModule,
UserRoleModule, UserRoleModule,
TypeOrmModule.forFeature([SettingPermissionEntity], 'metadata'),
], ],
providers: [PermissionsService], providers: [PermissionsService],
exports: [PermissionsService], exports: [PermissionsService],

View File

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

View File

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

View File

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