aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-08-07 22:59:05 -0700
committerAdam Soltys <[email protected]>2026-08-07 23:29:24 -0700
commit9f99bbdb765fc70373943930d543ce1cde7a5f35 (patch)
tree9b36328df6cc860d8288e4c839a476148b472a51
parent01784219f83f68c7d03eb19c35f05eeaedc74487 (diff)
downloadcoinos-server-9f99bbdb765fc70373943930d543ce1cde7a5f35.tar.gz
coinos-server-9f99bbdb765fc70373943930d543ce1cde7a5f35.zip
auth: hash passwords at bcrypt cost 12 with transparent upgrade (S2)
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 <[email protected]>
-rw-r--r--lib/register.ts2
-rw-r--r--routes/users.ts19
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);