security: switch default hashing to Argon2id, fix tests

- hashPassword now uses Argon2id (memory-hard, GPU-resistant) via hash-wasm
- verifyPassword checks both Argon2id and bcrypt
- Legacy hashes (bcrypt, argon2, md5, sha1, sha256, sha512, combined, salted)
  auto-migrate to Argon2id on successful login
- Updated all password tests to expect Argon2id format
- Register validation: min 12 chars, max 128, upper+lower+digit+special required
- Username restricted to [A-Za-z0-9_-], reserved names blocked
- Disposable email domains blocked
- Fixed parameter names for hash-wasm argon2id API (memorySize, iterations, parallelism, hashLength)
This commit is contained in:
openhands committed 2026-09-21 19:48:31 +02:00
1 parent ac60a867d9
commit b13b3a50ff
4 files changed
+92 -38

No files matched your search

+4 -1
View File
@@ -214,7 +214,10 @@ expect(state.insert.mock.calls[0][1]).toMatchObject({
it("rejects passwords without an uppercase letter", async () => { it("rejects passwords without an uppercase letter", async () => {
const result = await register( const result = await register(
PREV, PREV,
buildForm({ password: "secret1234!@", password_confirmation: "secret1234!@" }), buildForm({
password: "secret1234!@",
password_confirmation: "secret1234!@",
}),
); );
expect(result.error).toContain("uppercase"); expect(result.error).toContain("uppercase");
}); });
+64 -14
View File
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+4 -3
View File
@@ -14,10 +14,10 @@ import { env } from "@/env";
/** Argon2id parameters — memory-hard, GPU-resistant. */ /** Argon2id parameters — memory-hard, GPU-resistant. */
const ARGON2_CONFIG = { const ARGON2_CONFIG = {
memoryCost: 19456, // ~19 MiB memorySize: 19456, // ~19 MiB
timeCost: 2, iterations: 2,
parallelism: 1, parallelism: 1,
outputLen: 32, hashLength: 32,
}; };
/** Hash new passwords with Argon2id (best practice 2024+). */ /** Hash new passwords with Argon2id (best practice 2024+). */
@@ -210,6 +210,7 @@ export async function verifyPassword(
password: string, password: string,
stored: string, stored: string,
): Promise<boolean> { ): Promise<boolean> {
if (/^\$argon2/.test(stored)) return isArgon2Of(password, stored);
return isBcryptOf(password, stored); return isBcryptOf(password, stored);
} }