---
path: /unauthorized
surface: admin
domain: meta
auth: anon (public path)
source: apps/oshun/admin/src/app/unauthorized/page.tsx
status: drafted
last_walked: '—'
---

# Meta · Access not granted

## Purpose

Auth-denial UI for the admin app. Reason-based copy from the internal
`REASON_COPY` table; surfaces the attempted `returnTo` path so the operator can
resume after handoff. Always renders the same lightweight card with no shell
chrome.

## Entry points

- Middleware bounce — `apps/oshun/admin/src/middleware.ts` redirects here when a
  non-public path is hit without a valid admin session
  (`shell/02-routing-layouts.md`). Reasons:
  - `missing-session` (no `OSHUN_ADMIN_SESSION_COOKIE_NAME`)
  - `invalid-session` (`parseAdminSessionToken` returned null — cookie also
    deleted on the response)
- Page-level / BFF redirect — `forbidden-workspace` reason can be surfaced when
  a page (or downstream) detects an operator with a valid session but missing
  workspace scope. Middleware itself does not check scope today, so this is
  dispatched by individual pages or by `HandoffLauncher` flows
- Direct URL — `PUBLIC_PATHS` includes `/unauthorized`; no session required

## Layout regions

This page does NOT render `AdminShell`. Renders a plain
`<main role="main" className={styles.page}>`:

- **Card** (`section.styles.card`):
  - **Eyebrow** — "Oshun · Operations"
  - **Heading** — h1 from `REASON_COPY[reasonKey].heading`
  - **Body** — `<p>` from `REASON_COPY[reasonKey].body`
  - **Attempted path** — when `returnTo` is non-null and validates via
    `sanitizeReturnTo`, renders
    `<p>Attempted path: <code> {returnTo}</code></p>`
  - **Action link** — `<Link href="/">Return to handoff entry</Link>` (anchors
    to admin root, which middleware will re-bounce to `/handoff` if there's no
    admin session)

## States

The `reasonKey` selection runs before render:

- [ ] `searchParams.reason` matches a `REASON_COPY` key (`missing-session` /
      `invalid-session` / `forbidden-workspace`) → that copy renders
- [ ] `searchParams.reason` absent or unrecognized → defaults to
      `missing-session`
- [ ] `returnTo` query missing → "Attempted path" line omitted
- [ ] `returnTo` query starts with `/` but not `//` → "Attempted path:
      <code><value></code>" renders
- [ ] `returnTo` query starts with `//` or doesn't start with `/` →
      `sanitizeReturnTo` returns null; line omitted
- [ ] Reason copy variants (from page source):
  - `missing-session` — h1 "Admin session required"; body explains
    privileged-handoff requirement and that customer sessions cannot enter admin
    routes
  - `invalid-session` — h1 "Admin session expired or invalid"; body explains
    token verification failure and recommends requesting a new privileged
    handoff
  - `forbidden-workspace` — h1 "Workspace access denied"; body explains the
    operator's admin role does not grant the workspace scope and to contact a
    studio operator

## Interactions

- [ ] **"Return to handoff entry"** (link — primary)
  - Function: navigates to `/` (admin root). Anonymous operators hit middleware,
    get bounced to `/handoff` (the privileged- handoff entry). Operators with a
    valid admin session will land on the dashboard.
  - Tab order: the only focusable control on the page

The page exposes no buttons or forms.

## Data & contracts

- **Reads**:
  - `searchParams` (Promise) — `reason`, `returnTo`
  - `REASON_COPY` — module-local constant mapping `missing-session` /
    `invalid-session` / `forbidden-workspace` to `{ heading, body }`
- **Writes**: _None._
- **Realtime**: _None._
- **Auth/role check**: NO middleware session check — `/unauthorized` is in
  `PUBLIC_PATHS`

## Cross-references

- Shell: `shell/02-routing-layouts.md` (middleware redirect logic),
  `shell/03-auth-session.md` (the three reasons documented there match
  `REASON_COPY` exactly)
- Sibling meta route: `handoff.md` (the page operators land on after clicking
  the action link, when no admin session is present)

## Open questions / known gaps

- [ ] `forbidden-workspace` reason: middleware does NOT check workspace scope;
      this reason is presumably dispatched by a page-level check or by the BFF
      `/api/admin/signin` flow. Locate the actual dispatcher
- [ ] No "Sign in differently" or "Switch operator" affordance — verify whether
      a future state should expose operator switching from here
- [ ] Action link points to `/` not `/handoff`; this is intentional (it lets the
      middleware bounce back to `/handoff` for anonymous traffic) but it adds an
      extra redirect hop
