feat(identity): handles Wave A2 — console claim UI (/console/security) - #185
Merged
Conversation
…ty (#A2)
A "Your handle" block on /console/security: an unclaimed account sees a
plain-form claim (no JS — the console house style) prefilled with the
email-derived suggestion (suggestHandleFromEmail) and the policy stated in
full; a claimed account sees its handle read-only ("renaming comes later").
POST /console/handle is the console door — session + CSRF + same-origin (the
console write boundary, checkForm) — calling the SAME claimHandle core as the
Bearer door (POST /account/handle), the checkoutCore/portalCore two-door
precedent from billing. No claim logic is duplicated. Typed failures map to a
re-rendered page: invalid/reserved inline in the form (attempted value
preserved, esc'd so a crafted handle can't break the attribute), handle_taken a
friendly inline "already taken", handle_already_set a gentle notice over the
read-only handle. Success re-renders showing the claimed handle.
No migration (A1 shipped 0022). Additive + inert on the wire — nothing serves a
/u/<handle> URL until later waves.
Tests (auth.test.ts, synthetic data only): both render states, and the POST
handler's happy path, mixed-case canonicalization, invalid, reserved, taken,
already-set, attribute-escape, CSRF-missing, cross-origin, and no-session.
Gates: identity typecheck clean + vitest 888 pass across 35 files (auth.test.ts
60, +14 new; run per-file — the local workerd pool exhausts sockets running all
files at once); root typecheck clean + root bun test 153 pass. Version → rc.102.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XLZtmuSs1RirWGMGyCB1QB
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
PR A2 — the console claim UI (Handles, Wave A)
Second PR of the handles wave sequence, building directly on A1 (#184, migration 0022 +
src/handles.ts+ the Bearer claim API). No migration here — UI + one console door.What ships
A "Your handle" block on
/console/security(renderSecurity, alongside email/password/2FA):suggestHandleFromEmail), the policy stated in full (lowercase letters/numbers/hyphens, 3–30, one per account, permanent for now).POST /console/handle— the console write boundary (session + CSRF + same-origin, viacheckForm, mirroringPOST /console/security), calling the sameclaimHandlecore as the Bearer door (POST /account/handle) — thecheckoutCore/portalCoretwo-door precedent from billing. No claim logic duplicated.Error mapping (all re-render
/console/security):invalid/reserved→ message inline in the form, attempted value preserved (andesc()'d — a crafted handle can't break thevalue="…"attribute, the same CSP injection-safety posture as every other form).handle_taken→ friendly inline "already taken".handle_already_set(a raced/stale double-submit) → a gentle notice over the now-read-only handle (no form left to inline into).Server-rendered HTML only, ui.ts style, CSP-compliant (no new inline scripts/handlers). Additive + inert on the wire — nothing serves a
/u/<handle>URL until later waves.Tests (
test/auth.test.ts, synthetic data only)Both render states (unclaimed shows suggestion + form; claimed shows read-only, no form), plus the POST handler: happy path, mixed-case canonicalization, invalid, reserved, taken, already-set, attribute-escape (
a"b→a"b), CSRF-missing, cross-origin, no-session.Gates (literal counts)
bun run typecheck— cleanbun x vitest run— 888 pass across 35 files (auth.test.ts60, +14 new). Run per-file: the local workerd pool exhausts sockets (EADDRNOTAVAIL) running all files at once — the documented cloud gotcha; every file passes individually.bun run typecheck— cleanbun run test— 153 pass, 0 fail (16 files)Version →
0.0.8-rc.102.🤖 Generated with Claude Code
https://claude.ai/code/session_01XLZtmuSs1RirWGMGyCB1QB