feat(auth): prevent soft-deleted users from authenticating (GOO-15)
Some checks failed
CI / AI Services (Python) — Smoke (push) Failing after 26s
CodeQL Analysis / CodeQL (javascript-typescript) (push) Failing after 2m37s
Deploy / Build Web Image (push) Failing after 1m9s
Deploy / Build AI Services Image (push) Failing after 37s
E2E Tests / Playwright E2E (push) Failing after 56s
CI / Lint → Typecheck → Test → Build (22) (push) Failing after 11m58s
Deploy / Build API Image (push) Failing after 12m43s
Security Scanning / Dependency Audit (pnpm) (push) Failing after 9s
Security Scanning / Trivy Scan — API Image (push) Failing after 1m27s
Security Scanning / Trivy Scan — Web Image (push) Failing after 43s
Security Scanning / Trivy Scan — AI Services Image (push) Failing after 30s
Security Scanning / Trivy Filesystem Scan (push) Failing after 32s
Security Scanning / Security Gate (push) Failing after 1s
CI / E2E Tests (push) Has been cancelled
Deploy / Deploy to Staging (push) Has been cancelled
Deploy / Smoke Test Staging (push) Has been cancelled
Deploy / Rollback Staging (push) Has been cancelled
Deploy / Deploy to Production (push) Has been cancelled
Deploy / Smoke Test Production (push) Has been cancelled
Deploy / Rollback Production (push) Has been cancelled
Some checks failed
CI / AI Services (Python) — Smoke (push) Failing after 26s
CodeQL Analysis / CodeQL (javascript-typescript) (push) Failing after 2m37s
Deploy / Build Web Image (push) Failing after 1m9s
Deploy / Build AI Services Image (push) Failing after 37s
E2E Tests / Playwright E2E (push) Failing after 56s
CI / Lint → Typecheck → Test → Build (22) (push) Failing after 11m58s
Deploy / Build API Image (push) Failing after 12m43s
Security Scanning / Dependency Audit (pnpm) (push) Failing after 9s
Security Scanning / Trivy Scan — API Image (push) Failing after 1m27s
Security Scanning / Trivy Scan — Web Image (push) Failing after 43s
Security Scanning / Trivy Scan — AI Services Image (push) Failing after 30s
Security Scanning / Trivy Filesystem Scan (push) Failing after 32s
Security Scanning / Security Gate (push) Failing after 1s
CI / E2E Tests (push) Has been cancelled
Deploy / Deploy to Staging (push) Has been cancelled
Deploy / Smoke Test Staging (push) Has been cancelled
Deploy / Rollback Staging (push) Has been cancelled
Deploy / Deploy to Production (push) Has been cancelled
Deploy / Smoke Test Production (push) Has been cancelled
Deploy / Rollback Production (push) Has been cancelled
- Add deletedAt field to UserProps interface and UserEntity - Map raw.deletedAt in PrismaUserRepository.toDomain() - Check deletedAt !== null in LocalStrategy.validate() → 401 Tài khoản đã bị xóa - Update existing LocalStrategy tests with deletedAt: null on valid mocks - Add test: soft-deleted user login → 401 Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
@@ -17,6 +17,7 @@ export interface UserProps {
|
||||
kycStatus: KYCStatus;
|
||||
kycData: unknown;
|
||||
isActive: boolean;
|
||||
deletedAt: Date | null;
|
||||
totpSecret: string | null;
|
||||
totpEnabled: boolean;
|
||||
totpBackupCodes: string[];
|
||||
@@ -33,6 +34,7 @@ export class UserEntity extends AggregateRoot<string> {
|
||||
private _kycStatus: KYCStatus;
|
||||
private _kycData: unknown;
|
||||
private _isActive: boolean;
|
||||
private _deletedAt: Date | null;
|
||||
private _totpSecret: string | null;
|
||||
private _totpEnabled: boolean;
|
||||
private _totpBackupCodes: string[];
|
||||
@@ -49,6 +51,7 @@ export class UserEntity extends AggregateRoot<string> {
|
||||
this._kycStatus = props.kycStatus;
|
||||
this._kycData = props.kycData;
|
||||
this._isActive = props.isActive;
|
||||
this._deletedAt = props.deletedAt;
|
||||
this._totpSecret = props.totpSecret;
|
||||
this._totpEnabled = props.totpEnabled;
|
||||
this._totpBackupCodes = props.totpBackupCodes;
|
||||
@@ -64,6 +67,7 @@ export class UserEntity extends AggregateRoot<string> {
|
||||
get kycStatus(): KYCStatus { return this._kycStatus; }
|
||||
get kycData(): unknown { return this._kycData; }
|
||||
get isActive(): boolean { return this._isActive; }
|
||||
get deletedAt(): Date | null { return this._deletedAt; }
|
||||
get totpSecret(): string | null { return this._totpSecret; }
|
||||
get totpEnabled(): boolean { return this._totpEnabled; }
|
||||
get totpBackupCodes(): string[] { return this._totpBackupCodes; }
|
||||
@@ -87,6 +91,44 @@ export class UserEntity extends AggregateRoot<string> {
|
||||
kycStatus: 'NONE',
|
||||
kycData: null,
|
||||
isActive: true,
|
||||
deletedAt: null,
|
||||
totpSecret: null,
|
||||
totpEnabled: false,
|
||||
totpBackupCodes: [],
|
||||
totpEnabledAt: null,
|
||||
});
|
||||
|
||||
user.addDomainEvent(new UserRegisteredEvent(id, phone.value, role));
|
||||
return user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a passwordless user (e.g. via Phone-OTP login auto-register).
|
||||
* `passwordHash` is null so password login is not possible until the user
|
||||
* sets one via the password-reset / profile flow. A fullName fallback is
|
||||
* used since OTP signup does not collect a name.
|
||||
*/
|
||||
static createPasswordless(
|
||||
id: string,
|
||||
phone: Phone,
|
||||
fullName?: string,
|
||||
role: UserRole = 'BUYER',
|
||||
): UserEntity {
|
||||
const displayName =
|
||||
fullName && fullName.trim().length > 0
|
||||
? fullName
|
||||
: `Người dùng ${phone.value.slice(-4)}`;
|
||||
const user = new UserEntity(id, {
|
||||
email: null,
|
||||
phone,
|
||||
passwordHash: null,
|
||||
fullName: displayName,
|
||||
avatarUrl: null,
|
||||
role,
|
||||
kycStatus: 'NONE',
|
||||
kycData: null,
|
||||
isActive: true,
|
||||
deletedAt: null,
|
||||
totpSecret: null,
|
||||
totpEnabled: false,
|
||||
totpBackupCodes: [],
|
||||
|
||||
Reference in New Issue
Block a user