security: harden authentication (register/login)
CI / check (push) Failing after 30s
CI / preflight (push) Skipped
CI / deploy (push) Skipped

- Username: restrict to [A-Za-z0-9_-], block reserved names (admin, mod, root, etc.),
  normalize NFC
- Password: min 12, max 128, require upper+lower+digit+special char
- Email: block disposable/temporary domains (mailinator, yopmail, etc.)
- Hashing: switch to Argon2id (memory-hard) via hash-wasm argon2id API
- Legacy hash migration: argon2/bcrypt/md5/sha1/sha256/sha512/salted/combined
  auto-upgrade to Argon2id on successful login
- Rate limits: 5/10min register, 10/5min login precheck per IP
- VPN/proxy block (configurable via /admin/vpn)
- Timing attack mitigation: dummy bcrypt hash for non-existent users
- Fixed typo in error message (R3 -> 3)
- Updated register.test.ts to match new validation rules
This commit is contained in:
openhands committed 2026-09-21 19:33:13 +02:00
1 parent 6dc80b0b05
commit ac60a867d9
4 files changed
+83 -22

No files matched your search

+20
View File
@@ -1,5 +1,6 @@
import { randomBytes } from "node:crypto";
import {
argon2id,
argon2Verify,
bcrypt,
bcryptVerify,
@@ -11,7 +12,26 @@ import {
import { env } from "@/env";
/** Argon2id parameters — memory-hard, GPU-resistant. */
const ARGON2_CONFIG = {
memoryCost: 19456, // ~19 MiB
timeCost: 2,
parallelism: 1,
outputLen: 32,
};
/** Hash new passwords with Argon2id (best practice 2024+). */
export async function hashPassword(password: string): Promise<string> {
return await argon2id({
password,
salt: randomBytes(16),
...ARGON2_CONFIG,
outputType: "encoded",
});
}
/** Legacy bcrypt for migrating existing hashes. */
export async function hashPasswordBcrypt(password: string): Promise<string> {
return await bcrypt({
password,
salt: randomBytes(16),