refactor(portal): gate the route guard on a session predicate #460

Merged
Claude-Run merged 2 commits from feat/MAPPS-395-portal-session into main 2026-08-02 00:43:13 +02:00
Member

PortalGuard asked for the portal token just to test it for presence, which put a reader of the token outside the fetch helpers. Hand it has_portal_session() instead, so the token value never leaves hooks::fetch::api, and add the predicate to the allowlist the recurrence gate enforces.

#MAPPS-395

`PortalGuard` asked for the portal token just to test it for presence, which put a reader of the token outside the fetch helpers. Hand it `has_portal_session()` instead, so the token value never leaves `hooks::fetch::api`, and add the predicate to the allowlist the recurrence gate enforces. #MAPPS-395
Every `/api/v1/portal/*` route is guarded by mokosh-server's `portal_auth_middleware`, which decodes the bearer and rejects anything whose `typ` is not `portal_access`. The SPA was sending the agent bearer (`typ: "access"`) at all eight portal fetch sites, so the portal KB reader, ticket thread, invoice detail and quote sign-off returned 401 on every request, for signed-out visitors and agents alike.

Add `/portal/login`, which POSTs `{tenant_slug, email, password}` to `POST /api/v1/portal/auth/login` (matching mokosh-server's `PortalLoginRequest`) and stores the returned token in a portal-only holder next to `ACCESS_TOKEN` in `src/hooks/fetch.rs`. The new `get_portal_authed` / `post_portal_authed` / `post_portal_authed_typed` helpers read that holder and only that holder; they fail fast when there is no portal session rather than falling back to the agent bearer or firing an anonymous request. All eight portal call sites now go through them.

The portal route block moves under a new `PortalGuard` layout so a visitor with no portal session is redirected to `/portal/login` instead of rendering a page whose every fetch 401s. `/portal/login` and `/portal/set-password` stay outside the guard: both are reachable without a session by construction.

Three recurrence gates run in `cargo test --lib`: only the `_portal_authed` helpers may read the portal token holder, the agent auto-authed wrappers may not mention it, and `src/pages/portal.rs` may not reference any agent helper. Each was verified to fail when the corresponding regression is reintroduced.

The issue's remaining acceptance criterion (an integration test asserting `GET /api/v1/portal/kb` is 401 for a `typ: "access"` bearer and 200 for a `typ: "portal_access"` one) lives in mokosh-server, not this repo, and needs its own issue there. mokosh-server `tests/knowledge_base.rs` currently covers no-token and garbage-token 401s plus the portal-token 200, but not the agent-token case.

#MAPPS-395
refactor(portal): gate the route guard on a session predicate
All checks were successful
Check / fmt + clippy + tests (pull_request) Successful in 1m34s
Create release / Create release from merged PR (pull_request) Has been skipped
d679632ed3
`PortalGuard` asked for the portal token just to test it for presence, which put a reader of the token outside the fetch helpers. Hand it `has_portal_session()` instead, so the token value never leaves `hooks::fetch::api`, and add the predicate to the allowlist the recurrence gate enforces.

#MAPPS-395
Claude-Run deleted branch feat/MAPPS-395-portal-session 2026-08-02 00:43:13 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
psa-systems/mokosh-apps!460
No description provided.