feat(auth): auto-upgrade every legacy password format to bcrypt on login
CI / check (push) Failing after 25s
CI / preflight (push) Skipped
CI / deploy (push) Skipped

checkLogin now verifies and migrates all known password formats without
configuration: bcrypt, argon2id/argon2i/argon2d, unsalted md5/sha1/sha256/
sha512, double-md5 (UberCMS/Butterfly), salted md5 with embedded salt
(hash:salt, salt:hash, hash$salt), and a guarded plaintext fallback.

Every successful legacy login rewrites the stored hash to bcrypt, so the
CONVERT_PASSWORDS flag is no longer required (kept for deploy compatibility).
This commit is contained in:
openhands committed 2026-09-17 14:56:31 +02:00
1 parent 905573e627
commit e153300da0
5 files changed
+326 -47

No files matched your search

+2 -5
View File
@@ -1,5 +1,4 @@
import { sql } from "drizzle-orm";
import { env } from "@/env";
import { checkLogin } from "@/lib/auth/password";
import { cachedQuery, invalidateKey } from "@/lib/cached-db";
import { db } from "@/lib/db";
@@ -98,7 +97,7 @@ export async function invalidateLoginCache(username: string): Promise<void> {
/** Runs a dummy hash check so missing-user responses stay timing-constant. */
export async function runDummyHashCheck(password: string): Promise<void> {
await checkLogin(password, DUMMY_BCRYPT_HASH, { convertPasswords: false });
await checkLogin(password, DUMMY_BCRYPT_HASH);
}
/** Verifies the password against the stored hash and reports a possible upgrade. */
@@ -107,9 +106,7 @@ export async function verifyLoginPassword(
password: string,
): Promise<{ valid: boolean; upgradedHash?: string }> {
if (!user.password) return { valid: false };
return checkLogin(password, user.password, {
convertPasswords: env.CONVERT_PASSWORDS,
});
return checkLogin(password, user.password);
}
/** True when email verification is required but this account hasn't verified yet. */