[permissions - seeds] Give tim@apple.dev restricted rights (#12768)

Let's introduce an object-limited role for Tim, to test and/or spot
incompatibilities with restricted permissions in the future.
Our main user tim@apple.dev is now assigned a role that has all settings
permissions, and all object permissions except for update on Pets (to
test read-only view) and read on Rockets.
Since we still need an admin user for each workspace we are introducing
a new member, Jane, who has the admin role

---------

Co-authored-by: Félix Malfait <felix@twenty.com>
This commit is contained in:
Marie
2025-06-23 15:46:53 +02:00
committed by GitHub
parent 8f0c9facf2
commit 2cb2f528df
12 changed files with 246 additions and 108 deletions

View File

@ -92,71 +92,6 @@ export class UserService extends TypeOrmQueryService<User> {
});
}
private async deleteUserFromWorkspace({
userId,
workspaceId,
}: {
userId: string;
workspaceId: string;
}) {
const workspaceMemberRepository =
await this.twentyORMGlobalManager.getRepositoryForWorkspace<WorkspaceMemberWorkspaceEntity>(
workspaceId,
'workspaceMember',
);
const workspaceMembers = await workspaceMemberRepository.find();
if (workspaceMembers.length > 1) {
const userWorkspace =
await this.userWorkspaceService.getUserWorkspaceForUserOrThrow({
userId,
workspaceId,
});
await this.userRoleService.validateUserWorkspaceIsNotUniqueAdminOrThrow({
workspaceId,
userWorkspaceId: userWorkspace.id,
});
}
const workspaceMember = workspaceMembers.filter(
(member: WorkspaceMemberWorkspaceEntity) => member.userId === userId,
)?.[0];
assert(workspaceMember, 'WorkspaceMember not found');
await workspaceMemberRepository.delete({ userId });
const objectMetadata = await this.objectMetadataRepository.findOneOrFail({
where: {
nameSingular: 'workspaceMember',
workspaceId,
},
});
if (workspaceMembers.length === 1) {
await this.workspaceService.deleteWorkspace(workspaceId);
return;
}
this.workspaceEventEmitter.emitDatabaseBatchEvent({
objectMetadataNameSingular: 'workspaceMember',
action: DatabaseEventAction.DELETED,
events: [
{
recordId: workspaceMember.id,
objectMetadata,
properties: {
before: workspaceMember,
},
},
],
workspaceId,
});
}
async deleteUser(userId: string): Promise<User> {
const user = await this.userRepository.findOne({
where: {
@ -167,29 +102,97 @@ export class UserService extends TypeOrmQueryService<User> {
userValidator.assertIsDefinedOrThrow(user);
await Promise.all(
const prepareForUserDeletionInWorkspaces = await Promise.all(
user.workspaces.map(async (userWorkspace) => {
try {
await this.deleteUserFromWorkspace({
userId,
workspaceId: userWorkspace.workspaceId,
});
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (error: any) {
if (
error instanceof PermissionsException &&
error.code === PermissionsExceptionCode.CANNOT_UNASSIGN_LAST_ADMIN
) {
throw new PermissionsException(
PermissionsExceptionMessage.CANNOT_DELETE_LAST_ADMIN_USER,
PermissionsExceptionCode.CANNOT_DELETE_LAST_ADMIN_USER,
const { workspaceId } = userWorkspace;
const workspaceMemberRepository =
await this.twentyORMGlobalManager.getRepositoryForWorkspace<WorkspaceMemberWorkspaceEntity>(
workspaceId,
'workspaceMember',
);
const workspaceMembers = await workspaceMemberRepository.find();
if (workspaceMembers.length > 1) {
try {
await this.userRoleService.validateUserWorkspaceIsNotUniqueAdminOrThrow(
{
workspaceId,
userWorkspaceId: userWorkspace.id,
},
);
} catch (error) {
if (
error instanceof PermissionsException &&
error.code === PermissionsExceptionCode.CANNOT_UNASSIGN_LAST_ADMIN
) {
throw new PermissionsException(
PermissionsExceptionMessage.CANNOT_DELETE_LAST_ADMIN_USER,
PermissionsExceptionCode.CANNOT_DELETE_LAST_ADMIN_USER,
);
}
throw error;
}
throw error;
}
const workspaceMember = workspaceMembers.find(
(member: WorkspaceMemberWorkspaceEntity) => member.userId === userId,
);
assert(workspaceMember, 'WorkspaceMember not found');
return {
workspaceId,
workspaceMemberRepository,
workspaceMembers,
workspaceMember,
};
}),
);
await Promise.all(
prepareForUserDeletionInWorkspaces.map(
async ({
workspaceId,
workspaceMemberRepository,
workspaceMembers,
workspaceMember,
}) => {
await workspaceMemberRepository.delete({ userId });
const objectMetadata =
await this.objectMetadataRepository.findOneOrFail({
where: {
nameSingular: 'workspaceMember',
workspaceId,
},
});
if (workspaceMembers.length === 1) {
await this.workspaceService.deleteWorkspace(workspaceId);
return;
}
this.workspaceEventEmitter.emitDatabaseBatchEvent({
objectMetadataNameSingular: 'workspaceMember',
action: DatabaseEventAction.DELETED,
events: [
{
recordId: workspaceMember.id,
objectMetadata,
properties: {
before: workspaceMember,
},
},
],
workspaceId,
});
},
),
);
return user;
}