App-password display: chunked + click-to-copy, never expire

closed
#58db1bc opened by agent Sep 11

Problem

App-password secrets are long, high-entropy, server-generated strings shown once in a plain <code> block with no copy affordance (account.templ secret banner), forcing desktop → phone hand-transcription character by character.

Constraints (decided)

  • Secrets are generated by Stalwart (x:AppPassword/set, only ttl is ours) and returned exactly once — length/entropy must NOT be weakened.
  • Passwords never expire: keep the ttl = 0 default; no expiry feature.

Proposal

  • Add a Copy button to the one-time secret banner (reuse the generic data-copy helper in static/app.js; the raw secret goes in the attribute).
  • Render the displayed secret chunked (e.g. 4-char groups) for manual transcription while copying the raw value; keep the “copy now, never shown again” warning.
  • autocomplete=off on the banner region.

Files

  • internal/appview/ui/account.templ (+ small chunk helper in view.go or templ), static/app.js only if the helper needs work
  • Tests: ui_test.go render coverage for the banner

Acceptance

  • One click copies the exact secret; displayed form is chunked.
  • Non-goal: entropy reduction, QR handoff (iPhone Mail can’t consume it), OAuth login (separate future).

Context

Flow being optimized: desktop browser → hand-type into phone mail app.

1 Comment

agent 598dddb Sep 11

Progress: chunked display implemented (group size 5, dual copy)

Secret format confirmed: app_aaaaaaqz9x2p1hacftrl91qtpftnlbev9j7a is 40 chars, so groups of 5 divide evenly and keep _ mid-group (app_a) instead of at a group edge (app_).

Changes (uncommitted, in working copy)

  • internal/appview/ui/hx.go: new chunkSecret helper (5-char groups, display-only; raw secret untouched; entropy unchanged).
  • internal/appview/ui/fragments.templ (+ regenerated fragments_templ.go): appPasswordSecret now renders chunks as adjacent <span>s in a flex-with-gap <code> — no whitespace text nodes, so drag-select pastes the exact secret. Styling: font-mono font-bold text-base sm:text-lg whitespace-nowrap, overflow-x-auto wrapper (one line even on mobile). Raw secret in data-copy on both the <code> (click secret to copy) and an explicit Copy button. Banner has autocomplete="off" spellcheck="false". Warning copy + OK dismiss kept.
  • internal/appview/ui/static/app.js: data-copy feedback changed from textContent swap (which would flatten chunk spans) to a transient Copied badge inserted after the element.
  • Tests: TestChunkSecretGroupsOfFive (hx_test.go), TestAppPasswordSecretChunkedCopiesRaw (fragments_test.go, incl. adjacency lock >app_a</span><span).

Verification

  • go build ./... clean; gofmt/go vet clean.
  • go test ./internal/appview/... — all pass (incl. both new tests, watched fail RED before GREEN).
  • Rendered HTML dumped and inspected: <span>app_a</span><span>aaaaa</span>… with zero bytes between spans.
  • static/app.js has no JS runtime in this env to syntax-check (node absent); change uses only standard DOM APIs already in the file. Recommend a browser click-through: click secret, click Copy, drag-select across groups, paste each into an editor and compare to raw.