Adds a third initialisation path to src/portal/CustomerPortal.tsx so real
customers authenticated via Authentik SSO can reach /portal without being
bounced back to /login.
After the existing impersonation (?sessionId=) and dev-mode (localStorage
dev-user) paths, the portal now:
1. Calls GET /api/auth/get-session (credentials: include) to detect an
active Better Auth session.
2. If the user is a non-staff customer, POSTs /api/portal/session-from-auth
(the endpoint shipped by GRO-1866) to mint a portal session.
3. Stores the returned sessionId in portalSessionId state and threads it
through renderSection -> sections so all /api/portal/* calls include
the X-Impersonation-Session-Id header.
4. On 404 (no client row), renders a friendly "Portal access not
configured" card with a Sign out button instead of looping back to
/login. On 401/network error, falls through to the existing /login
redirect guard.
The bridge skips when impersonation or dev-user is active and when the
Better Auth user is staff (App.tsx already routes staff to /admin). The
impersonation banner remains gated on session?.status === "active", so the
SSO-bridged session does not show staff chrome.
Tests:
- 4 new vitest cases in src/__tests__/portal.test.tsx cover the success,
404 fallback, missing-Better-Auth-session, and staff-role paths.
- pnpm vitest run src/__tests__/portal.test.tsx -> 18 passed
- pnpm typecheck -> clean
UAT_PLAYBOOK.md: adds §5.25 (TC-WEB-5.25.1 - TC-WEB-5.25.11) covering the
new flow end-to-end on UAT.
Co-Authored-By: Paperclip <noreply@paperclip.ing>