From 9f99bbdb765fc70373943930d543ce1cde7a5f35 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Fri, 7 Aug 2026 22:59:05 -0700 Subject: auth: hash passwords at bcrypt cost 12 with transparent upgrade (S2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Registration, password change, and admin reset all hashed at bcrypt cost 4 (16 rounds, ~1,300 guesses/s/thread) — the single factor gating the custodial balance and the nip49 passphrase for the stored nostr key. Raise all three sites to cost 12, and on successful login re-hash any legacy cost-4 hash now that we hold the verified plaintext. Co-Authored-By: Claude Opus 4.8 --- lib/register.ts | 2 +- routes/users.ts | 19 +++++++++++++++++-- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/lib/register.ts b/lib/register.ts index bd1049c8..16aa5b02 100644 --- a/lib/register.ts +++ b/lib/register.ts @@ -32,7 +32,7 @@ export default async (user, ip) => { if (password) { user.password = await Bun.password.hash(password, { algorithm: "bcrypt", - cost: 4, + cost: 12, }); } diff --git a/routes/users.ts b/routes/users.ts index f10b728b..63699777 100644 --- a/routes/users.ts +++ b/routes/users.ts @@ -350,7 +350,7 @@ export default { if (password && password === confirm) { user.password = await Bun.password.hash(password, { algorithm: "bcrypt", - cost: 4, + cost: 12, }); } @@ -427,6 +427,21 @@ export default { if ((await db.ttl(ipFailKey)) < 0) await db.expire(ipFailKey, 600); return res.code(401).send("2fa required"); } + + // Transparent bcrypt upgrade: legacy accounts were hashed at cost 4 + // (~1,300 guesses/s). Now that we hold the verified plaintext, re-hash + // at the current cost so the stored hash strengthens on next login. + const cost = Number.parseInt( + user.password.match(/^\$2[aby]\$(\d{2})\$/)?.[1] ?? "0", + 10, + ); + if (cost < 12) { + user.password = await Bun.password.hash(password, { + algorithm: "bcrypt", + cost: 12, + }); + await s(`user:${user.id}`, user); + } } if (username !== "coinos") @@ -787,7 +802,7 @@ export default { user.password = await Bun.password.hash(password, { algorithm: "bcrypt", - cost: 4, + cost: 12, }); await s(`user:${id}`, user); -- cgit v1.2.3