/** * Auth Response Mapper * * Transforms auth API responses to User domain entities. * Maps from API schemas to dashboard domain objects. * * @layer Infrastructure - API Client */ import { userResponseSchema, tokenResponseSchema, } from '@archer/api-interface'; import { User, UserRole, UserStatus } from '../../../../../domain/entities/User'; import type { AuthSession } from '../../shared/types'; /** * AuthResponseMapper * * Maps authentication responses to User domain objects and auth sessions. * Handles token responses, user responses, and auth-only responses. */ export class AuthResponseMapper { /** * Maps UserResponse (from registration or /auth/me) to User domain entity * * @param response - UserResponse from API (auth + profile) * @returns User domain entity * @throws ZodError if response validation fails */ static toUser(response: unknown): User { const validated = userResponseSchema.parse(response); // Extract name from profile or use email as fallback const name = validated.profile?.firstName && validated.profile?.lastName ? `${validated.profile.firstName} ${validated.profile.lastName}`.trim() : validated.profile?.firstName || validated.profile?.lastName || validated.email; // Map status from API to domain UserStatus // API returns: ENABLED, DISABLED, PENDING_VERIFICATION, etc. // Domain expects: active, inactive, pending const statusMap: Record = { 'ENABLED': UserStatus.ACTIVE, 'DISABLED': UserStatus.INACTIVE, 'PENDING_VERIFICATION': UserStatus.PENDING, 'LOCKED': UserStatus.INACTIVE, }; const status = statusMap[validated.status] || UserStatus.PENDING; // For now, we don't have role in the auth response, so default to viewer // This will be properly set when we have the full user context const role = UserRole.VIEWER; // For now, we don't have companyId in the auth response // This will be set when we have the full company context const companyId = 'unknown'; return User.fromPersistence({ id: validated.id, email: validated.email, name, companyId, role, status, avatar: validated.profile?.avatarUrl || null, createdAt: new Date().toISOString(), // Will be overwritten with actual data updatedAt: new Date().toISOString(), }); } /** * Maps TokenResponse (from login/refresh) to User domain entity * * @param response - TokenResponse from API * @returns User domain entity extracted from token response * @throws ZodError if response validation fails */ static toUserFromToken(response: unknown): User { const validated = tokenResponseSchema.parse(response); if (!validated.user) { throw new Error('Token response does not contain user data'); } const tokenUser = validated.user; // Map role from company role const roleMap: Record = { 'admin': UserRole.ADMIN, 'manager': UserRole.MANAGER, 'worker': UserRole.DEVELOPER, }; const role = tokenUser.currentCompanyRole ? roleMap[tokenUser.currentCompanyRole] || UserRole.VIEWER : UserRole.VIEWER; // Map status - if email verified and 2FA passed, user is active const status = tokenUser.emailVerified ? UserStatus.ACTIVE : UserStatus.PENDING; return User.fromPersistence({ id: tokenUser.id, email: tokenUser.email, name: tokenUser.fullName, companyId: tokenUser.currentCompanyId || 'unknown', role, status, avatar: tokenUser.avatarUrl, createdAt: tokenUser.createdAt, updatedAt: tokenUser.updatedAt, }); } /** * Maps TokenResponse to AuthSession with User * * Combines token data and user data into complete auth session. * * @param response - TokenResponse from login or refresh * @returns Object with user and auth session tokens * @throws ZodError if response validation fails * @throws Error if response doesn't contain required tokens or user */ static toAuthSession(response: unknown): { user: User; session: AuthSession } { const validated = tokenResponseSchema.parse(response); // Check for 2FA required response if (validated.requires2FA) { throw new Error('2FA verification required. Use tempToken to complete authentication.'); } if (!validated.accessToken || !validated.refreshToken) { throw new Error('Token response missing access or refresh token'); } if (!validated.user) { throw new Error('Token response missing user data'); } const user = this.toUserFromToken(response); const session: AuthSession = { accessToken: validated.accessToken, refreshToken: validated.refreshToken, expiresIn: 3600, // 1 hour default (should come from API in future) tokenType: 'Bearer', }; return { user, session }; } /** * Checks if TokenResponse requires 2FA verification * * @param response - TokenResponse from login * @returns true if 2FA verification is required */ static requires2FA(response: unknown): boolean { const validated = tokenResponseSchema.parse(response); return validated.requires2FA === true; } /** * Extracts temporary 2FA token from response * * @param response - TokenResponse with requires2FA flag * @returns Temporary token for 2FA verification * @throws Error if response doesn't contain temp token */ static getTempToken(response: unknown): string { const validated = tokenResponseSchema.parse(response); if (!validated.tempToken) { throw new Error('Response does not contain temporary 2FA token'); } return validated.tempToken; } }