9.4 KiB
Business Logic Model — Unit 2: Authentication Pages
Overview
Unit 2 implements five distinct business logic flows. Each is technology-agnostic; implementation details (TanStack Router APIs, React Query, etc.) are resolved in Code Generation.
Flow 1: App Initialization — InitGuard
Trigger: Every page load / app mount (runs in the root route)
Purpose: Ensure the system is initialized before rendering any route
flowchart TD
A([App mounts]) --> B{"Setup status<br/>already cached?"}
B -- Yes --> E
B -- No --> C["Fetch GET /Setup/status"]
C --> D{"Request<br/>outcome"}
D -- Network error --> ERR["Unable to reach server"]
D -- Success --> E{"initialized?"}
E -- false --> F{"On /setup<br/>already?"}
F -- Yes --> G([Render /setup page])
F -- No --> H([Redirect to /setup])
E -- true --> I{"On /setup<br/>already?"}
I -- Yes --> J([Redirect to /login])
I -- No --> K([Continue to route])
classDef start fill:#c7f9e9,stroke:#065f46,stroke-width:2px,color:#1a1a1a
classDef decision fill:#fef3c7,stroke:#b45309,stroke-width:1px,color:#1a1a1a
classDef action fill:#dbeafe,stroke:#1d4ed8,stroke-width:1px,color:#1a1a1a
classDef error fill:#fee2e2,stroke:#b91c1c,stroke-width:1px,color:#1a1a1a
classDef terminal fill:#f3e8ff,stroke:#7c3aed,stroke-width:2px,color:#1a1a1a
class A start
class B,D,E,F,I decision
class C action
class ERR error
class G,H,J,K terminal
Text alternative: On app mount, fetch setup status (once per session). If not initialized → redirect to /setup (except when already on /setup). If initialized → allow navigation (redirect away from /setup to /login).
Caching rule: The fetch result is held in React state at root level. Once loaded, it never re-fetches within the same session (BR-U2-08).
Flow 2: Protected Route Access — ProtectedRoute
Trigger: User navigates to any route inside the authenticated layout
Purpose: Prevent unauthenticated access to protected pages
flowchart TD
A([Route navigation]) --> B{"AuthContext:<br/>user present?"}
B -- Yes --> C([Render requested page])
B -- No --> D{"Restoring<br/>session?"}
D -- Yes --> E([Loading spinner])
E --> B
D -- No --> F["Redirect to /login<br/>with ?redirect=path"]
classDef start fill:#c7f9e9,stroke:#065f46,stroke-width:2px,color:#1a1a1a
classDef decision fill:#fef3c7,stroke:#b45309,stroke-width:1px,color:#1a1a1a
classDef action fill:#dbeafe,stroke:#1d4ed8,stroke-width:1px,color:#1a1a1a
classDef terminal fill:#f3e8ff,stroke:#7c3aed,stroke-width:2px,color:#1a1a1a
classDef loading fill:#e0e7ff,stroke:#4338ca,stroke-width:1px,color:#1a1a1a
class A start
class B,D decision
class F action
class C terminal
class E loading
Text alternative: Check if user is in AuthContext. If restoring session, show spinner. If no user after restore, redirect to /login preserving the original path.
Flow 3: Role-Restricted Route Access — RoleGuard
Trigger: Authenticated user navigates to a role-restricted route
Purpose: Enforce per-route role requirements
flowchart TD
A([Authenticated navigation]) --> B{"Route has<br/>role restriction?"}
B -- No restriction --> C([Render page])
B -- Owner only --> D{"user.role<br/>= Owner?"}
B -- Owner or Admin --> E{"user.role<br/>= Owner or Admin?"}
D -- Yes --> C
D -- No --> F([Inline Access Denied])
E -- Yes --> C
E -- No --> F
classDef start fill:#c7f9e9,stroke:#065f46,stroke-width:2px,color:#1a1a1a
classDef decision fill:#fef3c7,stroke:#b45309,stroke-width:1px,color:#1a1a1a
classDef terminal fill:#f3e8ff,stroke:#7c3aed,stroke-width:2px,color:#1a1a1a
classDef denied fill:#fee2e2,stroke:#b91c1c,stroke-width:1px,color:#1a1a1a
class A start
class B,D,E decision
class C terminal
class F denied
Text alternative: If route has no restriction → render. If Owner-only route → check role, render page or show inline "Access Denied". If Owner/Admin route → same check.
Inline Access Denied: Rendered within the normal page shell (sidebar + layout remain visible). The message identifies the required role. No redirect occurs (BR-U2-19).
Flow 4: System Setup — SetupPage
Trigger: User visits /setup and system is uninitialized (initialized: false)
Purpose: Create the first Owner account
flowchart TD
A([User opens /setup]) --> B["Render 5-field form<br/>Name · Email · Password<br/>Confirm Password · Locale"]
B --> C{"Locale<br/>changed?"}
C -- Yes --> D["Apply i18n.changeLanguage"]
D --> B
C -- No --> E["User submits form"]
E --> F{"Zod<br/>validation"}
F -- Invalid --> G["Show inline field errors"]
G --> B
F -- Valid --> H["Disable submit · loading"]
H --> I["POST /Setup"]
I --> J{"Response"}
J -- 200/201 --> K["Show success message"]
K --> L([Redirect to /login])
J -- 409 Conflict --> M["Banner: already initialized"]
J -- 4xx --> N["Banner: API error"]
J -- Network error --> O["Banner: Unable to connect"]
M --> B
N --> B
O --> B
classDef start fill:#c7f9e9,stroke:#065f46,stroke-width:2px,color:#1a1a1a
classDef decision fill:#fef3c7,stroke:#b45309,stroke-width:1px,color:#1a1a1a
classDef action fill:#dbeafe,stroke:#1d4ed8,stroke-width:1px,color:#1a1a1a
classDef error fill:#fee2e2,stroke:#b91c1c,stroke-width:1px,color:#1a1a1a
classDef terminal fill:#f3e8ff,stroke:#7c3aed,stroke-width:2px,color:#1a1a1a
classDef success fill:#dcfce7,stroke:#15803d,stroke-width:1px,color:#1a1a1a
class A start
class C,F,J decision
class B,D,E,H,I action
class G,M,N,O error
class K success
class L terminal
Text alternative: Render 5-field form → live locale switching → submit → Zod validation → POST /Setup → success banner + redirect to /login, or error banner on API/network failure.
Flow 5: Invitation Completion — InviteCompletePage
Trigger: User clicks an invitation link (/invite/complete?token=xxx)
Purpose: Complete account setup for an invited user
flowchart TD
A([User opens /invite/complete]) --> B{"token in URL?"}
B -- No --> C(["Error: invalid link"])
B -- Yes --> D(["Loading spinner"])
D --> E["GET /Invitation/validate"]
E --> F{"Validation<br/>result"}
F -- Network error --> G(["Error: unable to connect"])
F -- isValid=false --> H(["Error: expired / used / not found"])
F -- isValid=true --> I["Show form<br/>email read-only · Name · Password<br/>Confirm Password"]
I --> J["User submits form"]
J --> K{"Zod<br/>validation"}
K -- Invalid --> L["Inline field errors"]
L --> I
K -- Valid --> M(["Disable submit · loading"])
M --> N["POST /Invitation/complete"]
N --> O{"Response"}
O -- Success --> P["Show success message"]
P --> Q([Redirect to /login])
O -- 4xx --> R["Banner: API error"]
O -- Network error --> S["Banner: Unable to connect"]
R --> I
S --> I
classDef start fill:#c7f9e9,stroke:#065f46,stroke-width:2px,color:#1a1a1a
classDef decision fill:#fef3c7,stroke:#b45309,stroke-width:1px,color:#1a1a1a
classDef action fill:#dbeafe,stroke:#1d4ed8,stroke-width:1px,color:#1a1a1a
classDef error fill:#fee2e2,stroke:#b91c1c,stroke-width:1px,color:#1a1a1a
classDef terminal fill:#f3e8ff,stroke:#7c3aed,stroke-width:2px,color:#1a1a1a
classDef success fill:#dcfce7,stroke:#15803d,stroke-width:1px,color:#1a1a1a
classDef loading fill:#e0e7ff,stroke:#4338ca,stroke-width:1px,color:#1a1a1a
class A start
class B,F,K,O decision
class E,I,J,N action
class C,G,H,R,S error
class P success
class Q terminal
class D,M loading
Text alternative: On mount → check token param → validate with API (loading spinner) → invalid token shows error state; valid token shows form → submit → success banner + redirect to /login, or error banner on failure.
Flow Interaction Diagram
The five flows compose within the TanStack Router tree:
graph TD
Root["__root · InitGuard"] --> PublicSetup["/setup · SetupPage"]
Root --> PublicLogin["/login · LoginPage"]
Root --> PublicInvite["/invite/complete · InviteCompletePage"]
Root --> AuthLayout["_authenticated · ProtectedRoute"]
AuthLayout --> Dashboard["/ · Dashboard"]
AuthLayout --> Profile["/profile · ProfilePage"]
AuthLayout --> Users["/users · UsersPage<br/>RoleGuard: Owner or Admin"]
AuthLayout --> Settings["/settings · SettingsPage<br/>RoleGuard: Owner"]
AuthLayout --> CMS["/cms · CmsPage<br/>RoleGuard: Owner"]
classDef guard fill:#fef3c7,stroke:#b45309,stroke-width:2px,color:#1a1a1a
classDef public fill:#dbeafe,stroke:#1d4ed8,stroke-width:1px,color:#1a1a1a
classDef protected fill:#dcfce7,stroke:#15803d,stroke-width:1px,color:#1a1a1a
classDef restricted fill:#fee2e2,stroke:#b91c1c,stroke-width:1px,color:#1a1a1a
class Root guard
class PublicSetup,PublicLogin,PublicInvite public
class Dashboard,Profile protected
class AuthLayout,Users,Settings,CMS restricted
Text alternative: Root route runs InitGuard. Public routes (/setup, /login, /invite/complete) are accessible without authentication. The _authenticated layout wraps all protected routes and runs ProtectedRoute. Role-restricted routes (/users, /settings, /cms) additionally run RoleGuard.