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
@@ -27,6 +27,7 @@ import {
Throttle,
URLHelper,
UseNamedGuard,
WrongSignInCredentials,
} from '../../base';
import { Models, TokenType } from '../../models';
import { validators } from '../utils/validators';
@@ -162,7 +163,10 @@ export class AuthController {
clientNonce?: string
) {
// send email magic link
const user = await this.models.user.getUserByEmail(email);
const user = await this.models.user.getUserByEmail(email, {
withDisabled: true,
});
if (!user) {
if (!this.config.auth.allowSignup) {
throw new SignUpForbidden();
@@ -191,6 +195,8 @@ export class AuthController {
throw new InvalidEmail({ email });
}
}
} else if (user.disabled) {
throw new WrongSignInCredentials({ email });
}
const ttlInSec = 30 * 60;
@@ -41,6 +41,7 @@ export class DocEventsListener {
@OnEvent('user.deleted')
async clearUserWorkspaces(payload: Events['user.deleted']) {
for (const workspace of payload.ownedWorkspaces) {
await this.models.workspace.delete(workspace);
await this.workspace.deleteSpace(workspace);
}
}