diigo-cache: webkey page logs in with the password manager
/cache/ without a fragment is now a login form: petname (autocomplete=username) and cache id (type=password, autocomplete=current-password), the shape password managers fill from the entries the save button makes. Its inputs have no name, so a native submit (script failed) sends nothing; the key stays in memory, out of the address bar and history. Home links to it. Generated-by: Claude Opus 5.5
This commit is contained in:
1 parent
961515d8db
commit
efaf74023d
3 files changed
+37
-2
No files matched your search
@@ -5,6 +5,11 @@
|
|||||||
* it for a capability (`api.cache(key)`, Cap'n Web over /api, in-band),
|
* it for a capability (`api.cache(key)`, Cap'n Web over /api, in-band),
|
||||||
* and drives the fill job. Each round trip is its own HTTP batch session:
|
* and drives the fill job. Each round trip is its own HTTP batch session:
|
||||||
* calls made before an await travel together.
|
* calls made before an await travel together.
|
||||||
|
*
|
||||||
|
* Without a fragment, the page is a login form a password manager fills
|
||||||
|
* (petname as username, cache id as password). Its inputs have no
|
||||||
|
* `name`, so a native submit (script failed) sends nothing; the key
|
||||||
|
* stays in memory, out of the address bar and history.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export const CACHE_PAGE_HTML = `<!doctype html>
|
export const CACHE_PAGE_HTML = `<!doctype html>
|
||||||
@@ -13,6 +18,11 @@ export const CACHE_PAGE_HTML = `<!doctype html>
|
|||||||
<body>
|
<body>
|
||||||
<h1>diigo cache <code id="fingerprint"></code></h1>
|
<h1>diigo cache <code id="fingerprint"></code></h1>
|
||||||
<p id="problem" hidden></p>
|
<p id="problem" hidden></p>
|
||||||
|
<form id="login" hidden>
|
||||||
|
<p><label>petname <input id="login-petname" autocomplete="username" size="24"></label></p>
|
||||||
|
<p><label>cache id <input id="login-key" type="password" autocomplete="current-password" size="24" required></label></p>
|
||||||
|
<p><button type="submit">open</button></p>
|
||||||
|
</form>
|
||||||
<section id="private" hidden>
|
<section id="private" hidden>
|
||||||
<h2>private cache</h2>
|
<h2>private cache</h2>
|
||||||
<p id="private-status">no fill job yet</p>
|
<p id="private-status">no fill job yet</p>
|
||||||
@@ -29,7 +39,7 @@ export const CACHE_PAGE_HTML = `<!doctype html>
|
|||||||
<script type="module">
|
<script type="module">
|
||||||
import { newHttpBatchRpcSession } from "/_capnweb.js";
|
import { newHttpBatchRpcSession } from "/_capnweb.js";
|
||||||
|
|
||||||
const key = location.hash.slice(1);
|
let key = location.hash.slice(1);
|
||||||
const $ = (id) => document.getElementById(id);
|
const $ = (id) => document.getElementById(id);
|
||||||
const problem = (text) => {
|
const problem = (text) => {
|
||||||
$("problem").textContent = text;
|
$("problem").textContent = text;
|
||||||
@@ -71,8 +81,21 @@ function report(err) {
|
|||||||
problem(String(err));
|
problem(String(err));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** no fragment: ask for the key (the password manager fills it in) */
|
||||||
|
function login() {
|
||||||
|
const form = $("login");
|
||||||
|
form.hidden = false;
|
||||||
|
form.addEventListener("submit", (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
key = $("login-key").value.trim();
|
||||||
|
if (!key) return;
|
||||||
|
form.hidden = true;
|
||||||
|
main().catch(report);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
if (!key) return problem("no key: this page's address should end in #<cache id>");
|
if (!key) return login();
|
||||||
const c = cap();
|
const c = cap();
|
||||||
const [fingerprint, hasPublic] = await Promise.all([c.fingerprint(), c.hasPublic()]);
|
const [fingerprint, hasPublic] = await Promise.all([c.fingerprint(), c.hasPublic()]);
|
||||||
$("fingerprint").textContent = fingerprint;
|
$("fingerprint").textContent = fingerprint;
|
||||||
|
|||||||
@@ -582,6 +582,7 @@ nobody here opens it. Mint lands you on the wire step, id pre-filled.</li>
|
|||||||
<p><button type="submit">wire</button></p>
|
<p><button type="submit">wire</button></p>
|
||||||
</form>
|
</form>
|
||||||
<p>the ids this mints are for <b>you</b>: copy them somewhere you control (password manager)</p>
|
<p>the ids this mints are for <b>you</b>: copy them somewhere you control (password manager)</p>
|
||||||
|
<p><a href="/cache/">open a cache</a> — fill it, watch progress (your password manager can fill in the cache id)</p>
|
||||||
${dumpForm}
|
${dumpForm}
|
||||||
${result}
|
${result}
|
||||||
</body></html>`,
|
</body></html>`,
|
||||||
|
|||||||
@@ -240,6 +240,17 @@ describe("fill via webkey and Cap'n Web (goal 1e)", () => {
|
|||||||
expect(html).toContain('<meta name="referrer" content="no-referrer">');
|
expect(html).toContain('<meta name="referrer" content="no-referrer">');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("without a fragment, the webkey page is a login form a password manager can fill, which posts nothing", async () => {
|
||||||
|
const html = await (await call("/cache/")).text();
|
||||||
|
const form = html.match(/<form id="login"[^]*?<\/form>/)?.[0] ?? "";
|
||||||
|
expect(form).toMatch(/<input[^>]* autocomplete="username"/);
|
||||||
|
expect(form).toMatch(/<input[^>]* type="password"[^>]* autocomplete="current-password"/);
|
||||||
|
// no name attributes: a native submit carries no key anywhere
|
||||||
|
expect(form).not.toMatch(/\bname=/);
|
||||||
|
expect(form).not.toMatch(/\baction=/);
|
||||||
|
expect(html).toContain("event.preventDefault()");
|
||||||
|
});
|
||||||
|
|
||||||
it("serves capnweb's browser build itself (no third-party script beside the key)", async () => {
|
it("serves capnweb's browser build itself (no third-party script beside the key)", async () => {
|
||||||
const res = await call("/_capnweb.js");
|
const res = await call("/_capnweb.js");
|
||||||
expect(res.headers.get("content-type")).toMatch(/javascript/);
|
expect(res.headers.get("content-type")).toMatch(/javascript/);
|
||||||
|
|||||||
Reference in new issue
Block a user