Fix user deletion flows (#15614)
**Before** - any user with workpace_members permission was able to remove a user from their workspace. This triggered the deletion of workspaceMember + of userWorkspace, but did not delete the user (even if they had no workspace left) nor the roleTarget (acts as junction between role and userWorkspace) which was left with a userWorkspaceId pointing to nothing. This is because roleTarget points to userWorkspaceId but the foreign key constraint was not implemented - any user could delete their own account. This triggered the deletion of all their workspaceMembers, but not of their userWorkspace nor their user nor the roleTarget --> we have orphaned userWorkspace, not technically but product wise - a userWorkspace without a workspaceMember does not make sense So the problems are - we have some roleTargets pointing to non-existing userWorkspaceId (which caused https://github.com/twentyhq/twenty/issues/14608 ) - we have userWorkspaces that should not exist and that have no workspaceMember counterpart - it is not possible for a user to leave a workspace by themselves, they can only leave all workspaces at once, except if they are being removed from the workspace by another user **Now** - if a user has multiple workspaces, they are given the possibility to leave one workspace while remaining in the others (we show two buttons: Leave workspace and Delete account buttons). if a user has just one workspace, they only see Delete account - when a user leaves a workspace, we delete their workspaceMember, userWorkspace and roleTarget. If they don't belong to any other workspace we also soft-delete their user - soft-deleted users get hard deleted after 30 days thanks to a cron - we have two commands to clean the orphans roleTarget and userWorkspace (TODO: query db to see how many must be run) **Next** - once the commands have been run, we can implement and introduce the foreign key constraint on roleTarget Fixes https://github.com/twentyhq/twenty/issues/14608
This commit is contained in:
+8
-16
@@ -1,16 +1,12 @@
|
||||
import { assertIsDefinedOrThrow } from 'twenty-shared/utils';
|
||||
|
||||
import { type WorkspacePreQueryHookInstance } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/interfaces/workspace-query-hook.interface';
|
||||
import { type DeleteManyResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
||||
|
||||
import {
|
||||
CommonQueryRunnerException,
|
||||
CommonQueryRunnerExceptionCode,
|
||||
} from 'src/engine/api/common/common-query-runners/errors/common-query-runner.exception';
|
||||
import { WorkspaceQueryHook } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/decorators/workspace-query-hook.decorator';
|
||||
import { type AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||
import {
|
||||
PermissionsException,
|
||||
PermissionsExceptionCode,
|
||||
PermissionsExceptionMessage,
|
||||
} from 'src/engine/metadata-modules/permissions/permissions.exception';
|
||||
import { WorkspaceNotFoundDefaultError } from 'src/engine/core-modules/workspace/workspace.exception';
|
||||
|
||||
@WorkspaceQueryHook(`workspaceMember.deleteMany`)
|
||||
export class WorkspaceMemberDeleteManyPreQueryHook
|
||||
@@ -18,14 +14,10 @@ export class WorkspaceMemberDeleteManyPreQueryHook
|
||||
{
|
||||
constructor() {}
|
||||
|
||||
async execute(authContext: AuthContext): Promise<DeleteManyResolverArgs> {
|
||||
const workspace = authContext.workspace;
|
||||
|
||||
assertIsDefinedOrThrow(workspace, WorkspaceNotFoundDefaultError);
|
||||
|
||||
throw new PermissionsException(
|
||||
PermissionsExceptionMessage.PERMISSION_DENIED,
|
||||
PermissionsExceptionCode.PERMISSION_DENIED,
|
||||
async execute(_authContext: AuthContext): Promise<DeleteManyResolverArgs> {
|
||||
throw new CommonQueryRunnerException(
|
||||
'Please use /deleteUserFromWorkspace to remove a workspace member.',
|
||||
CommonQueryRunnerExceptionCode.BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+5
-1
@@ -9,6 +9,7 @@ import { WorkspaceQueryHook } from 'src/engine/api/graphql/workspace-query-runne
|
||||
import { WorkspaceQueryHookType } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/types/workspace-query-hook.type';
|
||||
import { type AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||
import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
|
||||
import { UserWorkspaceService } from 'src/engine/core-modules/user-workspace/user-workspace.service';
|
||||
import { WorkspaceNotFoundDefaultError } from 'src/engine/core-modules/workspace/workspace.exception';
|
||||
import {
|
||||
PermissionsException,
|
||||
@@ -30,6 +31,7 @@ export class WorkspaceMemberDeleteOnePostQueryHook
|
||||
@InjectRepository(UserWorkspaceEntity)
|
||||
private readonly userWorkspaceRepository: Repository<UserWorkspaceEntity>,
|
||||
private readonly workspaceMemberPreQueryHookService: WorkspaceMemberPreQueryHookService,
|
||||
private readonly userWorkspaceService: UserWorkspaceService,
|
||||
) {}
|
||||
|
||||
async execute(
|
||||
@@ -91,6 +93,8 @@ export class WorkspaceMemberDeleteOnePostQueryHook
|
||||
);
|
||||
}
|
||||
|
||||
await this.userWorkspaceRepository.delete(userWorkspace.id);
|
||||
await this.userWorkspaceService.deleteUserWorkspace({
|
||||
userWorkspaceId: userWorkspace.id,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
import { type WorkspacePreQueryHookInstance } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/interfaces/workspace-query-hook.interface';
|
||||
import { type RestoreOneResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
||||
|
||||
import {
|
||||
CommonQueryRunnerException,
|
||||
CommonQueryRunnerExceptionCode,
|
||||
} from 'src/engine/api/common/common-query-runners/errors/common-query-runner.exception';
|
||||
import { WorkspaceQueryHook } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/decorators/workspace-query-hook.decorator';
|
||||
import { type AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||
|
||||
@WorkspaceQueryHook(`workspaceMember.deleteOne`)
|
||||
export class WorkspaceMemberDeleteOnePreQueryHook
|
||||
implements WorkspacePreQueryHookInstance
|
||||
{
|
||||
constructor() {}
|
||||
|
||||
async execute(_authContext: AuthContext): Promise<RestoreOneResolverArgs> {
|
||||
throw new CommonQueryRunnerException(
|
||||
'Please use /deleteUserFromWorkspace to remove a workspace member.',
|
||||
CommonQueryRunnerExceptionCode.BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
}
|
||||
+4
@@ -3,11 +3,13 @@ import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
||||
import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
|
||||
import { UserWorkspaceModule } from 'src/engine/core-modules/user-workspace/user-workspace.module';
|
||||
import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module';
|
||||
import { WorkspaceMemberCreateManyPreQueryHook } from 'src/modules/workspace-member/query-hooks/workspace-member-create-many.pre-query.hook';
|
||||
import { WorkspaceMemberCreateOnePreQueryHook } from 'src/modules/workspace-member/query-hooks/workspace-member-create-one.pre-query.hook';
|
||||
import { WorkspaceMemberDeleteManyPreQueryHook } from 'src/modules/workspace-member/query-hooks/workspace-member-delete-many.pre-query.hook';
|
||||
import { WorkspaceMemberDeleteOnePostQueryHook } from 'src/modules/workspace-member/query-hooks/workspace-member-delete-one.post-query.hook';
|
||||
import { WorkspaceMemberDeleteOnePreQueryHook } from 'src/modules/workspace-member/query-hooks/workspace-member-delete-one.pre-query.hook';
|
||||
import { WorkspaceMemberDestroyManyPreQueryHook } from 'src/modules/workspace-member/query-hooks/workspace-member-destroy-many.pre-query.hook';
|
||||
import { WorkspaceMemberDestroyOnePreQueryHook } from 'src/modules/workspace-member/query-hooks/workspace-member-destroy-one.pre-query.hook';
|
||||
import { WorkspaceMemberPreQueryHookService } from 'src/modules/workspace-member/query-hooks/workspace-member-pre-query-hook.service';
|
||||
@@ -21,6 +23,7 @@ import { WorkspaceMemberUpdateOnePreQueryHook } from 'src/modules/workspace-memb
|
||||
WorkspaceMemberPreQueryHookService,
|
||||
WorkspaceMemberCreateOnePreQueryHook,
|
||||
WorkspaceMemberCreateManyPreQueryHook,
|
||||
WorkspaceMemberDeleteOnePreQueryHook,
|
||||
WorkspaceMemberDeleteOnePostQueryHook,
|
||||
WorkspaceMemberDeleteManyPreQueryHook,
|
||||
WorkspaceMemberDestroyOnePreQueryHook,
|
||||
@@ -33,6 +36,7 @@ import { WorkspaceMemberUpdateOnePreQueryHook } from 'src/modules/workspace-memb
|
||||
imports: [
|
||||
FeatureFlagModule,
|
||||
PermissionsModule,
|
||||
UserWorkspaceModule,
|
||||
TypeOrmModule.forFeature([UserWorkspaceEntity]),
|
||||
],
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user