The website sources are grouped under WebsiteDev/. site-theseus-x moves there for real (the other site folders are still junctions from WebsiteDev to the root); a junction at the old root path keeps existing scripts working and is ignored so the files are tracked once. Docs, the icon script and the mirror example follow the new path. whitepaper/whitepaper.md is the Markdown the published page was built from, so the text can be edited and rebuilt without the original document.
141 lines
5.5 KiB
JavaScript
141 lines
5.5 KiB
JavaScript
// Sirius.X persistent sign-in ("stay signed in on this device").
|
|
//
|
|
// Problem: every visit asked for the seed phrase, the password or the PIN
|
|
// again. A dashboard people come back to daily cannot work like that.
|
|
//
|
|
// Model: after one successful sign-in the wallet's recovery phrase is
|
|
// encrypted with a random AES-GCM key that lives ONLY inside IndexedDB as a
|
|
// non-extractable CryptoKey (the browser can use it, scripts cannot read
|
|
// its bytes). The ciphertext sits in localStorage. On the next visit the
|
|
// dashboard decrypts silently and the user is in — no prompt. Signing out,
|
|
// or turning "stay signed in" off in Settings, destroys both halves.
|
|
//
|
|
// Trust boundary, stated honestly: this is a device session, like a
|
|
// "remember me" cookie. Anyone who can run scripts in this browser profile
|
|
// can use the wallet while the session exists. The password-encrypted
|
|
// wallet blob (bns.wallet.v1) and the optional PIN escrow are unchanged and
|
|
// still the durable secrets. Users who want a prompt before every payment
|
|
// can keep that in Settings ("Ask for PIN before payments").
|
|
//
|
|
// window.siriusSession = { isEnabled, setEnabled, remember, restore, forget,
|
|
// requirePinForPayments, setRequirePinForPayments }
|
|
|
|
(() => {
|
|
const DB = "sirius-session";
|
|
const STORE = "keys";
|
|
const KEY_ID = "device";
|
|
const BLOB = "siriusSessionBlob";
|
|
const ENABLED = "siriusStaySignedIn"; // "0" = off; anything else = on (default on)
|
|
const REQUIRE_PIN = "siriusRequirePin"; // "1" = ask PIN before payments (default off)
|
|
|
|
const enc = new TextEncoder();
|
|
const dec = new TextDecoder();
|
|
const b64 = (bytes) => btoa(String.fromCharCode(...new Uint8Array(bytes)));
|
|
const unb64 = (str) => Uint8Array.from(atob(str), (c) => c.charCodeAt(0));
|
|
|
|
function openDb() {
|
|
return new Promise((resolve, reject) => {
|
|
if (!("indexedDB" in window)) return reject(new Error("IndexedDB unavailable"));
|
|
const req = indexedDB.open(DB, 1);
|
|
req.onupgradeneeded = () => { req.result.createObjectStore(STORE); };
|
|
req.onsuccess = () => resolve(req.result);
|
|
req.onerror = () => reject(req.error || new Error("IndexedDB open failed"));
|
|
});
|
|
}
|
|
function idbGet(db, key) {
|
|
return new Promise((resolve, reject) => {
|
|
const tx = db.transaction(STORE, "readonly");
|
|
const req = tx.objectStore(STORE).get(key);
|
|
req.onsuccess = () => resolve(req.result || null);
|
|
req.onerror = () => reject(req.error);
|
|
});
|
|
}
|
|
function idbPut(db, key, value) {
|
|
return new Promise((resolve, reject) => {
|
|
const tx = db.transaction(STORE, "readwrite");
|
|
tx.objectStore(STORE).put(value, key);
|
|
tx.oncomplete = () => resolve();
|
|
tx.onerror = () => reject(tx.error);
|
|
});
|
|
}
|
|
function idbDel(db, key) {
|
|
return new Promise((resolve, reject) => {
|
|
const tx = db.transaction(STORE, "readwrite");
|
|
tx.objectStore(STORE).delete(key);
|
|
tx.oncomplete = () => resolve();
|
|
tx.onerror = () => reject(tx.error);
|
|
});
|
|
}
|
|
|
|
async function deviceKey(create) {
|
|
const db = await openDb();
|
|
try {
|
|
let key = await idbGet(db, KEY_ID);
|
|
if (!key && create) {
|
|
key = await crypto.subtle.generateKey({ name: "AES-GCM", length: 256 }, false, ["encrypt", "decrypt"]);
|
|
await idbPut(db, KEY_ID, key);
|
|
}
|
|
return key;
|
|
} finally { db.close(); }
|
|
}
|
|
|
|
function isEnabled() {
|
|
try { return localStorage.getItem(ENABLED) !== "0"; } catch { return true; }
|
|
}
|
|
function setEnabled(on) {
|
|
try { localStorage.setItem(ENABLED, on ? "1" : "0"); } catch {}
|
|
if (!on) forget();
|
|
}
|
|
function requirePinForPayments() {
|
|
try { return localStorage.getItem(REQUIRE_PIN) === "1"; } catch { return false; }
|
|
}
|
|
function setRequirePinForPayments(on) {
|
|
try { localStorage.setItem(REQUIRE_PIN, on ? "1" : "0"); } catch {}
|
|
}
|
|
function hasSession() {
|
|
try { return !!localStorage.getItem(BLOB); } catch { return false; }
|
|
}
|
|
|
|
// Encrypt and store the recovery phrase for silent restore. `meta` may
|
|
// carry { accountPath } for wallets derived on a non-default path.
|
|
async function remember(mnemonic, meta = {}) {
|
|
if (!isEnabled() || !mnemonic) return false;
|
|
try {
|
|
const key = await deviceKey(true);
|
|
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
const ct = await crypto.subtle.encrypt({ name: "AES-GCM", iv }, key, enc.encode(mnemonic));
|
|
localStorage.setItem(BLOB, JSON.stringify({ v: 1, iv: b64(iv), ct: b64(ct), at: Date.now(), path: meta.accountPath || null }));
|
|
return true;
|
|
} catch { return false; }
|
|
}
|
|
|
|
// Silent restore. Resolves { mnemonic, accountPath } or null. Never throws.
|
|
async function restore() {
|
|
if (!isEnabled()) return null;
|
|
let raw = null;
|
|
try { raw = localStorage.getItem(BLOB); } catch {}
|
|
if (!raw) return null;
|
|
try {
|
|
const blob = JSON.parse(raw);
|
|
const key = await deviceKey(false);
|
|
if (!key) { forget(); return null; }
|
|
const pt = await crypto.subtle.decrypt({ name: "AES-GCM", iv: unb64(blob.iv) }, key, unb64(blob.ct));
|
|
return { mnemonic: dec.decode(pt), accountPath: blob.path || null };
|
|
} catch {
|
|
// Key rotated / storage cleared / tampered — drop the stale blob.
|
|
forget();
|
|
return null;
|
|
}
|
|
}
|
|
|
|
async function forget() {
|
|
try { localStorage.removeItem(BLOB); } catch {}
|
|
try { const db = await openDb(); try { await idbDel(db, KEY_ID); } finally { db.close(); } } catch {}
|
|
}
|
|
|
|
window.siriusSession = {
|
|
isEnabled, setEnabled, hasSession,
|
|
remember, restore, forget,
|
|
requirePinForPayments, setRequirePinForPayments,
|
|
};
|
|
})();
|