style(frontend): Prettier config + one-time repo-wide format pass

Prettier 3 (pinned), printWidth 100, double quotes — chosen to match the existing
hand-formatting and minimise reflow. This commit is ONLY the mechanical format pass
(`prettier --write`) plus the config/ignore/scripts, isolated so it never pollutes a
feature diff. Verified behaviour-neutral: typecheck, eslint (0 errors), and vitest
all green before and after. See .git-blame-ignore-revs — `git blame` skips this sha.
This commit is contained in:
2026-07-21 02:26:40 +02:00
parent 04ecd8b3a7
commit e257e2aca0
101 changed files with 4388 additions and 2641 deletions
+44 -13
View File
@@ -87,18 +87,22 @@ export async function clearDevice(userId: number): Promise<void> {
// --- key derivation ---
async function wrapKeyFrom(passphrase: string, salt: Uint8Array): Promise<CryptoKey> {
const base = await subtle.importKey("raw", bsrc(enc.encode(passphrase)), "PBKDF2", false, ["deriveKey"]);
const base = await subtle.importKey("raw", bsrc(enc.encode(passphrase)), "PBKDF2", false, [
"deriveKey",
]);
return subtle.deriveKey(
{ name: "PBKDF2", salt: bsrc(salt), iterations: PBKDF2_ITERS, hash: "SHA-256" },
base,
{ name: "AES-GCM", length: 256 },
false,
["encrypt", "decrypt"]
["encrypt", "decrypt"],
);
}
async function importPrivate(pkcs8: ArrayBuffer): Promise<CryptoKey> {
// Non-extractable: usable for deriveBits but can never be exported again.
return subtle.importKey("pkcs8", bsrc(pkcs8), { name: "ECDH", namedCurve: "P-256" }, false, ["deriveBits"]);
return subtle.importKey("pkcs8", bsrc(pkcs8), { name: "ECDH", namedCurve: "P-256" }, false, [
"deriveBits",
]);
}
// Try to restore the unlocked key from this device's storage. Returns true if unlocked.
@@ -134,7 +138,11 @@ export async function setup(userId: number, passphrase: string): Promise<KeySetu
// wrapKey would be GCM nonce-reuse (leaks keystream + enables GHASH forgery). It is no longer a
// verification oracle either — unlock() authenticates the passphrase via the wrapped-key GCM tag.
const checkIv = crypto.getRandomValues(new Uint8Array(12));
const keyCheck = await subtle.encrypt({ name: "AES-GCM", iv: bsrc(checkIv) }, wrapKey, bsrc(enc.encode("ok")));
const keyCheck = await subtle.encrypt(
{ name: "AES-GCM", iv: bsrc(checkIv) },
wrapKey,
bsrc(enc.encode("ok")),
);
const spki = await subtle.exportKey("spki", kp.publicKey);
await installKey(userId, await importPrivate(pkcs8));
@@ -148,7 +156,11 @@ export async function setup(userId: number, passphrase: string): Promise<KeySetu
}
// Unlock on a device from the server blob; throws if the passphrase is wrong.
export async function unlock(userId: number, passphrase: string, bundle: MyKeyBundle): Promise<void> {
export async function unlock(
userId: number,
passphrase: string,
bundle: MyKeyBundle,
): Promise<void> {
if (!bundle.wrapped_private_key || !bundle.salt || !bundle.wrap_iv || !bundle.key_check) {
throw new Error("incomplete key bundle");
}
@@ -158,7 +170,7 @@ export async function unlock(userId: number, passphrase: string, bundle: MyKeyBu
const pkcs8 = await subtle.decrypt(
{ name: "AES-GCM", iv: bsrc(ub64(bundle.wrap_iv)) },
wrapKey,
bsrc(ub64(bundle.wrapped_private_key))
bsrc(ub64(bundle.wrapped_private_key)),
);
await installKey(userId, await importPrivate(pkcs8));
}
@@ -168,15 +180,26 @@ async function convKeyFor(partnerId: number, partnerPubB64: string): Promise<Cry
const cached = convKeys.get(partnerId);
if (cached) return cached;
if (!privKey) throw new Error("locked");
const pub = await subtle.importKey("spki", bsrc(ub64(partnerPubB64)), { name: "ECDH", namedCurve: "P-256" }, false, []);
const pub = await subtle.importKey(
"spki",
bsrc(ub64(partnerPubB64)),
{ name: "ECDH", namedCurve: "P-256" },
false,
[],
);
const bits = await subtle.deriveBits({ name: "ECDH", public: pub }, privKey, 256);
const hk = await subtle.importKey("raw", bits, "HKDF", false, ["deriveKey"]);
const key = await subtle.deriveKey(
{ name: "HKDF", hash: "SHA-256", salt: bsrc(new Uint8Array(0)), info: bsrc(enc.encode(HKDF_INFO)) },
{
name: "HKDF",
hash: "SHA-256",
salt: bsrc(new Uint8Array(0)),
info: bsrc(enc.encode(HKDF_INFO)),
},
hk,
{ name: "AES-GCM", length: 256 },
false,
["encrypt", "decrypt"]
["encrypt", "decrypt"],
);
convKeys.set(partnerId, key);
return key;
@@ -185,11 +208,15 @@ async function convKeyFor(partnerId: number, partnerPubB64: string): Promise<Cry
export async function encryptFor(
partnerId: number,
partnerPubB64: string,
plaintext: string
plaintext: string,
): Promise<{ ciphertext: string; iv: string }> {
const key = await convKeyFor(partnerId, partnerPubB64);
const iv = crypto.getRandomValues(new Uint8Array(12));
const ct = await subtle.encrypt({ name: "AES-GCM", iv: bsrc(iv) }, key, bsrc(enc.encode(plaintext)));
const ct = await subtle.encrypt(
{ name: "AES-GCM", iv: bsrc(iv) },
key,
bsrc(enc.encode(plaintext)),
);
return { ciphertext: b64(ct), iv: b64(iv) };
}
@@ -197,9 +224,13 @@ export async function decryptFrom(
partnerId: number,
partnerPubB64: string,
ciphertext: string,
iv: string
iv: string,
): Promise<string> {
const key = await convKeyFor(partnerId, partnerPubB64);
const pt = await subtle.decrypt({ name: "AES-GCM", iv: bsrc(ub64(iv)) }, key, bsrc(ub64(ciphertext)));
const pt = await subtle.decrypt(
{ name: "AES-GCM", iv: bsrc(ub64(iv)) },
key,
bsrc(ub64(ciphertext)),
);
return dec.decode(pt);
}