# Journey: Privileged admin handoff

The admin entry is a real, explicit privilege exchange. A protected route sends
an unauthenticated browser to `/unauthorized`; the public `/handoff` page
detects the customer cookie; the Admin API forwards that token to the BFF; and
an eligible operator receives a separate scoped admin cookie before returning to
a safe local path. The browser and route tests cover the chain in layers rather
than one customer-cookie click-through E2E.

| Boundary                   | Responsibility                                                                         |
| -------------------------- | -------------------------------------------------------------------------------------- |
| Admin middleware           | Request id, production rate limit, admin-cookie shape/expiry, protected-route redirect |
| `/handoff` + launcher      | Customer-cookie presence, explicit request, status and redirect                        |
| `POST /api/admin/signin`   | Forward customer token, sanitize return path, set admin cookie                         |
| BFF session route          | Authenticate customer, resolve operator eligibility/scopes, mint admin token           |
| Proxied Admin BFF requests | Authoritative token signature and scope verification                                   |

## Personas

- **Eligible operator** — has a customer session and a BFF operator record with
  admin workspace scopes.
- **Ineligible customer** — is authenticated as a customer but has no operator
  record.
- **Expired admin operator** — has a malformed or expired admin cookie and must
  exchange again.
- **Anonymous visitor** — has neither cookie and receives sign-in guidance
  without an elevation button.

## Pre-conditions

- The customer shell has issued the `oshun-session` cookie, or a trusted caller
  supplies `x-oshun-customer-token` to the Admin signin route.
- The BFF session handoff route is reachable.
- Eligible users have a seeded or production operator record with roles, scopes,
  home workspace, and session TTL.
- `/handoff` and `/unauthorized` are public Admin paths; workspace paths are
  protected.

## Steps

```mermaid
flowchart LR
    A[Protected route] -->|no admin cookie| B[Unauthorized]
    B --> C[Open /handoff]
    C -->|customer cookie present| D[Request admin handoff]
    D --> E[Admin signin route]
    E -->|Bearer customer token| F[BFF eligibility]
    F -->|scoped admin token| G[HttpOnly admin cookie]
    G --> H[Safe local returnTo]
    F -->|401 or 403| I[Visible handoff error]
```

### 1. Enter a protected route without an admin session

- [x] Middleware generates an `X-Request-Id`.
- [x] A missing cookie redirects to
      `/unauthorized?reason=missing-session&returnTo=<path>`.
- [x] A malformed or expired cookie redirects with `reason=invalid-session` and
      is deleted on the redirect response.
- [x] The unauthorized page displays the attempted safe local path.
- [ ] Its current **Return to handoff entry** link points to `/`, not directly
      to `/handoff`; without a cookie that protected root can redirect back to
      unauthorized.

### 2. Open the public handoff page

- [x] `/handoff` reads both cookie namespaces without treating the customer
      cookie as an admin session.
- [x] A valid existing admin cookie immediately redirects to the sanitized
      `returnTo` or `/`.
- [x] Without an admin cookie, the page renders **Privileged handoff**, the
      explicit-elevation explanation, an optional readable reason, and the
      current request id.
- [x] Without a customer cookie, the launcher instructs the visitor to sign in
      through the consumer shell and does not render the request button.

### 3. Request elevation

- [x] **Request admin handoff** posts JSON to `/api/admin/signin` with
      same-origin credentials.
- [x] The Admin route reads the customer token from the cookie or trusted
      header.
- [x] Missing customer state returns 401 `customer-session-missing`.
- [x] The route forwards the customer token as a Bearer credential to
      `POST /v1/admin/session/handoff`.
- [ ] The launcher sends `x-request-id` to the Admin route, but the current BFF
      session client does not forward that header to the BFF exchange; do not
      claim one request id spans the full hop.

### 4. Resolve operator eligibility

- [x] The BFF authenticates the customer and looks up their operator record.
- [x] An eligible operator receives resolved roles, scopes, home workspace,
      session id, audience, expiry, and a dev or JWT admin token.
- [x] An authenticated customer without an operator record receives 403
      `operator_not_eligible`.
- [x] Missing or rejected customer authentication receives 401.
- [ ] This is an explicit first-party HTTP exchange, not an undocumented
      out-of-band IdP step.

### 5. Set the separate admin cookie

- [x] The Admin route stores the BFF token in `oshun-admin-session`.
- [x] Cookie attributes are `Path=/`, `HttpOnly`, `SameSite=Lax`, BFF-supplied
      expiry, and `Secure` in production.
- [x] Customer and admin cookie names remain separate.
- [x] The JSON response returns the operator view and sanitized redirect.

### 6. Return to a safe local path

- [x] `returnTo` must start with one slash.
- [x] Scheme/host targets, protocol-relative paths, `/unauthorized`, and
      `/handoff` are rejected.
- [x] Unsafe or absent values fall back to the operator home workspace.
- [x] The launcher assigns `window.location` to the returned path after a
      successful exchange.
- [ ] The sanitizer is a local-path rule, not a complete allowlist of every
      registered Admin route.

