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. |