8.1 KiB
8.1 KiB
Domain Entities — Unit 2: Authentication Pages
User (from backend, extended in Unit 2)
Type Definition
interface User {
id: string;
name: string;
email: string;
role: "Owner" | "Admin" | "User";
createdAt: string; // ISO timestamp
}
Lifecycle in Unit 2
- Created during setup:
POST /Setupcreates first Owner - Created during invitation:
POST /Invitation/completecreates 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 callsname— displayed in user menu (Unit 3+)email— shown read-only in InviteCompletePagerole— used by RoleGuard to determine route access
SetupStatus
Type Definition
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/setupinitialized: true— system ready; allow normal flow
InvitationToken
Type Definition
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=xxxon 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
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 equalpassword
Usage
- SetupPage: Both
passwordandconfirmPasswordrequired - InviteCompletePage: Both
passwordandconfirmPasswordrequired - LoginPage: Only
password(no confirm, already implemented in Unit 1)
FormError
Type Definition
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
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) userRoleread from AuthContextrequiredRoledefined per route (hardcoded in route definition)hasAccesscalculated as:userRole in requiredRole(s)accessDeniedMessageshown inline ifhasAccess = false
Example Routes
// /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
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 + methodsuser.rolechecked by RoleGuard for access controluserRolederived fromuser.rolefor convenience
Language Preference
Type Definition
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
interface SetupResponse {
message: string;
user: User; // newly created Owner
}
Invitation Validation Response
interface InvitationValidationResponse {
valid: boolean;
email?: string; // present if valid
error?: string; // present if invalid
}
Invitation Completion Response
interface InvitationCompletionResponse {
message: string;
user: User; // newly created User
}
Error Response (ProblemDetails)
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<string, unknown>;
}
Relationships & Dependencies
Entity Relationships
graph LR
User["User<br/>(created by Setup<br/>or Invitation)"]
Auth["AuthContext<br/>(stores User)"]
Setup["SetupStatus<br/>(session cache)"]
Guard["InitGuard<br/>(checks status)"]
Token["InvitationToken<br/>(email tied)"]
Role["RoleGuard<br/>(reads role)"]
Cred["PasswordCredential<br/>(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
- App loads → InitGuard fetches SetupStatus
- If not initialized → redirect to SetupPage
- User completes setup → creates Owner User, redirects to LoginPage
- OR: User clicks invitation link → InviteCompletePage validates token
- User completes invitation → creates User, redirects to LoginPage
- LoginPage → creates session via AuthContext.login()
- AuthContext stores User (with role) in memory
- 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
accessTokenin memory)