### 7. Enter and use the Admin shell

- [x] Middleware decodes token claims for fast routing and expiry checks.
- [x] A signed-in operator can load the dashboard and workspace shell.
- [x] The BFF still performs authoritative signature and scope checks on each
      proxied Admin API request; middleware claim decoding is not the trust
      anchor.
- [x] When the admin cookie later expires or becomes malformed, the protected
      route closes the loop through `invalid-session`.

### 8. Keep the rate limiter accurately bounded

- [x] Non-local production requests are limited to 120 requests per minute per
      derived IP and return 429 with `Retry-After: 60` and a request id.
- [x] Local development requests bypass that limiter.
- [ ] The middleware limiter uses a module-level Map, so it is per Admin
      instance and is not a shared multi-replica quota.
- [ ] The focused handoff browser coverage does not drive the 121-request
      production-only boundary.

## Post-conditions

- An eligible operator has a separate, scoped, expiring, HttpOnly admin cookie.
- The browser lands on a sanitized local path or the operator home workspace.
- Ineligible and unauthenticated customer sessions do not receive an admin
  token.
- A protected request with an invalid admin cookie clears it and returns to the
  denial flow.
- Cross-hop request-id continuity and distributed rate limiting are not implied.

## Failure modes

- **No customer cookie** — the handoff page renders guidance only.
- **Customer session rejected** — the Admin route returns a visible 401 error.
- **No operator record** — the BFF returns 403 and no admin cookie is set.
- **BFF unavailable or malformed payload** — the signin route returns a failed
  exchange rather than inventing a session.
- **Unsafe return path** — redirect falls back to the home workspace.
- **Expired or malformed admin cookie** — middleware deletes it and redirects
  with `invalid-session`.
- **Non-admin scopes in a parseable token** — the server-session gate rejects
  Admin-shell entry even if middleware accepted the token shape.
- **Shared-NAT burst in production** — the per-instance IP counter may
  return 429.
- **Unauthorized-page loop** — its root link is not currently a direct handoff
  link.
- **Correlation overclaim** — the request id entering the launcher is not
  forwarded by `requestAdminSessionHandoff`.

## E2E coverage

- [apps/oshun/admin/e2e/auth-entry-flows.spec.ts](../../apps/oshun/admin/e2e/auth-entry-flows.spec.ts)
  covers protected-route redirects, invalid/expired cookies, non-admin scope
  rejection, public routes, dashboard entry, and existing-admin `returnTo`.
- [apps/oshun/admin/src/**tests**/signin-route.test.ts](../../apps/oshun/admin/src/__tests__/signin-route.test.ts)
  covers the customer-cookie exchange, HttpOnly cookie, safe/unsafe redirects,
  and BFF 401/403 mapping.
- [apps/oshun/bff/src/**tests**/admin-session-route.test.ts](../../apps/oshun/bff/src/__tests__/admin-session-route.test.ts)
  covers token issuance, resolved scopes, operator ineligibility, missing auth,
  Admin `/session/me`, and signout.
- [apps/oshun/admin/src/**tests**/session-cookie.test.ts](../../apps/oshun/admin/src/__tests__/session-cookie.test.ts)
  covers dev/JWT-shaped claim parsing and expiry behavior.

Coverage is **strong across middleware, Admin route, and BFF layers but not one
browser-driven exchange**. No current Playwright case seeds a customer cookie,
clicks the launcher, observes the BFF handoff, and lands in the target
workspace.

## Per-view files touched by this journey

- [meta/unauthorized.md](../meta/unauthorized.md) — denial reasons and attempted
  path.
- [meta/handoff.md](../meta/handoff.md) — explicit customer-to-admin exchange.
- [shell/03-auth-session.md](../shell/03-auth-session.md) — cookie and trust
  boundaries.
- [shell/02-routing-layouts.md](../shell/02-routing-layouts.md) — public and
  protected routing.
- Any gated workspace may be the local return target.

## Cross-references

- [review-cycle-admin.md](./review-cycle-admin.md) — one protected target.
- `apps/oshun/admin/src/middleware.ts` — redirect, request-id, and rate-limit
  behavior.
- `apps/oshun/admin/src/app/handoff/page.tsx` and `HandoffLauncher.tsx` — entry
  UI.
- `apps/oshun/admin/src/app/api/admin/signin/route.ts` — exchange and cookie
  boundary.
- `apps/oshun/admin/src/lib/bff-session-client.ts` — BFF request and response
  validation.
- `apps/oshun/admin/src/lib/session-cookie.ts` — non-authoritative middleware
  claim parsing.

## Open questions

- [ ] Should the unauthorized page link directly to `/handoff` with its
      sanitized return path?
- [ ] Should the Admin signin route forward `x-request-id` to the BFF?
- [ ] Should `returnTo` be checked against the registered route model rather
      than only local-path rules?
- [ ] Which shared store should enforce the production rate limit across
      replicas?
- [ ] Should Playwright cover the full customer-cookie → click → workspace
      exchange?
- [ ] How are production JWT signing keys, revocation, and session rotation
      operated?
