lichen.page.review from the reviewed point 0b92cb61 to stack eb5a1653, as one PR per subsystem (review set)

product: 10 Front server: auth, hosts and routing #57

targeting oct/09-iroh from oct/10-front-auth
Closed opened by and assigned to notplants-bot.bsky.social
targeting oct/09-iroh from oct/10-front-auth

superseded by the full set from 09-23, #17–#37

description: Review set for lichen.page.review, 0b92cb61 (reviewed) → eb5a1653 (stack). PR 10 of 16. 16 files, +2629/−467. One commit, 33c1d1346eea, which sets these files to their content at eb5a1653. Targets #9 (oct/09-iroh). #11 stacks on this one. Tests and generated files are not in any PR; they are in main.

The front server's sign-in, sessions and routing, after the admin/edit host split and the security-table conformance fixes.

Hosts and roles (ADMIN-EDIT-HOST-SPLIT). A server with a dashboard now answers three kinds of host: the dashboard (dash.<domain>, admin), each site's edit host (<site>.edit.<domain>, or edit.<custom domain>; the editor only), and the site's published host, where /cms editing is also allowed (operator note N10). An edit host has its own session cookie, bound to that host and its one site; the owner signed in on the dashboard reaches it through a one-time handoff code. Each role sets its own framing headers (frame-ancestors, X-Frame-Options, Origin-Agent-Cluster). server.rs dispatches by host to these roles, every route in its permission group (#2); clients' git routes go to the hub (#5).

Fixes from the security-table conformance review, each named in the code:

  • STC-CSRF1: a browser's state-changing request must come from a page of this host; the session cookie is shared with sibling sites on a dashboard domain.
  • Password sign-ins are rate-limited per client and account, on account login, token login and the share-password login; trusted_proxies says whose X-Forwarded-For to believe.
  • STC-ATPROTO1: the CLI's atproto login is verified (the handle against the DID, a loopback client id, the grant as the server refreshed it), and atproto discovery follows no redirect to a private address.
  • TOKEN-SESSION1: a bearer token's identity is that request's only; it no longer leaks into the session cookie.
  • STC-GUEST1 / STC-FIXATION1: a share guest is let go when the share password changes; every login cycles the session id.
  • STC-FWDCRED1: the session cookie is taken off what is forwarded to a site process.

info: Plans: ADMIN-EDIT-HOST-SPLIT-2026-10-13.md (H1 host roles, H1e guests' admin acts, H2a handoff, H3 request checks and framing; the operator's answers Q1–Q4 in its header), OPERATOR-REVIEW-NOTES-2026-10-13.md N10, SECURITY-TABLE-CONFORMANCE-2026-10-11.md (S2 fixes) and its findings list SECURITY-TABLE-CONFORMANCE-2-2026-10-13.md.

Files (crates/lichen-server/src/)

  • host_roles.rs — new: which host is which role; the edit-host name rules (one label, injective). Read first.
  • server.rs (+520/−166) — host dispatch, the edit host's router and cookie, role headers, the hub's git routes, permission groups on every router, the React dashboard's files and no-JS forms mounted.
  • handoff.rs — new: the dashboard-to-edit-host sign-in code (H2a).
  • same_origin.rs — new: STC-CSRF1.
  • session_scope.rs — the cookie's name and scope; refusals in the dashboard API's error shape on /api.
  • auth.rs — not_signed_in (401 for the API, redirect otherwise), the share guest's session bound to the password hash, session-id cycling.
  • auth/login_limit.rs — new: the rate limit.
  • auth/token.rs — TOKEN-SESSION1; the CLI's grant stored as the server refreshed it.
  • auth/atproto/cli_login.rs — new: what the CLI's login must prove (STC-ATPROTO1).
  • auth/atproto/client.rs — a no-redirect client; handles a DID names; discovery checked public.
  • auth/atproto/handlers.rs, auth/atproto.rs, auth/oidc/handlers.rs, auth/atproto/discovery.rs — routes into groups; the loopback client id; a moved type path.
  • credentials.rs — the owner's atproto session as the front server's own work uses it (push to pds://, records); no site process holds one.
  • users.rs — read_owned for blocking workers; small.

Moved in: auth/atproto/* (the starting map's iroh/sync bucket matched them on atproto) and handoff.rs (its Site area bucket matched handoff).

Lives elsewhere

  • access.rs, the front server's permission policy: #2.
  • The sign-in pages themselves, now React drawn on the server (sign_in_render.rs): #12.
  • server_settings.rs (trusted_proxies, single-site state): #11.
  • docs/HOSTS.md, which describes the hosts for operators: #16.
  • Tests (auth/tests/, tests/same_origin.rs, host_roles.rs, handoff.rs, server.rs, router_group_semantics.rs, e2e) are in main.
Commits 0