refactor: switch password hashing from argon2 to bcrypt
CI / check (push) Successful in 23s
CI / release (push) Skipped
CI / deploy (push) Successful in 1m0s

- hashPassword now emits bcrypt (cost 12) instead of argon2id
- checkLogin migrates legacy md5/argon2id hashes to bcrypt on sign-in
- keep argon2id verification only as a one-time migration path
- replace ARGON2_* env vars with BCRYPT_COST
This commit is contained in:
openhands committed 2026-08-03 18:08:57 +02:00
1 parent 6fc14b9b84
commit 30ed2b8ce2
4 files changed
+42 -53

No files matched your search

+22 -16
View File
@@ -1,16 +1,13 @@
import { randomBytes } from "node:crypto";
import { argon2id, argon2Verify, bcryptVerify, md5 } from "hash-wasm";
import { argon2Verify, bcrypt, bcryptVerify, md5 } from "hash-wasm";
import { env } from "@/env";
export async function hashPassword(password: string): Promise<string> {
return await argon2id({
return await bcrypt({
password,
salt: randomBytes(16),
parallelism: env.ARGON2_PARALLELISM,
iterations: env.ARGON2_ITERATIONS,
memorySize: env.ARGON2_MEMORY_KB,
hashLength: 32,
costFactor: env.BCRYPT_COST,
outputType: "encoded",
});
}
@@ -29,6 +26,11 @@ export async function isMd5Of(
);
}
/**
* Legacy argon2id verification — kept ONLY so accounts hashed before the
* bcrypt switch can still sign in once and be migrated to bcrypt. No new
* argon2 hashes are ever produced.
*/
export async function isArgon2idOf(
password: string,
stored: string,
@@ -41,7 +43,6 @@ export async function isArgon2idOf(
}
}
/** Legacy bcrypt support — only kept to verify & auto-upgrade old accounts. */
export async function isBcryptOf(
password: string,
stored: string,
@@ -58,9 +59,6 @@ export async function verifyPassword(
password: string,
stored: string,
): Promise<boolean> {
if (/^\$argon2id\$/.test(stored)) {
return isArgon2idOf(password, stored);
}
return isBcryptOf(password, stored);
}
@@ -74,14 +72,22 @@ export async function checkLogin(
stored: string,
opts: { convertPasswords: boolean },
): Promise<LoginCheck> {
// Legacy argon2id — verify so existing users can sign in, then immediately
// rehash to bcrypt so the hash format converges on bcrypt.
if (/^\$argon2id\$/.test(stored)) {
if (await isArgon2idOf(password, stored)) {
return { valid: true, upgradedHash: await hashPassword(password) };
}
return { valid: false };
}
if (/^\$2[aby]\$/.test(stored)) {
return { valid: await isBcryptOf(password, stored) };
}
if (opts.convertPasswords && (await isMd5Of(password, stored))) {
return { valid: true, upgradedHash: await hashPassword(password) };
}
if (opts.convertPasswords && (await isArgon2idOf(password, stored))) {
return { valid: true };
}
if (opts.convertPasswords && (await isBcryptOf(password, stored))) {
return { valid: true, upgradedHash: await hashPassword(password) };
}
return { valid: await verifyPassword(password, stored) };
}