From 4dd3fe6bdeb4393550df9f7fee80d6f98c51111d Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Thu, 8 Oct 2026 15:48:10 -0500 Subject: [PATCH] capsite/diigo-cache: the wire page hands the owner the store id webapp's mint now answers a store id (x-webapp-store-id) beside the instance id: authority to rotate or revoke the diigo cookie. The wire page shows it apart from the instance id, with its own copy button, petname, and password-manager save; the cache never gets it. A webapp that sends none gets a note on the page and a warning in the log. Generated-by: Claude Opus 5.5 --- diigo-cache/src/index.ts | 53 +++++++++++++++++++++++++++------------- 1 file changed, 36 insertions(+), 17 deletions(-) diff --git a/diigo-cache/src/index.ts b/diigo-cache/src/index.ts index 7c01c0b..8ee5ea2 100644 --- a/diigo-cache/src/index.ts +++ b/diigo-cache/src/index.ts @@ -174,11 +174,13 @@ async function proxyMint(request: Request, env: Env): Promise { const detail = (await res.text()).slice(0, 200); return home(env, { error: `mint said ${res.status}: ${detail}` }, null); } - // webapp answers {id} (+ x-webapp-id); the id rides the header, the - // wire page is ours + // webapp answers {id, storeId} (+ x-webapp-id, x-webapp-store-id); + // the ids ride the headers, the wire page is ours const id = res.headers.get("x-webapp-id"); if (!id) return home(env, { error: "mint produced no id" }, null); - return await wirePage(id); + const storeId = res.headers.get("x-webapp-store-id"); + if (!storeId) console.warn({ event: "mint", outcome: "no store id: this session cannot be rotated" }); + return await wirePage(id, storeId); } catch (err) { console.error({ event: "mint", error: String(err) }); return home(env, { error: `mint failed: ${String(err)}` }, null); @@ -188,22 +190,37 @@ async function proxyMint(request: Request, env: Env): Promise { /** the credential-save helper, once per page (see ./pm-save.ts) */ const PM_SAVE_JS = pmSave.toString(); -/** the suggested petname gets the holder's local hh:mm (the worker only - * knows UTC), so two saves the same day are told apart at a glance */ -const PETNAME_TIME_JS = `{ - const p = document.getElementById("petname"); +/** the suggested petname (in the input with id `petnameId`) gets the + * holder's local hh:mm (the worker only knows UTC), so two saves the same + * day are told apart at a glance */ +function petnameTimeJs(petnameId: string): string { + return `{ + const p = document.getElementById(${JSON.stringify(petnameId)}); const d = new Date(); p.value += " " + String(d.getHours()).padStart(2, "0") + ":" + String(d.getMinutes()).padStart(2, "0"); }`; - -/** the onclick that hands pmSave its browser powers */ -function pmSaveClick(value: string): string { - return `pmSave(this, ${JSON.stringify(value)}, { petname: document.getElementById("petname"), credentials: navigator.credentials, PasswordCredential: window.PasswordCredential, log: console })`; } -/** the wire step, with the freshly-minted webapp id pre-filled */ -async function wirePage(webappId: string): Promise { +/** the onclick that hands pmSave its browser powers, saving under the + * petname in the input with id `petnameId` */ +function pmSaveClick(value: string, petnameId: string): string { + return `pmSave(this, ${JSON.stringify(value)}, { petname: document.getElementById(${JSON.stringify(petnameId)}), credentials: navigator.credentials, PasswordCredential: window.PasswordCredential, log: console })`; +} + +/** the wire step, with the freshly-minted webapp id pre-filled, and the + * store id (rotating the cookie) for the account owner to keep */ +async function wirePage(webappId: string, storeId: string | null): Promise { const fingerprint = await nick(webappId); + const storeSection = storeId + ? `

keep: the cookie's store id

+

This one is yours alone, not the cache's: with it you can rotate the +diigo session (log in again, paste the new cookie) or revoke it. Fingerprint +${esc(await nick(storeId))} +

+

+ +

` + : `

note: webapp sent no store id, so this session's cookie cannot be rotated; when it expires, mint again.

`; const body = ` diigo cache: step 2 — wire the cache @@ -212,8 +229,10 @@ async function wirePage(webappId: string): Promise {

(private to you: the password-manager username; never sent to the server)

- -

+ +

+${storeSection} +

wire

choose the cache mode, then wire — the next page shows the cache id (copy that too), after which you land back on home:

@@ -320,8 +339,8 @@ async function wiredPage(cacheId: string, note: string, hasPublic: boolean): Pro

(private to you: the password-manager username; never sent to the server)

- -

+ +

${note}

open this cache — its webkey (bookmark it, or save the id above): fill it, watch progress${hasPublic ? ", fill its public companion" : ""}.