[Permissions] Implement getRoles (#9955)
In this PR - introducing roles module to separate roles logic (assign a Role, get a workspace's roles etc.) from permission logic (check if a user has a permission) - Introduces getRoles endpoint to fetch a workspace's roles - introduces the first permission check: getRoles in only accessible to users with permission on ROLE setting. Implemented validatesUserHasWorkspaceSettingPermissionOrThrow
This commit is contained in:
@ -3,8 +3,10 @@ import { Module } from '@nestjs/common';
|
|||||||
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
|
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
|
||||||
import { FieldMetadataModule } from 'src/engine/metadata-modules/field-metadata/field-metadata.module';
|
import { FieldMetadataModule } from 'src/engine/metadata-modules/field-metadata/field-metadata.module';
|
||||||
import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadata/object-metadata.module';
|
import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadata/object-metadata.module';
|
||||||
|
import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module';
|
||||||
import { RelationMetadataModule } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.module';
|
import { RelationMetadataModule } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.module';
|
||||||
import { RemoteServerModule } from 'src/engine/metadata-modules/remote-server/remote-server.module';
|
import { RemoteServerModule } from 'src/engine/metadata-modules/remote-server/remote-server.module';
|
||||||
|
import { RoleModule } from 'src/engine/metadata-modules/role/role.module';
|
||||||
import { ServerlessFunctionModule } from 'src/engine/metadata-modules/serverless-function/serverless-function.module';
|
import { ServerlessFunctionModule } from 'src/engine/metadata-modules/serverless-function/serverless-function.module';
|
||||||
import { WorkspaceMetadataVersionModule } from 'src/engine/metadata-modules/workspace-metadata-version/workspace-metadata-version.module';
|
import { WorkspaceMetadataVersionModule } from 'src/engine/metadata-modules/workspace-metadata-version/workspace-metadata-version.module';
|
||||||
import { WorkspaceMigrationModule } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.module';
|
import { WorkspaceMigrationModule } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.module';
|
||||||
@ -19,6 +21,8 @@ import { WorkspaceMigrationModule } from 'src/engine/metadata-modules/workspace-
|
|||||||
WorkspaceMetadataVersionModule,
|
WorkspaceMetadataVersionModule,
|
||||||
WorkspaceMigrationModule,
|
WorkspaceMigrationModule,
|
||||||
RemoteServerModule,
|
RemoteServerModule,
|
||||||
|
RoleModule,
|
||||||
|
PermissionsModule,
|
||||||
],
|
],
|
||||||
providers: [],
|
providers: [],
|
||||||
exports: [
|
exports: [
|
||||||
@ -28,6 +32,8 @@ import { WorkspaceMigrationModule } from 'src/engine/metadata-modules/workspace-
|
|||||||
RelationMetadataModule,
|
RelationMetadataModule,
|
||||||
ServerlessFunctionModule,
|
ServerlessFunctionModule,
|
||||||
RemoteServerModule,
|
RemoteServerModule,
|
||||||
|
RoleModule,
|
||||||
|
PermissionsModule,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class MetadataEngineModule {}
|
export class MetadataEngineModule {}
|
||||||
|
|||||||
@ -12,4 +12,6 @@ export enum PermissionsExceptionCode {
|
|||||||
USER_WORKSPACE_NOT_FOUND = 'USER_WORKSPACE_NOT_FOUND',
|
USER_WORKSPACE_NOT_FOUND = 'USER_WORKSPACE_NOT_FOUND',
|
||||||
WORKSPACE_ID_ROLE_USER_WORKSPACE_MISMATCH = 'WORKSPACE_ID_ROLE_USER_WORKSPACE_MISMATCH',
|
WORKSPACE_ID_ROLE_USER_WORKSPACE_MISMATCH = 'WORKSPACE_ID_ROLE_USER_WORKSPACE_MISMATCH',
|
||||||
TOO_MANY_ADMIN_CANDIDATES = 'TOO_MANY_ADMIN_CANDIDATES',
|
TOO_MANY_ADMIN_CANDIDATES = 'TOO_MANY_ADMIN_CANDIDATES',
|
||||||
|
USER_WORKSPACE_ALREADY_HAS_ROLE = 'USER_WORKSPACE_ALREADY_HAS_ROLE',
|
||||||
|
PERMISSION_DENIED = 'PERMISSION_DENIED',
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,17 +1,21 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
|
||||||
|
import { EnvironmentModule } from 'src/engine/core-modules/environment/environment.module';
|
||||||
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
||||||
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 { 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/permissions/role.entity';
|
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
|
||||||
import { UserWorkspaceRoleEntity } from 'src/engine/metadata-modules/permissions/user-workspace-role.entity';
|
import { UserWorkspaceRoleEntity } from 'src/engine/metadata-modules/role/user-workspace-role.entity';
|
||||||
|
import { UserRoleModule } from 'src/engine/metadata-modules/userRole/userRole.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
TypeOrmModule.forFeature([RoleEntity, UserWorkspaceRoleEntity], 'metadata'),
|
TypeOrmModule.forFeature([RoleEntity, UserWorkspaceRoleEntity], 'metadata'),
|
||||||
FeatureFlagModule,
|
FeatureFlagModule,
|
||||||
TypeOrmModule.forFeature([UserWorkspace], 'core'),
|
TypeOrmModule.forFeature([UserWorkspace], 'core'),
|
||||||
|
EnvironmentModule,
|
||||||
|
UserRoleModule,
|
||||||
],
|
],
|
||||||
providers: [PermissionsService],
|
providers: [PermissionsService],
|
||||||
exports: [PermissionsService],
|
exports: [PermissionsService],
|
||||||
|
|||||||
@ -1,71 +1,62 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
|
||||||
|
|
||||||
import { Repository } from 'typeorm';
|
import { SettingsFeatures } from 'twenty-shared';
|
||||||
import { isDefined } from 'twenty-shared';
|
|
||||||
|
|
||||||
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
|
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
|
||||||
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 {
|
import {
|
||||||
PermissionsException,
|
PermissionsException,
|
||||||
PermissionsExceptionCode,
|
PermissionsExceptionCode,
|
||||||
} from 'src/engine/metadata-modules/permissions/permissions.exception';
|
} from 'src/engine/metadata-modules/permissions/permissions.exception';
|
||||||
import { RoleEntity } from 'src/engine/metadata-modules/permissions/role.entity';
|
import { UserRoleService } from 'src/engine/metadata-modules/userRole/userRole.service';
|
||||||
import { UserWorkspaceRoleEntity } from 'src/engine/metadata-modules/permissions/user-workspace-role.entity';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class PermissionsService {
|
export class PermissionsService {
|
||||||
constructor(
|
constructor(
|
||||||
@InjectRepository(RoleEntity, 'metadata')
|
|
||||||
private readonly roleRepository: Repository<RoleEntity>,
|
|
||||||
@InjectRepository(UserWorkspaceRoleEntity, 'metadata')
|
|
||||||
private readonly userWorkspaceRoleRepository: Repository<UserWorkspaceRoleEntity>,
|
|
||||||
@InjectRepository(UserWorkspace, 'core')
|
|
||||||
private readonly userWorkspaceRepository: Repository<UserWorkspace>,
|
|
||||||
private readonly environmentService: EnvironmentService,
|
private readonly environmentService: EnvironmentService,
|
||||||
|
private readonly userRoleService: UserRoleService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public async createAdminRole({
|
public async getUserWorkspaceSettingsPermissions({
|
||||||
workspaceId,
|
userWorkspaceId,
|
||||||
}: {
|
}: {
|
||||||
workspaceId: string;
|
userWorkspaceId: string;
|
||||||
}): Promise<RoleEntity> {
|
}): Promise<Record<SettingsFeatures, boolean>> {
|
||||||
return this.roleRepository.save({
|
const roleOfUserWorkspace =
|
||||||
label: ADMIN_ROLE_LABEL,
|
await this.userRoleService.getRoleForUserWorkspace(userWorkspaceId);
|
||||||
description: 'Admin role',
|
|
||||||
canUpdateAllSettings: true,
|
let hasPermissionOnSettingFeature = false;
|
||||||
isEditable: false,
|
|
||||||
workspaceId,
|
if (roleOfUserWorkspace?.canUpdateAllSettings === true) {
|
||||||
});
|
hasPermissionOnSettingFeature = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Object.keys(SettingsFeatures).reduce(
|
||||||
|
(acc, feature) => ({
|
||||||
|
...acc,
|
||||||
|
[feature]: hasPermissionOnSettingFeature,
|
||||||
|
}),
|
||||||
|
{} as Record<SettingsFeatures, boolean>,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async assignRoleToUserWorkspace({
|
public async validateUserHasWorkspaceSettingPermissionOrThrow({
|
||||||
workspaceId,
|
|
||||||
userWorkspaceId,
|
userWorkspaceId,
|
||||||
roleId,
|
setting,
|
||||||
}: {
|
}: {
|
||||||
workspaceId: string;
|
|
||||||
userWorkspaceId: string;
|
userWorkspaceId: string;
|
||||||
roleId: string;
|
setting: SettingsFeatures;
|
||||||
}): Promise<void> {
|
}): Promise<void> {
|
||||||
const userWorkspace = await this.userWorkspaceRepository.findOne({
|
const userWorkspaceRole =
|
||||||
where: {
|
await this.userRoleService.getRoleForUserWorkspace(userWorkspaceId);
|
||||||
id: userWorkspaceId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!isDefined(userWorkspace)) {
|
if (userWorkspaceRole?.canUpdateAllSettings === true) {
|
||||||
throw new PermissionsException(
|
return;
|
||||||
'User workspace not found',
|
|
||||||
PermissionsExceptionCode.USER_WORKSPACE_NOT_FOUND,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
await this.userWorkspaceRoleRepository.save({
|
|
||||||
roleId,
|
throw new PermissionsException(
|
||||||
userWorkspaceId: userWorkspace.id,
|
`User does not have permission to update this setting: ${setting}`,
|
||||||
workspaceId,
|
PermissionsExceptionCode.PERMISSION_DENIED,
|
||||||
});
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async isPermissionsEnabled(): Promise<boolean> {
|
public async isPermissionsEnabled(): Promise<boolean> {
|
||||||
|
|||||||
@ -0,0 +1,21 @@
|
|||||||
|
import {
|
||||||
|
ForbiddenError,
|
||||||
|
InternalServerError,
|
||||||
|
} from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
|
||||||
|
import {
|
||||||
|
PermissionsException,
|
||||||
|
PermissionsExceptionCode,
|
||||||
|
} from 'src/engine/metadata-modules/permissions/permissions.exception';
|
||||||
|
|
||||||
|
export const permissionsGraphqlApiExceptionHandler = (error: Error) => {
|
||||||
|
if (error instanceof PermissionsException) {
|
||||||
|
switch (error.code) {
|
||||||
|
case PermissionsExceptionCode.PERMISSION_DENIED:
|
||||||
|
throw new ForbiddenError(error.message);
|
||||||
|
default:
|
||||||
|
throw new InternalServerError(error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
};
|
||||||
@ -0,0 +1,30 @@
|
|||||||
|
import { Field, HideField, ObjectType } from '@nestjs/graphql';
|
||||||
|
|
||||||
|
import { Relation } from 'typeorm';
|
||||||
|
|
||||||
|
import { WorkspaceMember } from 'src/engine/core-modules/user/dtos/workspace-member.dto';
|
||||||
|
import { UserWorkspaceRoleEntity } from 'src/engine/metadata-modules/role/user-workspace-role.entity';
|
||||||
|
|
||||||
|
@ObjectType()
|
||||||
|
export class RoleDTO {
|
||||||
|
@Field({ nullable: false })
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
@Field({ nullable: false })
|
||||||
|
label: string;
|
||||||
|
|
||||||
|
@Field({ nullable: false })
|
||||||
|
canUpdateAllSettings: boolean;
|
||||||
|
|
||||||
|
@Field({ nullable: true })
|
||||||
|
description: string;
|
||||||
|
|
||||||
|
@Field({ nullable: false })
|
||||||
|
isEditable: boolean;
|
||||||
|
|
||||||
|
@HideField()
|
||||||
|
userWorkspaceRoles: Relation<UserWorkspaceRoleEntity[]>;
|
||||||
|
|
||||||
|
@Field(() => [WorkspaceMember], { nullable: true })
|
||||||
|
workspaceMembers?: WorkspaceMember[];
|
||||||
|
}
|
||||||
@ -8,7 +8,7 @@ import {
|
|||||||
UpdateDateColumn,
|
UpdateDateColumn,
|
||||||
} from 'typeorm';
|
} from 'typeorm';
|
||||||
|
|
||||||
import { UserWorkspaceRoleEntity } from 'src/engine/metadata-modules/permissions/user-workspace-role.entity';
|
import { UserWorkspaceRoleEntity } from 'src/engine/metadata-modules/role/user-workspace-role.entity';
|
||||||
|
|
||||||
@Entity('role')
|
@Entity('role')
|
||||||
export class RoleEntity {
|
export class RoleEntity {
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
|
||||||
|
import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
|
||||||
|
import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module';
|
||||||
|
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
|
||||||
|
import { RoleResolver } from 'src/engine/metadata-modules/role/role.resolver';
|
||||||
|
import { RoleService } from 'src/engine/metadata-modules/role/role.service';
|
||||||
|
import { UserWorkspaceRoleEntity } from 'src/engine/metadata-modules/role/user-workspace-role.entity';
|
||||||
|
import { UserRoleModule } from 'src/engine/metadata-modules/userRole/userRole.module';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [
|
||||||
|
TypeOrmModule.forFeature([RoleEntity, UserWorkspaceRoleEntity], 'metadata'),
|
||||||
|
TypeOrmModule.forFeature([UserWorkspace], 'core'),
|
||||||
|
UserRoleModule,
|
||||||
|
PermissionsModule,
|
||||||
|
],
|
||||||
|
providers: [RoleService, RoleResolver],
|
||||||
|
exports: [RoleService],
|
||||||
|
})
|
||||||
|
export class RoleModule {}
|
||||||
@ -0,0 +1,65 @@
|
|||||||
|
import { Parent, Query, ResolveField, Resolver } from '@nestjs/graphql';
|
||||||
|
|
||||||
|
import { SettingsFeatures } from 'twenty-shared';
|
||||||
|
|
||||||
|
import { WorkspaceMember } from 'src/engine/core-modules/user/dtos/workspace-member.dto';
|
||||||
|
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||||
|
import { AuthUserWorkspaceId } from 'src/engine/decorators/auth/auth-user-workspace-id.decorator';
|
||||||
|
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||||
|
import { PermissionsService } from 'src/engine/metadata-modules/permissions/permissions.service';
|
||||||
|
import { permissionsGraphqlApiExceptionHandler } from 'src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception-handler';
|
||||||
|
import { RoleDTO } from 'src/engine/metadata-modules/role/dtos/role.dto';
|
||||||
|
import { RoleService } from 'src/engine/metadata-modules/role/role.service';
|
||||||
|
import { UserRoleService } from 'src/engine/metadata-modules/userRole/userRole.service';
|
||||||
|
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
|
||||||
|
|
||||||
|
@Resolver(() => RoleDTO)
|
||||||
|
export class RoleResolver {
|
||||||
|
constructor(
|
||||||
|
private readonly userRoleService: UserRoleService,
|
||||||
|
private readonly permissionsService: PermissionsService,
|
||||||
|
private readonly roleService: RoleService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
@Query(() => [RoleDTO])
|
||||||
|
async getRoles(
|
||||||
|
@AuthUserWorkspaceId() userWorkspaceId: string,
|
||||||
|
@AuthWorkspace() workspace: Workspace,
|
||||||
|
): Promise<RoleDTO[]> {
|
||||||
|
try {
|
||||||
|
await this.permissionsService.validateUserHasWorkspaceSettingPermissionOrThrow(
|
||||||
|
{
|
||||||
|
userWorkspaceId,
|
||||||
|
setting: SettingsFeatures.ROLES,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const roles = await this.roleService.getWorkspaceRoles(workspace.id);
|
||||||
|
|
||||||
|
return roles.map((role) => ({
|
||||||
|
id: role.id,
|
||||||
|
label: role.label,
|
||||||
|
canUpdateAllSettings: role.canUpdateAllSettings,
|
||||||
|
description: role.description,
|
||||||
|
workspaceId: role.workspaceId,
|
||||||
|
createdAt: role.createdAt,
|
||||||
|
updatedAt: role.updatedAt,
|
||||||
|
isEditable: role.isEditable,
|
||||||
|
userWorkspaceRoles: role.userWorkspaceRoles,
|
||||||
|
}));
|
||||||
|
} catch (error) {
|
||||||
|
return permissionsGraphqlApiExceptionHandler(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ResolveField('workspaceMembers', () => [WorkspaceMember])
|
||||||
|
async getWorkspaceMembersAssignedToRole(
|
||||||
|
@Parent() role: RoleDTO,
|
||||||
|
@AuthWorkspace() workspace: Workspace,
|
||||||
|
): Promise<WorkspaceMemberWorkspaceEntity[]> {
|
||||||
|
return this.userRoleService.getWorkspaceMembersAssignedToRole(
|
||||||
|
role.id,
|
||||||
|
workspace.id,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,36 @@
|
|||||||
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
|
||||||
|
import { Repository } from 'typeorm';
|
||||||
|
|
||||||
|
import { ADMIN_ROLE_LABEL } from 'src/engine/metadata-modules/permissions/constants/admin-role-label.constants';
|
||||||
|
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
|
||||||
|
|
||||||
|
export class RoleService {
|
||||||
|
constructor(
|
||||||
|
@InjectRepository(RoleEntity, 'metadata')
|
||||||
|
private readonly roleRepository: Repository<RoleEntity>,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public async getWorkspaceRoles(workspaceId: string): Promise<RoleEntity[]> {
|
||||||
|
return this.roleRepository.find({
|
||||||
|
where: {
|
||||||
|
workspaceId,
|
||||||
|
},
|
||||||
|
relations: ['userWorkspaceRoles'],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async createAdminRole({
|
||||||
|
workspaceId,
|
||||||
|
}: {
|
||||||
|
workspaceId: string;
|
||||||
|
}): Promise<RoleEntity> {
|
||||||
|
return this.roleRepository.save({
|
||||||
|
label: ADMIN_ROLE_LABEL,
|
||||||
|
description: 'Admin role',
|
||||||
|
canUpdateAllSettings: true,
|
||||||
|
isEditable: false,
|
||||||
|
workspaceId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -2,6 +2,7 @@ import {
|
|||||||
Column,
|
Column,
|
||||||
CreateDateColumn,
|
CreateDateColumn,
|
||||||
Entity,
|
Entity,
|
||||||
|
JoinColumn,
|
||||||
ManyToOne,
|
ManyToOne,
|
||||||
PrimaryGeneratedColumn,
|
PrimaryGeneratedColumn,
|
||||||
Relation,
|
Relation,
|
||||||
@ -9,7 +10,7 @@ import {
|
|||||||
UpdateDateColumn,
|
UpdateDateColumn,
|
||||||
} from 'typeorm';
|
} from 'typeorm';
|
||||||
|
|
||||||
import { RoleEntity } from 'src/engine/metadata-modules/permissions/role.entity';
|
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
|
||||||
|
|
||||||
@Entity('userWorkspaceRole')
|
@Entity('userWorkspaceRole')
|
||||||
@Unique('IndexOnUserWorkspaceRoleUnique', ['userWorkspaceId', 'roleId'])
|
@Unique('IndexOnUserWorkspaceRoleUnique', ['userWorkspaceId', 'roleId'])
|
||||||
@ -26,6 +27,7 @@ export class UserWorkspaceRoleEntity {
|
|||||||
@ManyToOne(() => RoleEntity, (role) => role.userWorkspaceRoles, {
|
@ManyToOne(() => RoleEntity, (role) => role.userWorkspaceRoles, {
|
||||||
onDelete: 'CASCADE',
|
onDelete: 'CASCADE',
|
||||||
})
|
})
|
||||||
|
@JoinColumn({ name: 'roleId' })
|
||||||
role: Relation<RoleEntity>;
|
role: Relation<RoleEntity>;
|
||||||
|
|
||||||
@Column({ nullable: false, type: 'uuid' })
|
@Column({ nullable: false, type: 'uuid' })
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
|
||||||
|
import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
|
||||||
|
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
|
||||||
|
import { UserWorkspaceRoleEntity } from 'src/engine/metadata-modules/role/user-workspace-role.entity';
|
||||||
|
import { UserRoleService } from 'src/engine/metadata-modules/userRole/userRole.service';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [
|
||||||
|
TypeOrmModule.forFeature([RoleEntity, UserWorkspaceRoleEntity], 'metadata'),
|
||||||
|
TypeOrmModule.forFeature([UserWorkspace], 'core'),
|
||||||
|
],
|
||||||
|
providers: [UserRoleService],
|
||||||
|
exports: [UserRoleService],
|
||||||
|
})
|
||||||
|
export class UserRoleModule {}
|
||||||
@ -0,0 +1,148 @@
|
|||||||
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
|
||||||
|
import isEmpty from 'lodash.isempty';
|
||||||
|
import { isDefined } from 'twenty-shared';
|
||||||
|
import { In, Repository } from 'typeorm';
|
||||||
|
|
||||||
|
import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
|
||||||
|
import {
|
||||||
|
PermissionsException,
|
||||||
|
PermissionsExceptionCode,
|
||||||
|
} from 'src/engine/metadata-modules/permissions/permissions.exception';
|
||||||
|
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
|
||||||
|
import { UserWorkspaceRoleEntity } from 'src/engine/metadata-modules/role/user-workspace-role.entity';
|
||||||
|
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||||
|
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
|
||||||
|
|
||||||
|
export class UserRoleService {
|
||||||
|
constructor(
|
||||||
|
@InjectRepository(RoleEntity, 'metadata')
|
||||||
|
private readonly roleRepository: Repository<RoleEntity>,
|
||||||
|
@InjectRepository(UserWorkspaceRoleEntity, 'metadata')
|
||||||
|
private readonly userWorkspaceRoleRepository: Repository<UserWorkspaceRoleEntity>,
|
||||||
|
@InjectRepository(UserWorkspace, 'core')
|
||||||
|
private readonly userWorkspaceRepository: Repository<UserWorkspace>,
|
||||||
|
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public async unassignRolesFromUserWorkspace({
|
||||||
|
userWorkspaceId,
|
||||||
|
workspaceId,
|
||||||
|
}: {
|
||||||
|
userWorkspaceId: string;
|
||||||
|
workspaceId: string;
|
||||||
|
}): Promise<void> {
|
||||||
|
const userWorkspaceRoles = await this.userWorkspaceRoleRepository.find({
|
||||||
|
where: {
|
||||||
|
userWorkspaceId,
|
||||||
|
workspaceId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!isEmpty(userWorkspaceRoles)) {
|
||||||
|
userWorkspaceRoles.forEach(async (userWorkspaceRole) => {
|
||||||
|
await this.userWorkspaceRoleRepository.delete({
|
||||||
|
id: userWorkspaceRole.id,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async assignRoleToUserWorkspace({
|
||||||
|
workspaceId,
|
||||||
|
userWorkspaceId,
|
||||||
|
roleId,
|
||||||
|
}: {
|
||||||
|
workspaceId: string;
|
||||||
|
userWorkspaceId: string;
|
||||||
|
roleId: string;
|
||||||
|
}): Promise<void> {
|
||||||
|
const userWorkspace = await this.userWorkspaceRepository.findOne({
|
||||||
|
where: {
|
||||||
|
id: userWorkspaceId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!isDefined(userWorkspace)) {
|
||||||
|
throw new PermissionsException(
|
||||||
|
'User workspace not found',
|
||||||
|
PermissionsExceptionCode.USER_WORKSPACE_NOT_FOUND,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.unassignRolesFromUserWorkspace({
|
||||||
|
userWorkspaceId: userWorkspace.id,
|
||||||
|
workspaceId,
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.userWorkspaceRoleRepository.save({
|
||||||
|
roleId,
|
||||||
|
userWorkspaceId: userWorkspace.id,
|
||||||
|
workspaceId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getRoleForUserWorkspace(
|
||||||
|
userWorkspaceId: string,
|
||||||
|
): Promise<RoleEntity | null> {
|
||||||
|
if (!isDefined(userWorkspaceId)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const userWorkspaceRole = await this.userWorkspaceRoleRepository.findOne({
|
||||||
|
where: {
|
||||||
|
userWorkspaceId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!isDefined(userWorkspaceRole)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return await this.roleRepository.findOne({
|
||||||
|
where: {
|
||||||
|
id: userWorkspaceRole.roleId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getWorkspaceMembersAssignedToRole(
|
||||||
|
roleId: string,
|
||||||
|
workspaceId: string,
|
||||||
|
): Promise<WorkspaceMemberWorkspaceEntity[]> {
|
||||||
|
const userWorkspaceRoles = await this.userWorkspaceRoleRepository.find({
|
||||||
|
where: {
|
||||||
|
roleId,
|
||||||
|
workspaceId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const userIds = await this.userWorkspaceRepository
|
||||||
|
.find({
|
||||||
|
where: {
|
||||||
|
id: In(
|
||||||
|
userWorkspaceRoles.map(
|
||||||
|
(userWorkspaceRole) => userWorkspaceRole.userWorkspaceId,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((userWorkspaces) =>
|
||||||
|
userWorkspaces.map((userWorkspace) => userWorkspace.userId),
|
||||||
|
);
|
||||||
|
|
||||||
|
const workspaceMemberRepository =
|
||||||
|
await this.twentyORMGlobalManager.getRepositoryForWorkspace<WorkspaceMemberWorkspaceEntity>(
|
||||||
|
workspaceId,
|
||||||
|
'workspaceMember',
|
||||||
|
);
|
||||||
|
|
||||||
|
const workspaceMembers = await workspaceMemberRepository.find({
|
||||||
|
where: {
|
||||||
|
userId: In(userIds),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return workspaceMembers;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -146,6 +146,7 @@ export class MiddlewareService {
|
|||||||
request.workspaceId = data.workspace.id;
|
request.workspaceId = data.workspace.id;
|
||||||
request.workspaceMetadataVersion = metadataVersion;
|
request.workspaceMetadataVersion = metadataVersion;
|
||||||
request.workspaceMemberId = data.workspaceMemberId;
|
request.workspaceMemberId = data.workspaceMemberId;
|
||||||
|
request.userWorkspaceId = data.userWorkspaceId;
|
||||||
}
|
}
|
||||||
|
|
||||||
private getStatus(error: any): number {
|
private getStatus(error: any): number {
|
||||||
|
|||||||
@ -6,6 +6,8 @@ import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-works
|
|||||||
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
|
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
|
||||||
import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadata/object-metadata.module';
|
import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadata/object-metadata.module';
|
||||||
import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module';
|
import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module';
|
||||||
|
import { RoleModule } from 'src/engine/metadata-modules/role/role.module';
|
||||||
|
import { UserRoleModule } from 'src/engine/metadata-modules/userRole/userRole.module';
|
||||||
import { WorkspaceMigrationModule } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.module';
|
import { WorkspaceMigrationModule } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.module';
|
||||||
import { SeederModule } from 'src/engine/seeder/seeder.module';
|
import { SeederModule } from 'src/engine/seeder/seeder.module';
|
||||||
import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module';
|
import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module';
|
||||||
@ -26,6 +28,8 @@ import { WorkspaceManagerService } from './workspace-manager.service';
|
|||||||
FeatureFlagModule,
|
FeatureFlagModule,
|
||||||
PermissionsModule,
|
PermissionsModule,
|
||||||
TypeOrmModule.forFeature([UserWorkspace], 'core'),
|
TypeOrmModule.forFeature([UserWorkspace], 'core'),
|
||||||
|
RoleModule,
|
||||||
|
UserRoleModule,
|
||||||
],
|
],
|
||||||
exports: [WorkspaceManagerService],
|
exports: [WorkspaceManagerService],
|
||||||
providers: [WorkspaceManagerService],
|
providers: [WorkspaceManagerService],
|
||||||
|
|||||||
@ -13,6 +13,8 @@ import {
|
|||||||
PermissionsExceptionCode,
|
PermissionsExceptionCode,
|
||||||
} from 'src/engine/metadata-modules/permissions/permissions.exception';
|
} from 'src/engine/metadata-modules/permissions/permissions.exception';
|
||||||
import { PermissionsService } from 'src/engine/metadata-modules/permissions/permissions.service';
|
import { PermissionsService } from 'src/engine/metadata-modules/permissions/permissions.service';
|
||||||
|
import { RoleService } from 'src/engine/metadata-modules/role/role.service';
|
||||||
|
import { UserRoleService } from 'src/engine/metadata-modules/userRole/userRole.service';
|
||||||
import { WorkspaceMigrationService } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.service';
|
import { WorkspaceMigrationService } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.service';
|
||||||
import { PETS_DATA_SEEDS } from 'src/engine/seeder/data-seeds/pets-data-seeds';
|
import { PETS_DATA_SEEDS } from 'src/engine/seeder/data-seeds/pets-data-seeds';
|
||||||
import { SURVEY_RESULTS_DATA_SEEDS } from 'src/engine/seeder/data-seeds/survey-results-data-seeds';
|
import { SURVEY_RESULTS_DATA_SEEDS } from 'src/engine/seeder/data-seeds/survey-results-data-seeds';
|
||||||
@ -36,6 +38,8 @@ export class WorkspaceManagerService {
|
|||||||
private readonly permissionsService: PermissionsService,
|
private readonly permissionsService: PermissionsService,
|
||||||
@InjectRepository(UserWorkspace, 'core')
|
@InjectRepository(UserWorkspace, 'core')
|
||||||
private readonly userWorkspaceRepository: Repository<UserWorkspace>,
|
private readonly userWorkspaceRepository: Repository<UserWorkspace>,
|
||||||
|
private readonly roleService: RoleService,
|
||||||
|
private readonly userRoleService: UserRoleService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -188,7 +192,7 @@ export class WorkspaceManagerService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async initPermissions(workspaceId: string) {
|
private async initPermissions(workspaceId: string) {
|
||||||
const adminRole = await this.permissionsService.createAdminRole({
|
const adminRole = await this.roleService.createAdminRole({
|
||||||
workspaceId,
|
workspaceId,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -212,7 +216,7 @@ export class WorkspaceManagerService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.permissionsService.assignRoleToUserWorkspace({
|
await this.userRoleService.assignRoleToUserWorkspace({
|
||||||
workspaceId,
|
workspaceId,
|
||||||
userWorkspaceId: userWorkspace[0].id,
|
userWorkspaceId: userWorkspace[0].id,
|
||||||
roleId: adminRole.id,
|
roleId: adminRole.id,
|
||||||
|
|||||||
9
packages/twenty-shared/src/constants/SettingsFeatures.ts
Normal file
9
packages/twenty-shared/src/constants/SettingsFeatures.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
export enum SettingsFeatures {
|
||||||
|
API_KEYS_AND_WEBHOOKS = 'API_KEYS_AND_WEBHOOKS',
|
||||||
|
WORKSPACE_SETTINGS = 'WORKSPACE_SETTINGS',
|
||||||
|
WORKSPACE_USERS = 'WORKSPACE_USERS',
|
||||||
|
ROLES = 'WORKSPACE_ROLES',
|
||||||
|
DATA_MODEL = 'DATA_MODEL',
|
||||||
|
ADMIN_PANEL = 'ADMIN_PANEL',
|
||||||
|
SECURITY_SETTINGS = 'SECURITY_SETTINGS',
|
||||||
|
}
|
||||||
@ -1,5 +1,6 @@
|
|||||||
export * from './constants/FieldForTotalCountAggregateOperation';
|
export * from './constants/FieldForTotalCountAggregateOperation';
|
||||||
export * from './constants/Locales';
|
export * from './constants/Locales';
|
||||||
|
export * from './constants/SettingsFeatures';
|
||||||
export * from './constants/TwentyCompaniesBaseUrl';
|
export * from './constants/TwentyCompaniesBaseUrl';
|
||||||
export * from './constants/TwentyIconsBaseUrl';
|
export * from './constants/TwentyIconsBaseUrl';
|
||||||
export * from './types/ConnectedAccountProvider';
|
export * from './types/ConnectedAccountProvider';
|
||||||
|
|||||||
Reference in New Issue
Block a user