feat(auth): implement Auth module with register, login, JWT, guards, and CQRS
- Add RefreshToken and OAuthAccount models to Prisma schema - Implement clean architecture: domain (entities, VOs, events, repo interfaces), infrastructure (Prisma repos, Passport strategies, token service), application (CQRS command/query handlers), presentation (controller, guards, DTOs) - Endpoints: POST /auth/register, /auth/login, /auth/refresh, GET /auth/profile, GET /auth/profile/agent, PATCH /auth/kyc - JWT access + refresh token rotation with family-based revocation - Role-based guards (BUYER, SELLER, AGENT, ADMIN) - 16 unit tests (value objects, entity) + integration test suite - All 80 tests passing, clean TypeScript build Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1
apps/api/src/modules/auth/domain/entities/index.ts
Normal file
1
apps/api/src/modules/auth/domain/entities/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { UserEntity, type UserProps } from './user.entity';
|
||||
88
apps/api/src/modules/auth/domain/entities/user.entity.ts
Normal file
88
apps/api/src/modules/auth/domain/entities/user.entity.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
import { AggregateRoot } from '@modules/shared/domain/aggregate-root';
|
||||
import { type UserRole, type KYCStatus } from '@prisma/client';
|
||||
import { UserRegisteredEvent } from '../events/user-registered.event';
|
||||
import { type Email } from '../value-objects/email.vo';
|
||||
import { type Phone } from '../value-objects/phone.vo';
|
||||
import { type HashedPassword } from '../value-objects/hashed-password.vo';
|
||||
|
||||
export interface UserProps {
|
||||
email: Email | null;
|
||||
phone: Phone;
|
||||
passwordHash: HashedPassword | null;
|
||||
fullName: string;
|
||||
avatarUrl: string | null;
|
||||
role: UserRole;
|
||||
kycStatus: KYCStatus;
|
||||
kycData: unknown;
|
||||
isActive: boolean;
|
||||
}
|
||||
|
||||
export class UserEntity extends AggregateRoot<string> {
|
||||
private _email: Email | null;
|
||||
private _phone: Phone;
|
||||
private _passwordHash: HashedPassword | null;
|
||||
private _fullName: string;
|
||||
private _avatarUrl: string | null;
|
||||
private _role: UserRole;
|
||||
private _kycStatus: KYCStatus;
|
||||
private _kycData: unknown;
|
||||
private _isActive: boolean;
|
||||
|
||||
constructor(id: string, props: UserProps, createdAt?: Date, updatedAt?: Date) {
|
||||
super(id, createdAt, updatedAt);
|
||||
this._email = props.email;
|
||||
this._phone = props.phone;
|
||||
this._passwordHash = props.passwordHash;
|
||||
this._fullName = props.fullName;
|
||||
this._avatarUrl = props.avatarUrl;
|
||||
this._role = props.role;
|
||||
this._kycStatus = props.kycStatus;
|
||||
this._kycData = props.kycData;
|
||||
this._isActive = props.isActive;
|
||||
}
|
||||
|
||||
get email(): Email | null { return this._email; }
|
||||
get phone(): Phone { return this._phone; }
|
||||
get passwordHash(): HashedPassword | null { return this._passwordHash; }
|
||||
get fullName(): string { return this._fullName; }
|
||||
get avatarUrl(): string | null { return this._avatarUrl; }
|
||||
get role(): UserRole { return this._role; }
|
||||
get kycStatus(): KYCStatus { return this._kycStatus; }
|
||||
get kycData(): unknown { return this._kycData; }
|
||||
get isActive(): boolean { return this._isActive; }
|
||||
|
||||
static createNew(
|
||||
id: string,
|
||||
phone: Phone,
|
||||
fullName: string,
|
||||
passwordHash: HashedPassword,
|
||||
email?: Email,
|
||||
role: UserRole = 'BUYER',
|
||||
): UserEntity {
|
||||
const user = new UserEntity(id, {
|
||||
email: email ?? null,
|
||||
phone,
|
||||
passwordHash,
|
||||
fullName,
|
||||
avatarUrl: null,
|
||||
role,
|
||||
kycStatus: 'NONE',
|
||||
kycData: null,
|
||||
isActive: true,
|
||||
});
|
||||
|
||||
user.addDomainEvent(new UserRegisteredEvent(id, phone.value, role));
|
||||
return user;
|
||||
}
|
||||
|
||||
updateKycStatus(status: KYCStatus, kycData?: unknown): void {
|
||||
this._kycStatus = status;
|
||||
if (kycData !== undefined) this._kycData = kycData;
|
||||
this.updatedAt = new Date();
|
||||
}
|
||||
|
||||
deactivate(): void {
|
||||
this._isActive = false;
|
||||
this.updatedAt = new Date();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user