|
@@ -8,36 +8,50 @@
|
|
|
|
|
|
|
|
<!-- --------------------------------------------------------------------------- -->
|
|
<!-- --------------------------------------------------------------------------- -->
|
|
|
|
|
|
|
|
-# Frontend UI: App Shell, Routing, and Login Flow (RHL-019 / RHL-020)
|
|
|
|
|
|
|
+# Frontend UI: App Shell, Routing, Login Flow, and UI RBAC (RHL-019 / RHL-020 / RHL-021)
|
|
|
|
|
|
|
|
This document describes the **frontend routing scaffold** and the **application shell layout** for the RHL Lieferscheine app.
|
|
This document describes the **frontend routing scaffold** and the **application shell layout** for the RHL Lieferscheine app.
|
|
|
|
|
|
|
|
-It started as a pure scaffold in **RHL-019** (public vs protected routes + AppShell + placeholder pages) and was extended in **RHL-020** with a **real login flow**, a **session guard** for protected routes, and a minimal **logout + user status UX**.
|
|
|
|
|
|
|
+It started as a pure scaffold in **RHL-019** (public vs protected routes + AppShell + placeholder pages), was extended in **RHL-020** with a **real login flow** and a **session guard** for protected routes, and was extended again in **RHL-021** with a **UI-side RBAC guard** plus consistent **Forbidden / NotFound UX**.
|
|
|
|
|
|
|
|
---
|
|
---
|
|
|
|
|
|
|
|
## 1. Scope
|
|
## 1. Scope
|
|
|
|
|
|
|
|
-### 1.1 Implemented (as of RHL-020)
|
|
|
|
|
|
|
+### 1.1 Implemented (as of RHL-021)
|
|
|
|
|
|
|
|
- Public `/login` route with a functional login form (shadcn/ui primitives).
|
|
- Public `/login` route with a functional login form (shadcn/ui primitives).
|
|
|
|
|
+
|
|
|
- Protected application shell for all other routes.
|
|
- Protected application shell for all other routes.
|
|
|
-- Minimal session guard for the protected area:
|
|
|
|
|
|
|
+
|
|
|
|
|
+- Session guard for the protected area:
|
|
|
|
|
|
|
|
- checks session via `GET /api/auth/me`
|
|
- checks session via `GET /api/auth/me`
|
|
|
- redirects to `/login?reason=expired&next=<original-url>` when unauthenticated
|
|
- redirects to `/login?reason=expired&next=<original-url>` when unauthenticated
|
|
|
|
|
|
|
|
- Logout button wired to `GET /api/auth/logout`.
|
|
- Logout button wired to `GET /api/auth/logout`.
|
|
|
|
|
+
|
|
|
- Minimal `UserStatus` that displays session state (role + branch).
|
|
- Minimal `UserStatus` that displays session state (role + branch).
|
|
|
|
|
+
|
|
|
- Centralized helper utilities for auth redirect behavior (`reason` / `next`) and error-to-message mapping.
|
|
- Centralized helper utilities for auth redirect behavior (`reason` / `next`) and error-to-message mapping.
|
|
|
|
|
|
|
|
-### 1.2 Still out of scope (planned)
|
|
|
|
|
|
|
+**RHL-021 additions:**
|
|
|
|
|
+
|
|
|
|
|
+- UI-side RBAC guard for branch routes (`BranchGuard`).
|
|
|
|
|
+- Consistent **Forbidden** UX for branch mismatches.
|
|
|
|
|
+- Consistent **NotFound** UX for invalid route params.
|
|
|
|
|
+- Server-side param validation via nested layouts (`notFound()` early).
|
|
|
|
|
+- Optional branch existence validation for admin/dev users via `GET /api/branches`.
|
|
|
|
|
+
|
|
|
|
|
+### 1.2 Still out of scope / planned
|
|
|
|
|
|
|
|
-- Full RBAC UI guard (branch users should be prevented from navigating to other branches in the UI).
|
|
|
|
|
- Explorer navigation UI (years/months/days lists in sidebar).
|
|
- Explorer navigation UI (years/months/days lists in sidebar).
|
|
|
- Search UI and filters.
|
|
- Search UI and filters.
|
|
|
- PDF viewer / file open.
|
|
- PDF viewer / file open.
|
|
|
-- HTTPS / reverse proxy (handled in a separate ticket).
|
|
|
|
|
|
|
+- A UI branch selector for admin/dev users (Sidebar placeholder will later host this).
|
|
|
|
|
+- Centralized UI error boundary mapping for API-level errors (e.g. mapping `AUTH_FORBIDDEN_BRANCH` to Forbidden UX inside explorer/search components).
|
|
|
|
|
+
|
|
|
|
|
+> Note: Prior to RHL-021, “Full RBAC UI guard” was listed as out of scope. It is now implemented for branch routes.
|
|
|
|
|
|
|
|
---
|
|
---
|
|
|
|
|
|
|
@@ -55,19 +69,23 @@ The app uses Next.js App Router **Route Groups** to separate public and protecte
|
|
|
- **Protected**: `app/(protected)`
|
|
- **Protected**: `app/(protected)`
|
|
|
|
|
|
|
|
- Routes that render inside the **AppShell**.
|
|
- Routes that render inside the **AppShell**.
|
|
|
- - As of RHL-020, protected routes are guarded by a session check.
|
|
|
|
|
|
|
+ - Protected routes are guarded by:
|
|
|
|
|
+
|
|
|
|
|
+ 1. session check (AuthProvider)
|
|
|
|
|
+ 2. UI RBAC check for branch routes (BranchGuard)
|
|
|
|
|
|
|
|
### 2.2 URL map
|
|
### 2.2 URL map
|
|
|
|
|
|
|
|
-| URL | Purpose | Notes |
|
|
|
|
|
-| ---------------------------- | --------------------------- | ---------------------------------------------------------- |
|
|
|
|
|
-| `/login` | Login page | Supports `reason` and `next` query params |
|
|
|
|
|
-| `/` | Protected entry placeholder | Only rendered when authenticated |
|
|
|
|
|
-| `/:branch` | Branch placeholder | Example: `/NL01` |
|
|
|
|
|
-| `/:branch/:year` | Year placeholder | Example: `/NL01/2025` |
|
|
|
|
|
-| `/:branch/:year/:month` | Month placeholder | Example: `/NL01/2025/12` |
|
|
|
|
|
-| `/:branch/:year/:month/:day` | Day placeholder | Example: `/NL01/2025/12/31` |
|
|
|
|
|
-| `/:branch/search` | Search placeholder | Explicit segment so `search` is not interpreted as `:year` |
|
|
|
|
|
|
|
+| URL | Purpose | Notes |
|
|
|
|
|
+| ---------------------------- | --------------------------- | ------------------------------------------------------------- |
|
|
|
|
|
+| `/login` | Login page | Supports `reason` and `next` query params |
|
|
|
|
|
+| `/` | Protected entry placeholder | Only rendered when authenticated |
|
|
|
|
|
+| `/:branch` | Branch placeholder | Example: `/NL01` |
|
|
|
|
|
+| `/:branch/:year` | Year placeholder | Example: `/NL01/2025` |
|
|
|
|
|
+| `/:branch/:year/:month` | Month placeholder | Example: `/NL01/2025/12` |
|
|
|
|
|
+| `/:branch/:year/:month/:day` | Day placeholder | Example: `/NL01/2025/12/31` |
|
|
|
|
|
+| `/:branch/search` | Search placeholder | Explicit segment so `search` is not interpreted as `:year` |
|
|
|
|
|
+| `/forbidden` | Forbidden page | Optional wrapper route; UI typically renders Forbidden inline |
|
|
|
|
|
|
|
|
Important:
|
|
Important:
|
|
|
|
|
|
|
@@ -123,6 +141,7 @@ File: `components/auth/AuthProvider.jsx`
|
|
|
Behavior:
|
|
Behavior:
|
|
|
|
|
|
|
|
1. On mount, call `apiClient.getMe()`.
|
|
1. On mount, call `apiClient.getMe()`.
|
|
|
|
|
+
|
|
|
2. If `{ user: { ... } }`:
|
|
2. If `{ user: { ... } }`:
|
|
|
|
|
|
|
|
- set auth state to `authenticated`
|
|
- set auth state to `authenticated`
|
|
@@ -147,12 +166,14 @@ Files:
|
|
|
Flow:
|
|
Flow:
|
|
|
|
|
|
|
|
1. Login page parses query params using `parseLoginParams(...)`.
|
|
1. Login page parses query params using `parseLoginParams(...)`.
|
|
|
|
|
+
|
|
|
2. If `reason` is present:
|
|
2. If `reason` is present:
|
|
|
|
|
|
|
|
- `expired` → show “Session expired” banner
|
|
- `expired` → show “Session expired” banner
|
|
|
- `logged-out` → show “Logged out” banner
|
|
- `logged-out` → show “Logged out” banner
|
|
|
|
|
|
|
|
3. On submit, the form calls `apiClient.login({ username, password })`.
|
|
3. On submit, the form calls `apiClient.login({ username, password })`.
|
|
|
|
|
+
|
|
|
4. On success:
|
|
4. On success:
|
|
|
|
|
|
|
|
- redirect to `next` if present
|
|
- redirect to `next` if present
|
|
@@ -201,15 +222,21 @@ Behavior:
|
|
|
|
|
|
|
|
### 5.1 API client
|
|
### 5.1 API client
|
|
|
|
|
|
|
|
-File: `lib/frontend/apiClient.js`
|
|
|
|
|
|
|
+File:
|
|
|
|
|
+
|
|
|
|
|
+- `lib/frontend/apiClient.js`
|
|
|
|
|
+
|
|
|
|
|
+Rules:
|
|
|
|
|
|
|
|
- All UI code must call the backend through this client.
|
|
- All UI code must call the backend through this client.
|
|
|
|
|
+
|
|
|
- Defaults:
|
|
- Defaults:
|
|
|
|
|
|
|
|
- `credentials: "include"`
|
|
- `credentials: "include"`
|
|
|
- `cache: "no-store"`
|
|
- `cache: "no-store"`
|
|
|
|
|
|
|
|
- Throws `ApiClientError` for standardized backend errors.
|
|
- Throws `ApiClientError` for standardized backend errors.
|
|
|
|
|
+
|
|
|
- RHL-020 uses:
|
|
- RHL-020 uses:
|
|
|
|
|
|
|
|
- `login({ username, password })`
|
|
- `login({ username, password })`
|
|
@@ -233,13 +260,21 @@ File: `lib/frontend/authMessages.js`
|
|
|
- Centralized mapping from error codes to user-facing strings.
|
|
- Centralized mapping from error codes to user-facing strings.
|
|
|
- Centralized banner copy for `reason=expired` and `reason=logged-out`.
|
|
- Centralized banner copy for `reason=expired` and `reason=logged-out`.
|
|
|
|
|
|
|
|
|
|
+### 5.4 Frontend route helpers
|
|
|
|
|
+
|
|
|
|
|
+File: `lib/frontend/routes.js`
|
|
|
|
|
+
|
|
|
|
|
+- Centralizes URL building.
|
|
|
|
|
+- Prevents scattered stringly-typed URLs.
|
|
|
|
|
+- Encodes dynamic segments defensively.
|
|
|
|
|
+
|
|
|
---
|
|
---
|
|
|
|
|
|
|
|
## 6. UI primitives (shadcn/ui)
|
|
## 6. UI primitives (shadcn/ui)
|
|
|
|
|
|
|
|
The login UI uses shadcn/ui primitives from `components/ui/*`.
|
|
The login UI uses shadcn/ui primitives from `components/ui/*`.
|
|
|
|
|
|
|
|
-Required components for RHL-020:
|
|
|
|
|
|
|
+Required components for the current scope:
|
|
|
|
|
|
|
|
- `card`
|
|
- `card`
|
|
|
- `input`
|
|
- `input`
|
|
@@ -268,7 +303,7 @@ To keep the project consistent and avoid tooling issues:
|
|
|
|
|
|
|
|
Note:
|
|
Note:
|
|
|
|
|
|
|
|
-- `components/auth/authContext` must be `.jsx` because it renders a JSX Provider.
|
|
|
|
|
|
|
+- `components/auth/authContext.jsx` must be `.jsx` because it renders a JSX Provider.
|
|
|
|
|
|
|
|
---
|
|
---
|
|
|
|
|
|
|
@@ -276,12 +311,20 @@ Note:
|
|
|
|
|
|
|
|
### 8.1 Unit tests
|
|
### 8.1 Unit tests
|
|
|
|
|
|
|
|
|
|
+Existing (RHL-019 / RHL-020):
|
|
|
|
|
+
|
|
|
- `lib/frontend/routes.test.js` (route builder)
|
|
- `lib/frontend/routes.test.js` (route builder)
|
|
|
- `lib/frontend/apiClient.test.js` (client defaults + error mapping)
|
|
- `lib/frontend/apiClient.test.js` (client defaults + error mapping)
|
|
|
- `lib/frontend/authRedirect.test.js` (reason/next parsing + sanitization)
|
|
- `lib/frontend/authRedirect.test.js` (reason/next parsing + sanitization)
|
|
|
- `lib/frontend/authMessages.test.js` (UI message mappings)
|
|
- `lib/frontend/authMessages.test.js` (UI message mappings)
|
|
|
- `components/app-shell/AppShell.test.js` (SSR smoke test)
|
|
- `components/app-shell/AppShell.test.js` (SSR smoke test)
|
|
|
|
|
|
|
|
|
|
+RHL-021 additions:
|
|
|
|
|
+
|
|
|
|
|
+- `lib/frontend/params.test.js` (year/month/day/branch param validation)
|
|
|
|
|
+- `lib/frontend/rbac/branchAccess.test.js` (pure RBAC decision helper)
|
|
|
|
|
+- `lib/frontend/rbac/branchUiDecision.test.js` (UI decision helper: allowed/forbidden/not-found)
|
|
|
|
|
+
|
|
|
### 8.2 Running tests
|
|
### 8.2 Running tests
|
|
|
|
|
|
|
|
From the repo root:
|
|
From the repo root:
|
|
@@ -298,7 +341,7 @@ npm run build
|
|
|
|
|
|
|
|
---
|
|
---
|
|
|
|
|
|
|
|
-## 9. Manual verification checklist (RHL-020)
|
|
|
|
|
|
|
+## 9. Manual verification checklist
|
|
|
|
|
|
|
|
### 9.1 Local (Docker)
|
|
### 9.1 Local (Docker)
|
|
|
|
|
|
|
@@ -326,11 +369,24 @@ Verify flows in the browser:
|
|
|
|
|
|
|
|
- Expect redirect to `/login?reason=logged-out`
|
|
- Expect redirect to `/login?reason=logged-out`
|
|
|
|
|
|
|
|
-### 9.2 Server (direct HTTP)
|
|
|
|
|
|
|
+RHL-021 checks:
|
|
|
|
|
+
|
|
|
|
|
+- Branch user:
|
|
|
|
|
+
|
|
|
|
|
+ - `/NL01/...` works (own branch)
|
|
|
|
|
+ - `/NL02/...` shows Forbidden (UI guard)
|
|
|
|
|
+ - Invalid params (e.g. `/NL01/abcd`, `/NL01/2024/99/01`) show NotFound
|
|
|
|
|
+
|
|
|
|
|
+- Admin/dev:
|
|
|
|
|
+
|
|
|
|
|
+ - Existing branches render
|
|
|
|
|
+ - Non-existing branch (e.g. `/NL9999`) shows NotFound (branch existence validation)
|
|
|
|
|
|
|
|
-The current server deployment is accessed via **direct HTTP**:
|
|
|
|
|
|
|
+> Note: Branch existence validation for admin/dev uses `GET /api/branches`. The backend branch listing is subject to a 60s server-side TTL micro-cache (storage module). New branch folders may appear with up to ~60s delay.
|
|
|
|
|
|
|
|
-- `http://<server-ip>:3000`
|
|
|
|
|
|
|
+### 9.2 Server
|
|
|
|
|
+
|
|
|
|
|
+Deploy and verify on the server URL.
|
|
|
|
|
|
|
|
Important cookie note:
|
|
Important cookie note:
|
|
|
|
|
|
|
@@ -347,12 +403,171 @@ Verify flows on the server URL:
|
|
|
- Valid login sets cookie and redirects back to `next`
|
|
- Valid login sets cookie and redirects back to `next`
|
|
|
- Logout clears session and shows `reason=logged-out`
|
|
- Logout clears session and shows `reason=logged-out`
|
|
|
|
|
|
|
|
|
|
+RHL-021 checks on server:
|
|
|
|
|
+
|
|
|
|
|
+- Branch-user forbidden routes show Forbidden UI.
|
|
|
|
|
+- Invalid params show NotFound.
|
|
|
|
|
+- Admin/dev branch existence validation matches real NAS branch folders.
|
|
|
|
|
+
|
|
|
---
|
|
---
|
|
|
|
|
|
|
|
## 10. Planned follow-ups
|
|
## 10. Planned follow-ups
|
|
|
|
|
|
|
|
- HTTPS / reverse proxy deployment (separate ticket)
|
|
- HTTPS / reverse proxy deployment (separate ticket)
|
|
|
-- UI-level RBAC guards (branch users cannot navigate to other branches)
|
|
|
|
|
- Replace placeholders with Explorer pages (years/months/days + files)
|
|
- Replace placeholders with Explorer pages (years/months/days + files)
|
|
|
- Add Search UI and filters
|
|
- Add Search UI and filters
|
|
|
- Add PDF open/view experience
|
|
- Add PDF open/view experience
|
|
|
|
|
+- Add admin/dev branch selector and navigation in the sidebar
|
|
|
|
|
+- Add centralized UI error mapping for API-level errors (Forbidden vs Session-expired)
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## 11. UI RBAC Guard & Forbidden/NotFound UX (RHL-021)
|
|
|
|
|
+
|
|
|
|
|
+### 11.1 Goals
|
|
|
|
|
+
|
|
|
|
|
+RHL-021 adds a friendly UI layer on top of backend RBAC:
|
|
|
|
|
+
|
|
|
|
|
+- Branch users must not access other branches’ URLs.
|
|
|
|
|
+- Admin/dev users may access any existing branch.
|
|
|
|
|
+- Invalid route parameters (year/month/day) should surface as NotFound.
|
|
|
|
|
+- Users should receive clear UX:
|
|
|
|
|
+
|
|
|
|
|
+ - Forbidden page for RBAC mismatches
|
|
|
|
|
+ - Consistent NotFound for invalid params and unknown branches
|
|
|
|
|
+
|
|
|
|
|
+Backend RBAC remains the source of truth. UI RBAC exists to:
|
|
|
|
|
+
|
|
|
|
|
+- prevent “obviously forbidden” navigation in the frontend
|
|
|
|
|
+- provide clearer, consistent UX for end users
|
|
|
|
|
+
|
|
|
|
|
+### 11.2 BranchGuard (UI-side RBAC)
|
|
|
|
|
+
|
|
|
|
|
+Files:
|
|
|
|
|
+
|
|
|
|
|
+- `components/auth/BranchGuard.jsx`
|
|
|
|
|
+- Pure logic:
|
|
|
|
|
+
|
|
|
|
|
+ - `lib/frontend/rbac/branchAccess.js`
|
|
|
|
|
+ - `lib/frontend/rbac/branchUiDecision.js`
|
|
|
|
|
+
|
|
|
|
|
+Responsibilities:
|
|
|
|
|
+
|
|
|
|
|
+- Read `user` and `status` from `AuthContext`.
|
|
|
|
|
+- Enforce branch rules:
|
|
|
|
|
+
|
|
|
|
|
+ - role `branch` → allowed only when `:branch === user.branchId`
|
|
|
|
|
+ - role `admin` / `dev` → allowed for any branch that exists
|
|
|
|
|
+
|
|
|
|
|
+Guard ordering:
|
|
|
|
|
+
|
|
|
|
|
+1. **AuthProvider** runs first and ensures we have a valid session (or redirects to login).
|
|
|
|
|
+2. **BranchGuard** runs for all routes under `/:branch/...`.
|
|
|
|
|
+
|
|
|
|
|
+### 11.3 Admin/dev branch existence validation
|
|
|
|
|
+
|
|
|
|
|
+Problem:
|
|
|
|
|
+
|
|
|
|
|
+- Without any existence check, an admin could navigate to a syntactically valid branch code that does not exist (e.g. `/NL200`) and still see a placeholder page.
|
|
|
|
|
+
|
|
|
|
|
+Solution:
|
|
|
|
|
+
|
|
|
|
|
+- For `admin` and `dev` users, BranchGuard validates that the route branch exists by calling:
|
|
|
|
|
+
|
|
|
|
|
+ - `GET /api/branches` via `apiClient.getBranches()`
|
|
|
|
|
+
|
|
|
|
|
+Behavior:
|
|
|
|
|
+
|
|
|
|
|
+- While the branch list is being fetched, BranchGuard shows a small loader:
|
|
|
|
|
+
|
|
|
|
|
+ - “Validating branch…”
|
|
|
|
|
+
|
|
|
|
|
+- If the requested `:branch` is not in the returned list:
|
|
|
|
|
+
|
|
|
|
|
+ - show **NotFound** UX
|
|
|
|
|
+
|
|
|
|
|
+Fail-open policy:
|
|
|
|
|
+
|
|
|
|
|
+- If fetching the branch list fails (network issues, temporary backend failure):
|
|
|
|
|
+
|
|
|
|
|
+ - BranchGuard does **not** block navigation permanently.
|
|
|
|
|
+ - It falls back to allowing the route to render.
|
|
|
|
|
+ - Backend RBAC and later API calls still enforce correctness.
|
|
|
|
|
+
|
|
|
|
|
+Security note:
|
|
|
|
|
+
|
|
|
|
|
+- Branch users do **not** perform existence checks for other branches.
|
|
|
|
|
+- If a branch user navigates to a different branch, they see Forbidden regardless of whether the branch exists.
|
|
|
|
|
+- This avoids leaking branch existence through UI behavior.
|
|
|
|
|
+
|
|
|
|
|
+### 11.4 Param validation (year/month/day)
|
|
|
|
|
+
|
|
|
|
|
+Files:
|
|
|
|
|
+
|
|
|
|
|
+- `lib/frontend/params.js`
|
|
|
|
|
+- Layout enforcement (server-side `notFound()`):
|
|
|
|
|
+
|
|
|
|
|
+ - `app/(protected)/[branch]/layout.jsx` (branch syntax)
|
|
|
|
|
+ - `app/(protected)/[branch]/[year]/layout.jsx`
|
|
|
|
|
+ - `app/(protected)/[branch]/[year]/[month]/layout.jsx`
|
|
|
|
|
+ - `app/(protected)/[branch]/[year]/[month]/[day]/layout.jsx`
|
|
|
|
|
+
|
|
|
|
|
+Rules (syntactic validation only):
|
|
|
|
|
+
|
|
|
|
|
+- `year`: `YYYY` (4 digits)
|
|
|
|
|
+- `month`: `MM` (01–12)
|
|
|
|
|
+- `day`: `DD` (01–31)
|
|
|
|
|
+
|
|
|
|
|
+If a param is invalid:
|
|
|
|
|
+
|
|
|
|
|
+- Next’s `notFound()` is triggered immediately
|
|
|
|
|
+- The protected NotFound UI is shown
|
|
|
|
|
+
|
|
|
|
|
+Notes:
|
|
|
|
|
+
|
|
|
|
|
+- This ticket only covers obvious invalid params.
|
|
|
|
|
+- “Syntactically valid but missing on disk” (backend `FS_NOT_FOUND`) is handled later in Explorer/Search UI components.
|
|
|
|
|
+
|
|
|
|
|
+### 11.5 Forbidden UX
|
|
|
|
|
+
|
|
|
|
|
+Files:
|
|
|
|
|
+
|
|
|
|
|
+- Reusable UI: `components/system/ForbiddenView.jsx`
|
|
|
|
|
+- Optional wrapper route: `app/(protected)/forbidden/page.jsx`
|
|
|
|
|
+
|
|
|
|
|
+Where Forbidden is shown:
|
|
|
|
|
+
|
|
|
|
|
+- BranchGuard renders ForbiddenView inline for branch mismatch.
|
|
|
|
|
+
|
|
|
|
|
+CTAs:
|
|
|
|
|
+
|
|
|
|
|
+- Branch users: “Go to my branch” (links to `/${user.branchId}`)
|
|
|
|
|
+- Admin/dev: “Go to home” (until a branch list/selector is available)
|
|
|
|
|
+
|
|
|
|
|
+### 11.6 NotFound UX
|
|
|
|
|
+
|
|
|
|
|
+Files:
|
|
|
|
|
+
|
|
|
|
|
+- Reusable UI: `components/system/NotFoundView.jsx`
|
|
|
|
|
+- Protected not-found entry: `app/(protected)/not-found.jsx`
|
|
|
|
|
+
|
|
|
|
|
+Where NotFound is shown:
|
|
|
|
|
+
|
|
|
|
|
+- Invalid params in layouts via `notFound()`.
|
|
|
|
|
+- Admin/dev: non-existing branch codes via BranchGuard existence validation.
|
|
|
|
|
+
|
|
|
|
|
+### 11.7 Interaction with backend RBAC errors
|
|
|
|
|
+
|
|
|
|
|
+Even with UI-side RBAC, the backend remains authoritative.
|
|
|
|
|
+
|
|
|
|
|
+Recommended policy for later UI tickets (Explorer/Search):
|
|
|
|
|
+
|
|
|
|
|
+- `AUTH_UNAUTHENTICATED`:
|
|
|
|
|
+
|
|
|
|
|
+ - let the existing session flow handle it (redirect to `/login?reason=expired`)
|
|
|
|
|
+
|
|
|
|
|
+- `AUTH_FORBIDDEN_BRANCH`:
|
|
|
|
|
+
|
|
|
|
|
+ - render ForbiddenView for that branch route
|
|
|
|
|
+
|
|
|
|
|
+This policy can be implemented as a small helper when Explorer/Search UI begins consuming the navigation endpoints.
|