# Auth Schema Improvements - Explanation ## Problems Identified and Fixed ### 1. **`z.unknown()` Type Safety Issue** ❌ → ✅ **Problem:** ```typescript // BEFORE - NO TYPE SAFETY export const signupResultSchema = z.object({ user: z.unknown(), // ❌ Loses all type information and validation tokens: authTokensSchema, }); ``` **Why it was wrong:** - `z.unknown()` provides **zero validation** at runtime - TypeScript can't infer proper types from it - Defeats the purpose of schema-first architecture - Any object could pass validation, even malformed data **Solution:** ```typescript // AFTER - FULL TYPE SAFETY export const userProfileSchema = z.object({ id: z.string().uuid(), email: z.string().email(), role: z.enum(["USER", "ADMIN"]), // ... all fields properly typed }); export const signupResultSchema = z.object({ user: userProfileSchema, // ✅ Full validation and type inference tokens: authTokensSchema, }); ``` **Benefits:** - Runtime validation of user data structure - Proper TypeScript type inference - Catches invalid data early - Self-documenting API contract --- ### 2. **Email Validation Issues** ❌ → ✅ **Problem:** The `z.string().email()` in `checkPasswordNeededResponseSchema` was correct, but using `z.unknown()` elsewhere meant emails weren't being validated in user objects. **Solution:** ```typescript export const userProfileSchema = z.object({ email: z.string().email(), // ✅ Validates email format // ... }); ``` Now all user objects have their emails properly validated. --- ### 3. **UserProfile Alias Confusion** 🤔 → ✅ **Problem:** ```typescript export type UserProfile = AuthenticatedUser; ``` This created confusion about why we have two names for the same thing. **Solution - Added Clear Documentation:** ```typescript /** * UserProfile type alias * * Note: This is an alias for backward compatibility. * Both types represent the same thing: a complete user profile with auth state. * * Architecture: * - PortalUser: Only auth state from portal DB (id, email, role, emailVerified, etc.) * - CustomerProfile: Profile data from WHMCS (firstname, lastname, address, etc.) * - AuthenticatedUser/UserProfile: CustomerProfile + auth state = complete user */ export type UserProfile = AuthenticatedUser; ``` **Why the alias exists:** - **Backward compatibility**: Code may use either name - **Domain language**: "UserProfile" is more business-friendly than "AuthenticatedUser" - **Convention**: Many authentication libraries use "UserProfile" --- ## Architecture Explanation ### Three-Layer User Model ``` ┌─────────────────────────────────────────────┐ │ AuthenticatedUser / UserProfile │ │ (Complete User) │ │ ┌────────────────────────────────────────┐ │ │ │ CustomerProfile (WHMCS) │ │ │ │ - firstname, lastname │ │ │ │ - address, phone │ │ │ │ - language, currency │ │ │ └────────────────────────────────────────┘ │ │ ┌────────────────────────────────────────┐ │ │ │ PortalUser (Portal DB) │ │ │ │ - id, email │ │ │ │ - role, emailVerified, mfaEnabled │ │ │ │ - lastLoginAt │ │ │ └────────────────────────────────────────┘ │ └─────────────────────────────────────────────┘ ``` ### 1. **PortalUser** (Portal Database) - **Purpose**: Authentication state only - **Source**: Portal's Prisma database - **Fields**: id, email, role, emailVerified, mfaEnabled, lastLoginAt - **Schema**: `portalUserSchema` - **Use Cases**: JWT validation, token refresh, auth checks ### 2. **CustomerProfile** (WHMCS) - **Purpose**: Business profile data - **Source**: WHMCS API (single source of truth for profile data) - **Fields**: firstname, lastname, address, phone, language, currency - **Schema**: Interface only (external system) - **Use Cases**: Displaying user info, profile updates ### 3. **AuthenticatedUser / UserProfile** (Combined) - **Purpose**: Complete user representation - **Source**: Portal DB + WHMCS (merged) - **Fields**: All fields from PortalUser + CustomerProfile - **Schema**: `userProfileSchema` (for validation) - **Use Cases**: API responses, full user context --- ## Why This Matters ### Before (with `z.unknown()`): ```typescript // Could pass completely invalid data ❌ const badData = { user: { totally: "wrong", structure: true }, tokens: { /* ... */ }, }; // Would validate successfully! ❌ ``` ### After (with proper schema): ```typescript // Validates structure correctly ✅ const goodData = { user: { id: "uuid-here", email: "valid@email.com", role: "USER", // ... all required fields }, tokens: { /* ... */ }, }; // Validates ✅ const badData = { user: { wrong: "structure" }, tokens: { /* ... */ }, }; // Throws validation error ✅ ``` --- ## Implementation Details ### Schema Definition Location The `userProfileSchema` is defined **before** it's used: ```typescript // 1. First, define the schema export const userProfileSchema = z.object({ // All fields defined }); // 2. Then use it in response schemas export const authResponseSchema = z.object({ user: userProfileSchema, // ✅ Now defined tokens: authTokensSchema, }); ``` ### Type Inference TypeScript now properly infers types: ```typescript // Type is inferred from schema type InferredUser = z.infer; // Results in proper type: // { // id: string; // email: string; // role: "USER" | "ADMIN"; // emailVerified: boolean; // // ... etc // } ``` --- ## Migration Impact ### ⚠️ Potential Breaking Change (Security Improvement) Auth endpoints now return **session metadata** instead of **token strings**: - **Before**: `{ user, tokens: { accessToken, refreshToken, expiresAt, refreshExpiresAt } }` - **After**: `{ user, session: { expiresAt, refreshExpiresAt } }` Token strings are delivered via **httpOnly cookies** only. ### ✅ Improved Type Safety - Better autocomplete in IDEs - Compile-time error checking - Runtime validation of API responses ### ✅ Better Documentation - Schema serves as living documentation - Clear separation of concerns - Self-describing data structures --- ## Summary | Issue | Before | After | | ------------------- | ---------------- | ---------------------- | | User validation | `z.unknown()` ❌ | `userProfileSchema` ✅ | | Type safety | None | Full | | Runtime validation | None | Complete | | Documentation | Unclear | Self-documenting | | Email validation | Partial | Complete | | UserProfile clarity | Confusing | Documented | **Result**: Proper schema-first architecture with full type safety and validation! 🎉