feat(server): handle account deleting properly (#12399)

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Users are now prevented from deleting their account if they own one or more team workspaces. A clear error message instructs users to transfer ownership or delete those workspaces first.
  - Disabled (banned) users are explicitly prevented from signing in or re-registering.
  - Added new error messages and translations to improve clarity around account deletion restrictions.

- **Bug Fixes**
  - Disabled users are now explicitly handled to prevent sign-in attempts.

- **Tests**
  - Introduced comprehensive end-to-end tests covering account deletion, banning, and re-registration scenarios.

- **Chores**
  - Improved event handling for user deletion and subscription cancellation.
  - Updated localization resources with new error messages.
  - Renamed payment event handler class for clarity.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
forehalo
2025-05-23 03:57:29 +00:00
parent f99b143bf9
commit f38b8fef4d
15 changed files with 235 additions and 23 deletions
+23 -5
View File
@@ -4,9 +4,11 @@ import { type ConnectedAccount, Prisma, type User } from '@prisma/client';
import { omit } from 'lodash-es';
import {
CannotDeleteAccountWithOwnedTeamWorkspace,
CryptoHelper,
EmailAlreadyUsed,
EventBus,
UserNotFound,
WrongSignInCredentials,
WrongSignInMethod,
} from '../base';
@@ -217,6 +219,10 @@ export class UserModel extends BaseModel {
...data,
});
} else {
if (user.disabled) {
throw new UserNotFound();
}
if (user.registered) {
delete data.registered;
} else {
@@ -237,13 +243,25 @@ export class UserModel extends BaseModel {
return user;
}
async ownedWorkspaces(id: string) {
return await this.models.workspaceUser.getUserActiveRoles(id, {
role: WorkspaceRole.Owner,
});
}
async delete(id: string) {
const ownedWorkspaces = await this.models.workspaceUser.getUserActiveRoles(
id,
{
role: WorkspaceRole.Owner,
const ownedWorkspaces = await this.ownedWorkspaces(id);
for (const ws of ownedWorkspaces) {
const isTeamWorkspace = await this.models.workspace.isTeamWorkspace(
ws.workspaceId
);
if (isTeamWorkspace) {
throw new CannotDeleteAccountWithOwnedTeamWorkspace();
}
);
}
const user = await this.db.user.delete({ where: { id } });
this.event.emit('user.deleted', {