Peering: generated key never displayed (empty bar), copy copies nothing, no key hint in issued list #136

Closed
opened 2026-08-07 20:15:29 +00:00 by Cordy · 1 comment
Owner

Dogfood report (v0.4.6, #131 followup). Generating a key for a peer shows an empty dark bar, clicking it flashes "Copied" near the file list (bottom-left), and the clipboard stays empty. Issued keys also show only the peer name, so an operator cannot tell which credential a row corresponds to.

Root cause (confirmed by code read): the issue handler does

const r = await api("/api/v1/admin/peering/keys", { ... });
out.textContent = r.key;
out.onclick = () => copyText(r.key);

but api() returns the raw Response object — the handler never calls .json(). r.key is undefined: the bar gets no text, and the click copies undefined. The stray "Copied" bubble appears at the viewport origin because copyText(r.key) is called without the btn argument that showBubble positions against. Being on plain HTTP additionally rules out navigator.clipboard (insecure context), so only the legacy path could ever work.

Fix:

  1. Parse the response (await res.json()) — the one-line root cause.
  2. Replace the bare click-to-copy <p> with a labelled one-time-key block: "One-time key for {peer}" + readonly mono input + explicit Copy button. Copy selects the visible input and uses document.execCommand("copy") (works in a modal dialog on insecure origins), with async clipboard as the secure-context path; feedback goes to the dialog's own message line, not a global bubble.
  3. listKeys returns the stored non-secret id (derived from the public key); the issued-keys list renders a Paddle-style mono hint next to each peer name so a row is identifiable against the credential the other admin holds.
Dogfood report (v0.4.6, #131 followup). Generating a key for a peer shows an empty dark bar, clicking it flashes "Copied" **near the file list** (bottom-left), and the clipboard stays empty. Issued keys also show only the peer name, so an operator cannot tell which credential a row corresponds to. **Root cause (confirmed by code read):** the issue handler does ```js const r = await api("/api/v1/admin/peering/keys", { ... }); out.textContent = r.key; out.onclick = () => copyText(r.key); ``` but `api()` returns the raw `Response` object — the handler never calls `.json()`. `r.key` is `undefined`: the bar gets no text, and the click copies `undefined`. The stray "Copied" bubble appears at the viewport origin because `copyText(r.key)` is called without the `btn` argument that `showBubble` positions against. Being on plain HTTP additionally rules out `navigator.clipboard` (insecure context), so only the legacy path could ever work. **Fix:** 1. Parse the response (`await res.json()`) — the one-line root cause. 2. Replace the bare click-to-copy `<p>` with a labelled one-time-key block: "One-time key for {peer}" + readonly mono input + explicit **Copy** button. Copy selects the visible input and uses `document.execCommand("copy")` (works in a modal dialog on insecure origins), with async clipboard as the secure-context path; feedback goes to the dialog's own message line, not a global bubble. 3. `listKeys` returns the stored non-secret `id` (derived from the public key); the issued-keys list renders a Paddle-style mono hint next to each peer name so a row is identifiable against the credential the other admin holds.
Cordy closed this issue 2026-08-07 20:21:08 +00:00
Author
Owner

Shipped in v0.4.7 (PR #137), live on the dogfood.

Root cause was one missing .json(): the issue handler read r.key off the raw Response object api() returns, so the one-time key was undefined — empty bar, and click-to-copy copied nothing. The mispositioned "Copied" bubble was copyText(r.key) called without the btn argument showBubble positions against.

What changed:

  • Handler parses the response and fills a proper one-time-key block: label "One-time key for {peer} — copy it now, it is not shown again.", readonly mono input with the key, and an explicit Copy button. No more invisible click-to-copy affordance; the stale value is cleared when the dialog reopens.
  • Copy works on the plain-HTTP LAN dogfood: the handler selects the visible input and tries document.execCommand("copy") first — that path works on insecure origins and inside modal dialogs, where navigator.clipboard is undefined. Async clipboard is the HTTPS path. Success/failure feedback goes to the dialog's own message line, not a global bubble.
  • Issued rows are identifiable: listKeys now returns the stored non-secret id (derived from the public key — the secret itself is KeyPrefix + b64(priv) and is never stored, so a true prefix-mask of the credential is impossible by design). The list renders Hyrsh 3f2a91c8d1… in faded mono, Paddle-style.

Verified green on the branch before merge: gofmt/vet, full go test ./..., node --check on both script blocks. Image cordy/cairn:v0.4.7 (sha256:e213440b…) confirmed Running 1/1 on the dogfood.

Dogfood caveat noticed while deploying: /data in cairn-enc is an emptyDir, so the peering registry (/data/.cairn/peering.json) — including the key issued for Hyrsh — and the runtime settings file are wiped on every redeploy. That is a deployment-manifest matter, not a code bug; flagged separately.

Shipped in **v0.4.7** (PR #137), live on the dogfood. **Root cause** was one missing `.json()`: the issue handler read `r.key` off the raw `Response` object `api()` returns, so the one-time key was `undefined` — empty bar, and click-to-copy copied nothing. The mispositioned "Copied" bubble was `copyText(r.key)` called without the `btn` argument `showBubble` positions against. **What changed:** - **Handler** parses the response and fills a proper one-time-key block: label "One-time key for {peer} — copy it now, it is not shown again.", readonly mono input with the key, and an explicit **Copy** button. No more invisible click-to-copy affordance; the stale value is cleared when the dialog reopens. - **Copy works on the plain-HTTP LAN dogfood:** the handler selects the visible input and tries `document.execCommand("copy")` first — that path works on insecure origins and inside modal dialogs, where `navigator.clipboard` is undefined. Async clipboard is the HTTPS path. Success/failure feedback goes to the dialog's own message line, not a global bubble. - **Issued rows are identifiable:** `listKeys` now returns the stored non-secret `id` (derived from the public key — the secret itself is `KeyPrefix + b64(priv)` and is never stored, so a true prefix-mask of the credential is impossible by design). The list renders `Hyrsh 3f2a91c8d1…` in faded mono, Paddle-style. Verified green on the branch before merge: gofmt/vet, full `go test ./...`, `node --check` on both script blocks. Image `cordy/cairn:v0.4.7` (`sha256:e213440b…`) confirmed Running 1/1 on the dogfood. **Dogfood caveat noticed while deploying:** `/data` in `cairn-enc` is an `emptyDir`, so the peering registry (`/data/.cairn/peering.json`) — including the key issued for Hyrsh — and the runtime settings file are wiped on every redeploy. That is a deployment-manifest matter, not a code bug; flagged separately.
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: Cordy/Cairn#136
No description provided.