theseus/unlock.html

189 lines
10 KiB
HTML
Raw Normal View History

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Unlock</title>
<style>
:root { color-scheme: light dark;
--surface:#1c222c; --surface2:#0f1621; --line:rgba(255,255,255,.12);
--ink:#e7eaf1; --mut:#8b98a9; --dim:#5e6678; --acid:#d6ff3d; --danger:#f6768a; }
@media (prefers-color-scheme: light) {
:root { --surface:#ffffff; --surface2:#f1f4fa; --line:rgba(0,0,0,.12);
--ink:#1a1f2b; --mut:#5c6577; --dim:#8a93a5; --acid: #0AC18E; }
}
* { box-sizing: border-box; }
html, body { margin: 0; height: 100%; background: transparent; }
body { font: 13px/1.5 system-ui, -apple-system, Segoe UI, Roboto, sans-serif; color: var(--ink); }
.promptmask { position: fixed; inset: 0; background: rgba(0,0,0,.45);
Vault: PIN setup steps, 6-8 digit PINs, save and offer logins, keep sign-ins The PIN could only be six digits and was set from three bare inputs; the unlock prompt sat at the top of the page; and the password manager only filled when you found the key chip, never offered to save, and "Clear cookies on quit" signed you out of every site, including the ones whose login the vault already holds. - PINs are 6 to 8 digits. The PIN record stores its length so pads draw the right number of dots and submit on the last digit; a PIN of the wrong length is refused without a strike, so an older Aegis pad cannot burn the count against an 8-digit PIN. - Settings sets a PIN in steps: master password, choose the PIN on a pad (6/7/8), repeat it, done. The locked vault opens Theseus's own prompt, which is now centred, with the PIN pad or the master password field. - After a sign-in or sign-up form is sent and the page moves on, Theseus offers to save (or update) the login, with an optional "ask for my PIN or password before filling it". Focusing a login form offers the saved logins under it; on a locked vault it offers to unlock first. A failed login (the password field still showing) gets no offer. - "Keep sign-ins for sites in your vault" (on): the quit clear spares the cookies and site storage of sites with a saved login. Their hostnames are kept sealed with the OS keystore so the list is readable at quit while the vault is locked. Verified end to end on a scratch profile: signed in, restarted, still signed in; another site's cookie was cleared.
2026-10-04 20:23:43 +02:00
display: grid; place-items: center; padding: 16px; }
.promptbox { background: var(--surface); border: 1px solid var(--line); border-radius: 12px;
padding: 16px 18px 14px; width: min(380px, calc(100vw - 32px));
box-shadow: 0 20px 60px #000d; animation: pop .12s ease-out; }
Vault: PIN setup steps, 6-8 digit PINs, save and offer logins, keep sign-ins The PIN could only be six digits and was set from three bare inputs; the unlock prompt sat at the top of the page; and the password manager only filled when you found the key chip, never offered to save, and "Clear cookies on quit" signed you out of every site, including the ones whose login the vault already holds. - PINs are 6 to 8 digits. The PIN record stores its length so pads draw the right number of dots and submit on the last digit; a PIN of the wrong length is refused without a strike, so an older Aegis pad cannot burn the count against an 8-digit PIN. - Settings sets a PIN in steps: master password, choose the PIN on a pad (6/7/8), repeat it, done. The locked vault opens Theseus's own prompt, which is now centred, with the PIN pad or the master password field. - After a sign-in or sign-up form is sent and the page moves on, Theseus offers to save (or update) the login, with an optional "ask for my PIN or password before filling it". Focusing a login form offers the saved logins under it; on a locked vault it offers to unlock first. A failed login (the password field still showing) gets no offer. - "Keep sign-ins for sites in your vault" (on): the quit clear spares the cookies and site storage of sites with a saved login. Their hostnames are kept sealed with the OS keystore so the list is readable at quit while the vault is locked. Verified end to end on a scratch profile: signed in, restarted, still signed in; another site's cookie was cleared.
2026-10-04 20:23:43 +02:00
@keyframes pop { from { transform: scale(.97); opacity: 0; } to { transform: none; opacity: 1; } }
.who { display: flex; align-items: center; gap: 8px; color: var(--dim); font-size: 11.5px; margin-bottom: 8px; }
.who .addon { color: var(--mut); }
.title { font-size: 15px; font-weight: 650; margin: 0 0 4px; }
.reason { color: var(--mut); margin: 0 0 14px; }
/* The same PIN pad as Aegis (panel.html .pinpad): one PIN, one screen. */
.pinpad { display: flex; flex-direction: column; align-items: center; gap: 14px; margin: 6px 0 12px; }
.pinpad .pindots { display: flex; gap: 12px; }
.pinpad .pindot { width: 12px; height: 12px; border-radius: 50%; border: 1.5px solid var(--dim);
background: transparent; transition: background .12s, border-color .12s; }
/* No glow, and keys only lighten on hover: a bright acid border lighting
up on whichever key the pointer crosses read as the pad flashing. */
.pinpad .pindot.on { background: var(--acid); border-color: var(--acid); }
.pinpad .pinkeys { display: grid; grid-template-columns: repeat(3, 62px); gap: 8px; }
.pinpad .pinkeys button { height: 46px; border-radius: 10px; border: 1px solid var(--line);
background: var(--surface2); color: var(--ink); font: 500 18px system-ui, sans-serif; cursor: pointer; }
.pinpad .pinkeys button { transition: background-color .08s, transform .06s; }
.pinpad .pinkeys button:hover { background: rgb(from var(--surface2) calc(r + 10) calc(g + 10) calc(b + 12)); }
.pinpad .pinkeys button:active { transform: scale(.96); }
.pinpad .pinkeys button.util { background: transparent; font-size: 13px; color: var(--dim); }
/* Two lines reserved, so a message appearing never changes the dialog's
height (it is centred, so any height change moves the whole pad). */
.pinpad .pinerr { color: var(--danger); font-size: 12px; line-height: 16px; text-align: center; min-height: 32px; max-width: 220px; }
.pinpad .pinkeys.busy { opacity: .55; pointer-events: none; }
.promptbox.still { animation: none; }
input[type=password] { width: 100%; padding: 9px 11px; border-radius: 8px; border: 1px solid var(--line);
background: var(--surface2); color: var(--ink); font: inherit; font-size: 14px; margin-bottom: 10px; }
input:focus, button:focus-visible { outline: 2px solid rgb(from var(--acid) r g b / .6); outline-offset: 1px; }
.err { color: var(--danger); font-size: 12.5px; line-height: 18px; min-height: 36px; margin-bottom: 6px; text-align: center; }
.pact { display: flex; gap: 6px; justify-content: space-between; align-items: center; }
.link { background: none; border: none; color: var(--mut); cursor: pointer; font: inherit; font-size: 12.5px; padding: 0; text-decoration: underline; }
.pbtn { padding: 7px 14px; border-radius: 7px; border: 1px solid var(--line); background: var(--surface2);
color: var(--ink); cursor: pointer; font: inherit; font-size: 12.5px; }
.pbtn.primary { background: var(--acid); color: #0b0e14; border-color: transparent; font-weight: 650; }
.pbtn:disabled { opacity: .5; cursor: default; }
</style>
</head>
<body>
<script>
// Built with createElement/textContent only: the add-on supplies the
// reason text, and it must never be able to inject markup into a prompt
// that asks for the master password.
const el = (tag, attrs = {}, ...kids) => {
const n = document.createElement(tag);
for (const [k, v] of Object.entries(attrs)) {
if (k === "class") n.className = v;
else if (k.startsWith("on")) n.addEventListener(k.slice(2), v);
else n.setAttribute(k, v);
}
for (const c of kids.flat()) if (c != null && c !== false) n.append(c instanceof Node ? c : String(c));
return n;
};
let req = null;
let mode = "pin";
let pin = "";
Vault: PIN setup steps, 6-8 digit PINs, save and offer logins, keep sign-ins The PIN could only be six digits and was set from three bare inputs; the unlock prompt sat at the top of the page; and the password manager only filled when you found the key chip, never offered to save, and "Clear cookies on quit" signed you out of every site, including the ones whose login the vault already holds. - PINs are 6 to 8 digits. The PIN record stores its length so pads draw the right number of dots and submit on the last digit; a PIN of the wrong length is refused without a strike, so an older Aegis pad cannot burn the count against an 8-digit PIN. - Settings sets a PIN in steps: master password, choose the PIN on a pad (6/7/8), repeat it, done. The locked vault opens Theseus's own prompt, which is now centred, with the PIN pad or the master password field. - After a sign-in or sign-up form is sent and the page moves on, Theseus offers to save (or update) the login, with an optional "ask for my PIN or password before filling it". Focusing a login form offers the saved logins under it; on a locked vault it offers to unlock first. A failed login (the password field still showing) gets no offer. - "Keep sign-ins for sites in your vault" (on): the quit clear spares the cookies and site storage of sites with a saved login. Their hostnames are kept sealed with the OS keystore so the list is readable at quit while the vault is locked. Verified end to end on a scratch profile: signed in, restarted, still signed in; another site's cookie was cleared.
2026-10-04 20:23:43 +02:00
let pinLength = 6; // 6-8; the PIN record says which, so the pad submits on the last digit
let busy = false;
let error = "";
function done(cancelled) {
if (!req) return;
const id = req.reqId;
req = null;
ui = null;
document.body.replaceChildren();
if (cancelled) window.unlock.cancel(id);
}
async function submit(value) {
if (!req || busy) return;
busy = true;
paint();
const r = await window.unlock.submit(req.reqId, mode, value).catch((e) => ({ ok: false, error: e.message }));
busy = false;
if (r && r.ok) { req = null; ui = null; document.body.replaceChildren(); return; }
error = (r && r.error) || "Could not unlock";
if (r && r.mode) mode = r.mode;
Vault: PIN setup steps, 6-8 digit PINs, save and offer logins, keep sign-ins The PIN could only be six digits and was set from three bare inputs; the unlock prompt sat at the top of the page; and the password manager only filled when you found the key chip, never offered to save, and "Clear cookies on quit" signed you out of every site, including the ones whose login the vault already holds. - PINs are 6 to 8 digits. The PIN record stores its length so pads draw the right number of dots and submit on the last digit; a PIN of the wrong length is refused without a strike, so an older Aegis pad cannot burn the count against an 8-digit PIN. - Settings sets a PIN in steps: master password, choose the PIN on a pad (6/7/8), repeat it, done. The locked vault opens Theseus's own prompt, which is now centred, with the PIN pad or the master password field. - After a sign-in or sign-up form is sent and the page moves on, Theseus offers to save (or update) the login, with an optional "ask for my PIN or password before filling it". Focusing a login form offers the saved logins under it; on a locked vault it offers to unlock first. A failed login (the password field still showing) gets no offer. - "Keep sign-ins for sites in your vault" (on): the quit clear spares the cookies and site storage of sites with a saved login. Their hostnames are kept sealed with the OS keystore so the list is readable at quit while the vault is locked. Verified end to end on a scratch profile: signed in, restarted, still signed in; another site's cookie was cleared.
2026-10-04 20:23:43 +02:00
if (r && r.pinLength) pinLength = r.pinLength;
pin = "";
paint(); // re-renders only if the answer switched mode or PIN length
}
function press(d) {
if (busy) return;
if (d === "back") pin = pin.slice(0, -1);
else if (d === "clear") pin = "";
Vault: PIN setup steps, 6-8 digit PINs, save and offer logins, keep sign-ins The PIN could only be six digits and was set from three bare inputs; the unlock prompt sat at the top of the page; and the password manager only filled when you found the key chip, never offered to save, and "Clear cookies on quit" signed you out of every site, including the ones whose login the vault already holds. - PINs are 6 to 8 digits. The PIN record stores its length so pads draw the right number of dots and submit on the last digit; a PIN of the wrong length is refused without a strike, so an older Aegis pad cannot burn the count against an 8-digit PIN. - Settings sets a PIN in steps: master password, choose the PIN on a pad (6/7/8), repeat it, done. The locked vault opens Theseus's own prompt, which is now centred, with the PIN pad or the master password field. - After a sign-in or sign-up form is sent and the page moves on, Theseus offers to save (or update) the login, with an optional "ask for my PIN or password before filling it". Focusing a login form offers the saved logins under it; on a locked vault it offers to unlock first. A failed login (the password field still showing) gets no offer. - "Keep sign-ins for sites in your vault" (on): the quit clear spares the cookies and site storage of sites with a saved login. Their hostnames are kept sealed with the OS keystore so the list is readable at quit while the vault is locked. Verified end to end on a scratch profile: signed in, restarted, still signed in; another site's cookie was cleared.
2026-10-04 20:23:43 +02:00
else if (pin.length < pinLength) pin += d;
error = "";
paint();
Vault: PIN setup steps, 6-8 digit PINs, save and offer logins, keep sign-ins The PIN could only be six digits and was set from three bare inputs; the unlock prompt sat at the top of the page; and the password manager only filled when you found the key chip, never offered to save, and "Clear cookies on quit" signed you out of every site, including the ones whose login the vault already holds. - PINs are 6 to 8 digits. The PIN record stores its length so pads draw the right number of dots and submit on the last digit; a PIN of the wrong length is refused without a strike, so an older Aegis pad cannot burn the count against an 8-digit PIN. - Settings sets a PIN in steps: master password, choose the PIN on a pad (6/7/8), repeat it, done. The locked vault opens Theseus's own prompt, which is now centred, with the PIN pad or the master password field. - After a sign-in or sign-up form is sent and the page moves on, Theseus offers to save (or update) the login, with an optional "ask for my PIN or password before filling it". Focusing a login form offers the saved logins under it; on a locked vault it offers to unlock first. A failed login (the password field still showing) gets no offer. - "Keep sign-ins for sites in your vault" (on): the quit clear spares the cookies and site storage of sites with a saved login. Their hostnames are kept sealed with the OS keystore so the list is readable at quit while the vault is locked. Verified end to end on a scratch profile: signed in, restarted, still signed in; another site's cookie was cleared.
2026-10-04 20:23:43 +02:00
if (pin.length === pinLength) submit(pin);
}
// Keypresses and answers update the dialog in place. Rebuilding it on
// every press replayed its pop-in animation and re-centred it whenever the
// message changed height, so the pad jumped under the user's finger.
let ui = null; // { mode, pinLength, dots[], err, go }
function paint() {
if (!req) return;
if (!ui || ui.mode !== mode || ui.pinLength !== pinLength) return render();
if (ui.dots) ui.dots.forEach((d, i) => d.classList.toggle("on", i < pin.length));
ui.err.textContent = error;
if (ui.pad) ui.pad.classList.toggle("busy", busy);
if (ui.go) { ui.go.textContent = busy ? "Checking…" : "Unlock"; ui.go.disabled = busy; }
}
function render() {
if (!req) return;
const replaying = !!ui; // a mode switch inside the same prompt: no second pop-in
ui = { mode, pinLength };
const head = [
Vault: PIN setup steps, 6-8 digit PINs, save and offer logins, keep sign-ins The PIN could only be six digits and was set from three bare inputs; the unlock prompt sat at the top of the page; and the password manager only filled when you found the key chip, never offered to save, and "Clear cookies on quit" signed you out of every site, including the ones whose login the vault already holds. - PINs are 6 to 8 digits. The PIN record stores its length so pads draw the right number of dots and submit on the last digit; a PIN of the wrong length is refused without a strike, so an older Aegis pad cannot burn the count against an 8-digit PIN. - Settings sets a PIN in steps: master password, choose the PIN on a pad (6/7/8), repeat it, done. The locked vault opens Theseus's own prompt, which is now centred, with the PIN pad or the master password field. - After a sign-in or sign-up form is sent and the page moves on, Theseus offers to save (or update) the login, with an optional "ask for my PIN or password before filling it". Focusing a login form offers the saved logins under it; on a locked vault it offers to unlock first. A failed login (the password field still showing) gets no offer. - "Keep sign-ins for sites in your vault" (on): the quit clear spares the cookies and site storage of sites with a saved login. Their hostnames are kept sealed with the OS keystore so the list is readable at quit while the vault is locked. Verified end to end on a scratch profile: signed in, restarted, still signed in; another site's cookie was cleared.
2026-10-04 20:23:43 +02:00
el("div", { class: "who" }, el("span", {}, "🔒"), el("span", { class: "addon" }, req.addonName || "Theseus"), el("span", {}, "·"),
el("span", {}, req.confirm ? "asks you to confirm it's you" : "asks to unlock your vault")),
el("h1", { class: "title" }, mode === "pin" ? "Enter your PIN" : "Enter your master password"),
req.reason ? el("p", { class: "reason" }, req.reason) : null,
];
let bodyEls;
if (mode === "pin") {
Vault: PIN setup steps, 6-8 digit PINs, save and offer logins, keep sign-ins The PIN could only be six digits and was set from three bare inputs; the unlock prompt sat at the top of the page; and the password manager only filled when you found the key chip, never offered to save, and "Clear cookies on quit" signed you out of every site, including the ones whose login the vault already holds. - PINs are 6 to 8 digits. The PIN record stores its length so pads draw the right number of dots and submit on the last digit; a PIN of the wrong length is refused without a strike, so an older Aegis pad cannot burn the count against an 8-digit PIN. - Settings sets a PIN in steps: master password, choose the PIN on a pad (6/7/8), repeat it, done. The locked vault opens Theseus's own prompt, which is now centred, with the PIN pad or the master password field. - After a sign-in or sign-up form is sent and the page moves on, Theseus offers to save (or update) the login, with an optional "ask for my PIN or password before filling it". Focusing a login form offers the saved logins under it; on a locked vault it offers to unlock first. A failed login (the password field still showing) gets no offer. - "Keep sign-ins for sites in your vault" (on): the quit clear spares the cookies and site storage of sites with a saved login. Their hostnames are kept sealed with the OS keystore so the list is readable at quit while the vault is locked. Verified end to end on a scratch profile: signed in, restarted, still signed in; another site's cookie was cleared.
2026-10-04 20:23:43 +02:00
const dots = el("div", { class: "pindots" }, ...Array.from({ length: pinLength }, (_, i) => el("span", { class: i < pin.length ? "pindot on" : "pindot" })));
const keys = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "clear", "0", "back"];
const pad = el("div", { class: "pinkeys" }, ...keys.map((k) =>
el("button", { type: "button", class: k === "back" || k === "clear" ? "util" : "", onclick: () => press(k), "aria-label": k === "back" ? "Delete" : k === "clear" ? "Clear" : k },
k === "back" ? "⌫" : k === "clear" ? "Clear" : k)));
ui.dots = [...dots.children];
ui.pad = pad;
ui.err = el("div", { class: "pinerr", role: "alert" }, error);
bodyEls = [el("div", { class: "pinpad" }, dots, pad, ui.err),
el("div", { class: "pact" },
el("button", { class: "link", type: "button", onclick: () => { mode = "password"; error = ""; pin = ""; render(); } }, "Use master password"),
el("button", { class: "pbtn", type: "button", onclick: () => done(true) }, "Cancel"))];
} else {
const input = el("input", { type: "password", placeholder: "Master password", autocomplete: "current-password" });
const go = el("button", { class: "pbtn primary", type: "submit" }, busy ? "Checking…" : "Unlock");
if (busy) go.setAttribute("disabled", "");
ui.go = go;
ui.err = el("div", { class: "err", role: "alert" }, error);
const form = el("form", { onsubmit: (e) => { e.preventDefault(); if (input.value) submit(input.value); } }, input,
ui.err,
el("div", { class: "pact" },
req.pinSet && !(req.lockedMs > 0) && mode === "password" ? el("button", { class: "link", type: "button", onclick: () => { mode = "pin"; error = ""; pin = ""; render(); } }, "Use PIN") : el("span"),
el("div", { style: "display:flex;gap:6px" },
el("button", { class: "pbtn", type: "button", onclick: () => done(true) }, "Cancel"), go)));
bodyEls = [form];
setTimeout(() => input.focus(), 0);
}
document.body.replaceChildren(el("div", { class: "promptmask", onmousedown: (e) => { if (e.target.classList.contains("promptmask")) done(true); } },
el("div", { class: replaying ? "promptbox still" : "promptbox", role: "dialog", "aria-modal": "true" }, ...head, ...bodyEls)));
}
window.unlock.onShow((r) => {
req = r;
mode = r.pinSet && !(r.lockedMs > 0) ? "pin" : "password";
Vault: PIN setup steps, 6-8 digit PINs, save and offer logins, keep sign-ins The PIN could only be six digits and was set from three bare inputs; the unlock prompt sat at the top of the page; and the password manager only filled when you found the key chip, never offered to save, and "Clear cookies on quit" signed you out of every site, including the ones whose login the vault already holds. - PINs are 6 to 8 digits. The PIN record stores its length so pads draw the right number of dots and submit on the last digit; a PIN of the wrong length is refused without a strike, so an older Aegis pad cannot burn the count against an 8-digit PIN. - Settings sets a PIN in steps: master password, choose the PIN on a pad (6/7/8), repeat it, done. The locked vault opens Theseus's own prompt, which is now centred, with the PIN pad or the master password field. - After a sign-in or sign-up form is sent and the page moves on, Theseus offers to save (or update) the login, with an optional "ask for my PIN or password before filling it". Focusing a login form offers the saved logins under it; on a locked vault it offers to unlock first. A failed login (the password field still showing) gets no offer. - "Keep sign-ins for sites in your vault" (on): the quit clear spares the cookies and site storage of sites with a saved login. Their hostnames are kept sealed with the OS keystore so the list is readable at quit while the vault is locked. Verified end to end on a scratch profile: signed in, restarted, still signed in; another site's cookie was cleared.
2026-10-04 20:23:43 +02:00
pinLength = r.pinLength || 6;
pin = "";
error = r.pinSet && r.lockedMs > 0 ? `Too many failed attempts. Try again in ${Math.max(1, Math.ceil(r.lockedMs / 60000))} min or use the master password.` : "";
busy = false;
ui = null; // a new prompt: build it fresh, with its pop-in
render();
});
document.addEventListener("keydown", (e) => {
if (!req) return;
if (e.key === "Escape") return done(true);
if (mode === "pin" && /^[0-9]$/.test(e.key)) press(e.key);
else if (mode === "pin" && e.key === "Backspace") press("back");
});
</script>
</body>
</html>