# Business Rules — Unit 2: Authentication Pages ## Password Validation (Shared Schema) These rules apply to every password field in the application. The Zod schema lives in `src/lib/schemas/auth.ts` and is imported by `SetupPage`, `InviteCompletePage`, and `LoginPage` (BR-U2-24). | ID | Rule | Zod constraint | |---|---|---| | BR-U2-01 | Minimum 8 characters | `.min(8)` | | BR-U2-02 | At least 1 uppercase letter (A–Z) | `.regex(/[A-Z]/)` | | BR-U2-03 | At least 1 lowercase letter (a–z) | `.regex(/[a-z]/)` | | BR-U2-04 | At least 1 digit (0–9) | `.regex(/[0-9]/)` | | BR-U2-05 | At least 1 non-alphanumeric character (e.g. `!@#$%^&*`) | `.regex(/[^a-zA-Z0-9]/)` | These rules mirror the backend `IdentityOptions.Password` configuration exactly. Any change to backend password rules must also update this schema. --- ## Confirm Password | ID | Rule | |---|---| | BR-U2-06 | `confirmPassword` must be identical to `password`. Validated via Zod `.refine()` at the schema root level — not as an individual field constraint. Error is attached to the `confirmPassword` field. | --- ## Email Validation | ID | Rule | |---|---| | BR-U2-07 | `email` must pass Zod `.email()` (RFC-compliant format). Validated client-side before submission. | --- ## System Initialization Guard (InitGuard) | ID | Rule | |---|---| | BR-U2-08 | Setup status (`GET /Setup/status`) is fetched exactly once per browser session. The result is held in React state at the root route level. It is never re-fetched unless the user reloads the page. | | BR-U2-09 | When `initialized: false`, all routes redirect to `/setup` — including `/login`. The only route that bypasses this redirect is `/setup` itself. | | BR-U2-10 | When `initialized: true`, navigating to `/setup` redirects to `/login`. The `/setup` route is only accessible when the system is uninitialized. | --- ## Authentication Guard (ProtectedRoute) | ID | Rule | |---|---| | BR-U2-11 | Any route inside the authenticated layout requires a valid `AuthSession` (non-null `user` in `AuthContext`). | | BR-U2-12 | When there is no authenticated session, the router redirects to `/login`. The originally intended URL is preserved as a `redirect` search parameter (e.g. `/login?redirect=%2Fusers`). | | BR-U2-13 | No protected page content is rendered, even transiently, before the guard check resolves. | --- ## Role Guard (RoleGuard) | ID | Rule | |---|---| | BR-U2-14 | Route `/` (dashboard) — accessible to: `Owner`, `Admin`, `User` | | BR-U2-15 | Route `/profile` — accessible to: `Owner`, `Admin`, `User` | | BR-U2-16 | Route `/users` — accessible to: `Owner`, `Admin` | | BR-U2-17 | Route `/settings` — accessible to: `Owner` only | | BR-U2-18 | Route `/cms` — accessible to: `Owner` only | | BR-U2-19 | When a user's role is insufficient for the requested route, the page renders an inline "Access Denied" message within the normal page shell. No redirect to `/403` and no separate route is created in Unit 2. | | BR-U2-20 | The inline "Access Denied" state must be rendered by `RoleGuard` as a wrapper/HOC, not embedded in individual page components. | --- ## SetupPage Rules | ID | Rule | |---|---| | BR-U2-21 | The SetupPage form collects: `name`, `email`, `password`, `confirmPassword`, `locale`. | | BR-U2-22 | `locale` defaults to the browser's detected language (`navigator.language`), falling back to `'en'` if the detected language is not supported. | | BR-U2-23 | Changing `locale` immediately applies `i18n.changeLanguage()` so the page re-renders in the selected language as a preview. | | BR-U2-24 | After a successful `POST /Setup` response, the user is NOT automatically logged in. A success message is shown and the user is redirected to `/login` after a short delay (1–2 seconds) or immediately on a "Go to login" action. | | BR-U2-25 | The `POST /Setup` payload contains `{ name, email, password }`. The `locale` field is not sent to the backend. | --- ## InviteCompletePage Rules | ID | Rule | |---|---| | BR-U2-26 | On page mount, the token from `?token=xxx` is extracted from the URL and sent to `GET /Invitation/validate?token=xxx`. | | BR-U2-27 | While the validation request is in flight, a loading spinner is shown and the form is not rendered. | | BR-U2-28 | If the token is valid, the form is shown with the `email` field pre-filled from the validation response and set to read-only. | | BR-U2-29 | If the token is invalid or expired, an error state is shown. No form is rendered. The error message explains the reason (expired / already used / not found). A link to `/login` is provided. | | BR-U2-30 | If no `token` query parameter is present in the URL, this is treated as an invalid token (show error state immediately, no validation request). | | BR-U2-31 | After a successful `POST /Invitation/complete` response, the user is NOT automatically logged in. A success message is shown and the user is redirected to `/login`. | --- ## Form Error Handling (Application-Wide Standard) These rules define the error handling pattern that applies to ALL forms in the application (SetupPage, InviteCompletePage, LoginPage). | ID | Rule | |---|---| | BR-U2-32 | Field validation errors from Zod are shown **inline**, directly below the field. Inline errors are triggered **on blur** (when the user leaves a field), not on every keystroke. | | BR-U2-33 | Once a field has been touched (blurred), validation re-runs **on change** so the error clears as soon as the user corrects the input. | | BR-U2-34 | API-level errors (e.g. `400 Bad Request`, `409 Conflict`) returned after form submission are shown in a **dismissible banner** above the form. | | BR-U2-35 | Network errors (no response received) are shown in the **dismissible banner** with the message: "Unable to connect. Please try again." | | BR-U2-36 | The banner is dismissed when the user submits the form again or clicks the dismiss button. | | BR-U2-37 | `LoginPage` (from Unit 1) must be updated in Unit 2 to align with the inline validation pattern (BR-U2-32/33). The banner pattern is already in place. | --- ## i18n Rules | ID | Rule | |---|---| | BR-U2-38 | All Unit 2 strings (setup, invite complete, error messages, guard messages) are added to the existing `translation.json` files under `setup` and `inviteComplete` namespaces. No separate namespace files are created. | | BR-U2-39 | The supported locales are `en` and `nl`. All keys must be present in both locale files. | --- ## MSW Handler Rules | ID | Rule | |---|---| | BR-U2-40 | `POST /Setup` is added to the existing `src/mocks/setup/handlers.ts` file. | | BR-U2-41 | `GET /Invitation/validate` and `POST /Invitation/complete` are added to a new file `src/mocks/invitation/handlers.ts`. | | BR-U2-42 | MSW handlers for invitation endpoints are stubs: they return hard-coded success/error scenarios sufficient to test Unit 2 UI states. Full dynamic behaviour is implemented in Unit 5. |