# Domain Entities — Unit 2: Authentication Pages ## User (from backend, extended in Unit 2) ### Type Definition ```typescript interface User { id: string; name: string; email: string; role: "Owner" | "Admin" | "User"; createdAt: string; // ISO timestamp } ``` ### Lifecycle in Unit 2 - **Created during setup**: `POST /Setup` creates first Owner - **Created during invitation**: `POST /Invitation/complete` creates User - **Returned by**: Login, Refresh, Setup, Invitation completion - **Stored in**: AuthContext (in-memory, cleared on logout) ### Unit 2 Attributes Used - `id` — for authorization checks and API calls - `name` — displayed in user menu (Unit 3+) - `email` — shown read-only in InviteCompletePage - `role` — used by RoleGuard to determine route access --- ## SetupStatus ### Type Definition ```typescript interface SetupStatus { initialized: boolean; createdAt?: string; // ISO timestamp, present only if initialized = true } ``` ### Lifecycle in Unit 2 - **Fetched by**: InitGuard on app mount - **Stored in**: React Query cache (`staleTime: Infinity`) - **Used by**: InitGuard to determine redirect logic - **Returned by**: `GET /Setup/status` ### States - `initialized: false` — system requires setup; redirect to `/setup` - `initialized: true` — system ready; allow normal flow --- ## InvitationToken ### Type Definition ```typescript interface InvitationToken { token: string; // from query param: ?token=xxx valid: boolean; email: string; // present only if valid = true error?: string; // present only if valid = false } ``` ### Lifecycle in Unit 2 - **Source**: URL query param (`/invite/complete?token=xxx`) - **Validated by**: `GET /Invitation/validate?token=xxx` on page mount - **Stored in**: Local component state (InviteCompletePage) - **Used for**: Form submission to `POST /Invitation/complete` ### States - Token extracted from URL - Validation in progress (loading state) - Validation succeeded (`valid: true`, show form) - Validation failed (`valid: false`, show error) - Submission in progress - Submission succeeded (redirect to login) - Submission failed (show banner error) --- ## PasswordCredential ### Type Definition ```typescript interface PasswordCredential { password: string; confirmPassword?: string; // present in setup/invite forms, absent in login } ``` ### Validation - Both fields validated by Zod schema (src/lib/schemas/auth.ts) - `password`: 8+ chars, 1 uppercase, 1 digit, 1 special char (backend-aligned) - `confirmPassword`: must equal `password` ### Usage - **SetupPage**: Both `password` and `confirmPassword` required - **InviteCompletePage**: Both `password` and `confirmPassword` required - **LoginPage**: Only `password` (no confirm, already implemented in Unit 1) --- ## FormError ### Type Definition ```typescript interface FormError { field?: string; // field name if field-specific message: string; code?: string; // backend error code } ``` ### Lifecycle - Extracted from backend response or validation failure - Displayed inline (per field) or in banner (form-level) - Cleared on field blur (inline) or form reset (banner) ### Examples - `{ field: "password", message: "At least 8 characters" }` — inline - `{ message: "Email already in use" }` — banner - `{ message: "Network error. Please try again." }` — banner --- ## RoleGuard Context ### Type Definition ```typescript interface RoleGuardContext { userRole: "Owner" | "Admin" | "User" | null; requiredRole: "Owner" | "Admin" | "User" | ("Owner" | "Admin")[]; hasAccess: boolean; accessDeniedMessage: string; } ``` ### Lifecycle - Evaluated on route load (via TanStack Router `beforeLoad`) - `userRole` read from AuthContext - `requiredRole` defined per route (hardcoded in route definition) - `hasAccess` calculated as: `userRole in requiredRole(s)` - `accessDeniedMessage` shown inline if `hasAccess = false` ### Example Routes ```typescript // /users — Owner or Admin { requiredRole: ["Owner", "Admin"], message: "Owner or Admin access required" } // /settings — Owner only { requiredRole: "Owner", message: "Owner access required" } // /cms — Owner only { requiredRole: "Owner", message: "Owner access required" } ``` --- ## AuthContext State (Unit 2 Extends Unit 1) ### Extended State ```typescript interface AuthContextState { // From Unit 1 user: User | null; accessToken: string | null; expiresAt: number | null; isAuthenticated: boolean; // New in Unit 2 userRole: "Owner" | "Admin" | "User" | null; } ``` ### Usage in Unit 2 - `useAuth()` returns the state + methods - `user.role` checked by RoleGuard for access control - `userRole` derived from `user.role` for convenience --- ## Language Preference ### Type Definition ```typescript interface LanguagePreference { code: "en" | "nl"; // ISO 639-1 label: string; // "English" | "Nederlands" } ``` ### Lifecycle - Collected in SetupPage form - Sent to backend in `POST /Setup` - Stored in user profile (backend) - Retrieved on login (future: Unit 3+, stored in AuthContext) - Used by i18next to set app locale ### Available Locales - **en**: English (fallback, eager load) - **nl**: Nederlands (lazy load) --- ## API Response Types ### Setup Response ```typescript interface SetupResponse { message: string; user: User; // newly created Owner } ``` ### Invitation Validation Response ```typescript interface InvitationValidationResponse { valid: boolean; email?: string; // present if valid error?: string; // present if invalid } ``` ### Invitation Completion Response ```typescript interface InvitationCompletionResponse { message: string; user: User; // newly created User } ``` ### Error Response (ProblemDetails) ```typescript interface ProblemDetails { type: string; // e.g., "https://example.com/errors/validation-error" title: string; // e.g., "Bad Request" status: number; // HTTP status detail: string; // user-facing message instance?: string; extensions?: Record; } ``` --- ## Relationships & Dependencies ### Entity Relationships ```mermaid graph LR User["User
(created by Setup
or Invitation)"] Auth["AuthContext
(stores User)"] Setup["SetupStatus
(session cache)"] Guard["InitGuard
(checks status)"] Token["InvitationToken
(email tied)"] Role["RoleGuard
(reads role)"] Cred["PasswordCredential
(Zod schema)"] User -->|stored in| Auth Auth -->|provides role to| Role Setup -->|controls| Guard Token -->|creates| User Cred -->|validates| User Role -->|controls access| Auth classDef entity fill:#4CAF50,stroke:#2E7D32,color:#fff,stroke-width:2px classDef context fill:#2196F3,stroke:#0D47A1,color:#fff,stroke-width:2px classDef guard fill:#FF9800,stroke:#E65100,color:#fff,stroke-width:2px classDef validation fill:#9C27B0,stroke:#4A148C,color:#fff,stroke-width:2px class User,Setup,Token entity class Auth,Role context class Guard guard class Cred validation ``` Text alternative: Entity relationship diagram showing User as central entity (green), created by Setup or Invitation, stored in AuthContext (blue). SetupStatus controls InitGuard (orange) for initialization checks. InvitationToken creates User and is tied to email. PasswordCredential (purple) validates User credentials. RoleGuard reads User.role from AuthContext to control route access. Arrows show dependencies and data flow between entities. ### Data Flow 1. App loads → InitGuard fetches SetupStatus 2. If not initialized → redirect to SetupPage 3. User completes setup → creates Owner User, redirects to LoginPage 4. OR: User clicks invitation link → InviteCompletePage validates token 5. User completes invitation → creates User, redirects to LoginPage 6. LoginPage → creates session via AuthContext.login() 7. AuthContext stores User (with role) in memory 8. RoleGuard reads User.role to control route access --- ## Constraint Notes - All entities align with backend schema (no frontend-only fields) - Timestamps are ISO 8601 strings (backend provides) - Role is enum (fixed set: Owner, Admin, User) - Email is unique key (backend enforces) - Passwords never stored in frontend state (only `accessToken` in memory)