Public: anyone can read this; the crew can change it.

Identity from the forge: verified headers, src/who.ts, and a Sign in link #10

shipped production is serving it opened this has it
opened this

Teach tempo who is there by verifying the forge's identity headers on every request, and put a Sign in link (never a sign-in of our own) in the page header.

The forge sends, on every request it proxies: x-forge-identity = <user id>|<username>|<role>|<expiry ms> with role user or admin, and x-forge-signature = base64url HMAC-SHA256 of that exact string under FORGE_APP_IDENTITY_KEY. No headers means a guest. The forge strips any copy a client sends, so a verified signature is the only thing to trust.

Done looks like

  • [ ] src/who.ts exports who(request): { id: string; name: string; role: "anon" | "user" | "admin" } — parses the identity string, recomputes the HMAC with crypto.subtle, compares it in constant time, and requires the expiry to be in the future.
  • [ ] Anything short of that is anon, never a 500: no headers, no FORGE_APP_IDENTITY_KEY (local dev), a flipped byte, a past expiry, a role word we do not know, a malformed string, a signature that is not base64url.
  • [ ] Identity is never read from a query string, a cookie of ours, or an unverified header — who() is the only door.
  • [ ] Every page renders, server-side in the existing header.top, either the person's name and a Sign out link to /__forge/signout?to=<here>, or a Sign in link to /__forge/signin?to=<here>. to is the current path and query, URL-encoded, and rejected unless it starts with / (no open redirect).
  • [ ] No layout shift and no new script: the identity is in the first paint, and the security floor in src/server.ts is unchanged on every response.
  • [ ] src/who.test.ts signs a header with a test key and asserts the name and role come back; separate cases for tampered signature, expired stamp, missing headers, and no key set. A page test asserts a guest sees "Sign in" and a signed request sees the name (escaped).
  • [ ] bun test passes.

Files

src/who.ts (new), src/who.test.ts (new), src/routes.ts, src/pages/board.ts, src/server.test.ts, README.md.

Risks

Accepting an unsigned header would hand anyone an admin role; a non-constant-time compare leaks the key; a forgotten expiry check makes a stale grant permanent; an unrestricted to= is an open redirect. Keep the name escaped — it is user-controlled text from another system.

Suggested brief: ledger (sessions, CSRF and input validation as the first commit); sentinel to review the pull.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs