[permissions] Add settingsPermissions to getCurrentUser (#10054)

For a member with admin role:
<img width="392" alt="Capture d’écran 2025-02-06 à 15 04 07"
src="https://github.com/user-attachments/assets/f47e8611-9577-4d0b-889c-0846acfb0d75"
/>

For a member without admin role:
<img width="394" alt="Capture d’écran 2025-02-06 à 15 04 51"
src="https://github.com/user-attachments/assets/5daacd7a-aa4f-4e06-a886-661bf0830418"
/>

For a member of a workspace that does not have the feature flag enabled:
<img width="390" alt="Capture d’écran 2025-02-06 à 15 05 22"
src="https://github.com/user-attachments/assets/05071bd6-fd2d-4823-b121-8fd11313b833"
/>
This commit is contained in:
Marie
2025-02-07 15:33:17 +01:00
committed by GitHub
parent 08b8a0cc60
commit 859e7c94f9
8 changed files with 83 additions and 7 deletions

View File

@ -1,6 +1,7 @@
import { Field, ObjectType } from '@nestjs/graphql';
import { Field, ObjectType, registerEnumType } from '@nestjs/graphql';
import { IDField } from '@ptc-org/nestjs-query-graphql';
import { SettingsFeatures } from 'twenty-shared';
import {
Column,
CreateDateColumn,
@ -19,6 +20,10 @@ import { TwoFactorMethod } from 'src/engine/core-modules/two-factor-method/two-f
import { User } from 'src/engine/core-modules/user/user.entity';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
registerEnumType(SettingsFeatures, {
name: 'SettingsFeatures',
});
@Entity({ name: 'userWorkspace', schema: 'core' })
@ObjectType()
@Unique('IndexOnUserIdAndWorkspaceIdUnique', ['userId', 'workspaceId'])
@ -66,4 +71,7 @@ export class UserWorkspace {
(twoFactorMethod) => twoFactorMethod.userWorkspace,
)
twoFactorMethods: Relation<TwoFactorMethod[]>;
@Field(() => [SettingsFeatures], { nullable: true })
settingsPermissions?: SettingsFeatures[];
}

View File

@ -19,6 +19,7 @@ import { KeyValuePair } from 'src/engine/core-modules/key-value-pair/key-value-p
import { OnboardingStatus } from 'src/engine/core-modules/onboarding/enums/onboarding-status.enum';
import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
import { WorkspaceMember } from 'src/engine/core-modules/user/dtos/workspace-member.dto';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
registerEnumType(OnboardingStatus, {
name: 'OnboardingStatus',
@ -99,4 +100,10 @@ export class User {
@Field(() => OnboardingStatus, { nullable: true })
onboardingStatus: OnboardingStatus;
@Field(() => Workspace, { nullable: true })
currentWorkspace: Relation<Workspace>;
@Field(() => UserWorkspace, { nullable: true })
currentUserWorkspace?: Relation<UserWorkspace>;
}

View File

@ -21,6 +21,7 @@ import { UserResolver } from 'src/engine/core-modules/user/user.resolver';
import { WorkspaceModule } from 'src/engine/core-modules/workspace/workspace.module';
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 { userAutoResolverOpts } from './user.auto-resolver-opts';
@ -48,6 +49,7 @@ import { UserService } from './services/user.service';
DomainManagerModule,
UserRoleModule,
FeatureFlagModule,
PermissionsModule,
],
exports: [UserService],
providers: [UserService, UserResolver, TypeORMService],

View File

@ -13,6 +13,7 @@ import crypto from 'crypto';
import { GraphQLJSONObject } from 'graphql-type-json';
import { FileUpload, GraphQLUpload } from 'graphql-upload';
import { SettingsFeatures } from 'twenty-shared';
import { In, Repository } from 'typeorm';
import { SupportDriver } from 'src/engine/core-modules/environment/interfaces/support.interface';
@ -47,6 +48,7 @@ import { AuthUser } from 'src/engine/decorators/auth/auth-user.decorator';
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
import { OriginHeader } from 'src/engine/decorators/auth/origin-header.decorator';
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
import { PermissionsService } from 'src/engine/metadata-modules/permissions/permissions.service';
import { RoleDTO } from 'src/engine/metadata-modules/role/dtos/role.dto';
import { UserRoleService } from 'src/engine/metadata-modules/user-role/user-role.service';
import { AccountsToReconnectKeys } from 'src/modules/connected-account/types/accounts-to-reconnect-key-value.type';
@ -77,11 +79,15 @@ export class UserResolver {
@InjectRepository(UserWorkspace, 'core')
private readonly userWorkspaceRepository: Repository<UserWorkspace>,
private readonly userRoleService: UserRoleService,
private readonly permissionsService: PermissionsService,
private readonly featureFlagService: FeatureFlagService,
) {}
@Query(() => User)
async currentUser(@AuthUser() { id: userId }: User): Promise<User> {
async currentUser(
@AuthUser() { id: userId }: User,
@AuthWorkspace() workspace: Workspace,
): Promise<User> {
const user = await this.userRepository.findOne({
where: {
id: userId,
@ -94,7 +100,36 @@ export class UserResolver {
new AuthException('User not found', AuthExceptionCode.USER_NOT_FOUND),
);
return user;
const permissionsEnabled = await this.featureFlagService.isFeatureEnabled(
FeatureFlagKey.IsPermissionsEnabled,
workspace.id,
);
if (permissionsEnabled === true) {
const currentUserWorkspace = user.workspaces.find(
(userWorkspace) => userWorkspace.workspace.id === workspace.id,
);
if (!currentUserWorkspace) {
throw new Error('Current user workspace not found');
}
const permissions =
await this.permissionsService.getUserWorkspaceSettingsPermissions({
userWorkspaceId: currentUserWorkspace.id,
});
const permittedFeatures: SettingsFeatures[] = (
Object.keys(permissions) as SettingsFeatures[]
).filter((feature) => permissions[feature] === true);
currentUserWorkspace.settingsPermissions = permittedFeatures;
user.currentUserWorkspace = currentUserWorkspace;
}
return {
...user,
currentWorkspace: workspace,
};
}
@ResolveField(() => GraphQLJSONObject)