import argon2 from "argon2"; import { createHash, randomBytes, timingSafeEqual } from "node:crypto"; const ARGON_OPTIONS = { type: argon2.argon2id, memoryCost: 65_536, timeCost: 3, parallelism: 1, hashLength: 32, } as const; export function normalizeUsername(username: string): string { return username.normalize("NFKC").trim().toLocaleLowerCase("und"); } export function validateNewPassword(password: string): string | null { const length = [...password].length; if (length < 12 || length > 128 || Buffer.byteLength(password, "utf8") > 512) { return "密码长度需要为 12–128 个字符"; } return null; } export function hashPassword(password: string): Promise { return argon2.hash(password, ARGON_OPTIONS); } export async function verifyPassword(hash: string, password: string): Promise { try { return await argon2.verify(hash, password); } catch { return false; } } export function randomToken(bytes = 32): string { return randomBytes(bytes).toString("base64url"); } export function sha256(value: string | Buffer): string { return createHash("sha256").update(value).digest("hex"); } export function constantTimeEqual(left: string, right: string): boolean { const leftBuffer = Buffer.from(left); const rightBuffer = Buffer.from(right); return leftBuffer.length === rightBuffer.length && timingSafeEqual(leftBuffer, rightBuffer); } export function temporaryPassword(): string { return `${randomToken(15)}A7!`; }