GRO-1867: bridge Better Auth session to CustomerPortal #34
Reference in New Issue
Block a user
Delete Branch "gro-1867-portal-better-auth"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Adds the third initialisation path to
src/portal/CustomerPortal.tsxso a real customer authenticated via Authentik SSO can reach/portalinstead of being bounced back to/login. Pairs with GRO-1866 (POST /api/portal/session-from-auth).Closes GRO-1867.
Parent: GRO-1859 — Customer SSO sessions not persisting for HTML routes.
Flow
The bridged
sessionIdis threaded throughrenderSection()so every existing portal section keeps usingX-Impersonation-Session-Idheaders unchanged.Acceptance criteria
/loginredirect).sessionIdfromsession-from-authinX-Impersonation-Session-Id(verified byrenderSectionchange + section sources that already readsessionIdprop).session?.status === "active".dev.Verification
pnpm vitest run src/__tests__/portal.test.tsx→ 18 passed (4 new SSO-bridge cases).pnpm typecheck→ clean.pnpm lint→ no new warnings (pre-existinganywarning inApp.tsx:391untouched).UAT Playbook
Updated
UAT_PLAYBOOK.md§5.25 — added TC-WEB-5.25.1 through TC-WEB-5.25.11 covering: happy path, bridge call sequence, header propagation, no impersonation chrome, 404 fallback + sign-out, precedence vs?sessionId=/dev user, staff short-circuit, unauth login redirect, and reload persistence.Files
src/portal/CustomerPortal.tsx— newportalSessionId+authErrorstate, async SSO bridge in init effect, 404 friendly-message card, render guard updated.src/__tests__/portal.test.tsx— newCustomerPortal SSO bridgedescribe block (4 cases).UAT_PLAYBOOK.md— new §5.25 (11 cases).Handoff
Self-merging on green; will hand off to Lint Roller for QA via Paperclip.
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>QA Review — Post-merge finding
Status: Changes needed before this work can be considered complete.
Bug:
RescheduleFlowdoes not receiveportalSessionIdfor SSO bridge customersFile:
src/portal/CustomerPortal.tsxline 329Impact: When an SSO bridge customer opens the reschedule flow (triggered from the Dashboard),
RescheduleFlowreceivesnullas itssessionId. Any portal API calls insideRescheduleFlowthat sendX-Impersonation-Session-Id: nullwill be rejected. This violates AC3 ("Portal API calls use the session ID fromsession-from-authin theX-Impersonation-Session-Idheader").Context:
renderSection()was correctly updated to usesession?.id ?? portalSessionId(diff line +223), but theRescheduleFlowrender block at line 329 was missed — it continues to use the oldsession?.id ?? nullpattern.Fix: Change line 329 from
session?.id ?? nulltosession?.id ?? portalSessionId.All other acceptance criteria are satisfied. CI is green. UAT playbook updated. The fix is a one-liner.