feat(auth): auto-upgrade every legacy password format to bcrypt on login
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:
1 parent
905573e627
commit
e153300da0
5 files changed
+326
-47
No files matched your search
+3
-2
@@ -36,8 +36,9 @@ BADGE_URL=/swf/c_images/album1584
|
|||||||
# --- SECURITY & HASHING ---
|
# --- SECURITY & HASHING ---
|
||||||
AUTH_SECRET=your-super-secret-auth-key-change-this-min-32-chars
|
AUTH_SECRET=your-super-secret-auth-key-change-this-min-32-chars
|
||||||
APP_KEY=base64:your-app-key-here=
|
APP_KEY=base64:your-app-key-here=
|
||||||
CONVERT_PASSWORDS=true
|
# Deprecated: legacy md5/argon2id hashes are ALWAYS upgraded to bcrypt on
|
||||||
# CONVERT_PASSWORDS: enables legacy md5/argon2id -> bcrypt upgrade on login.
|
# login now. Kept only for config compatibility with existing deploys.
|
||||||
|
# CONVERT_PASSWORDS=true
|
||||||
BCRYPT_COST=12
|
BCRYPT_COST=12
|
||||||
|
|
||||||
# --- PATHS ---
|
# --- PATHS ---
|
||||||
|
|||||||
+2
-2
@@ -69,8 +69,8 @@ const schema = z
|
|||||||
// Laravel APP_KEY (base64:...) — needed to read existing 2FA secrets.
|
// Laravel APP_KEY (base64:...) — needed to read existing 2FA secrets.
|
||||||
APP_KEY: z.string().optional(),
|
APP_KEY: z.string().optional(),
|
||||||
|
|
||||||
// Mirrors Laravel config('habbo.site.convert_passwords') — enables
|
// Deprecated: legacy md5/argon2id hashes are ALWAYS upgraded to bcrypt
|
||||||
// legacy md5/argon2id hashes to be upgraded to bcrypt on login.
|
// on login now (no flag required). Kept for config compatibility.
|
||||||
CONVERT_PASSWORDS: z
|
CONVERT_PASSWORDS: z
|
||||||
.string()
|
.string()
|
||||||
.optional()
|
.optional()
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { sql } from "drizzle-orm";
|
import { sql } from "drizzle-orm";
|
||||||
import { env } from "@/env";
|
|
||||||
import { checkLogin } from "@/lib/auth/password";
|
import { checkLogin } from "@/lib/auth/password";
|
||||||
import { cachedQuery, invalidateKey } from "@/lib/cached-db";
|
import { cachedQuery, invalidateKey } from "@/lib/cached-db";
|
||||||
import { db } from "@/lib/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. */
|
/** Runs a dummy hash check so missing-user responses stay timing-constant. */
|
||||||
export async function runDummyHashCheck(password: string): Promise<void> {
|
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. */
|
/** Verifies the password against the stored hash and reports a possible upgrade. */
|
||||||
@@ -107,9 +106,7 @@ export async function verifyLoginPassword(
|
|||||||
password: string,
|
password: string,
|
||||||
): Promise<{ valid: boolean; upgradedHash?: string }> {
|
): Promise<{ valid: boolean; upgradedHash?: string }> {
|
||||||
if (!user.password) return { valid: false };
|
if (!user.password) return { valid: false };
|
||||||
return checkLogin(password, user.password, {
|
return checkLogin(password, user.password);
|
||||||
convertPasswords: env.CONVERT_PASSWORDS,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** True when email verification is required but this account hasn't verified yet. */
|
/** True when email verification is required but this account hasn't verified yet. */
|
||||||
|
|||||||
+180
-20
File diff suppressed because it is too large.
Load diff
+139
-18
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user