Closes the two gaps 010-identity-auth explicitly deferred (password reset, login rate-limiting), plus a shared password-strength validator both the reset-consume endpoint and admin account creation now depend on. - Password reset: single-use, paired-Redis-key tokens (never in Postgres), identical response regardless of account existence, stubbed delivery via a structured log line (no email infrastructure exists yet). - Password strength: one validatePasswordStrength() call site, wired into both POST /admin/users and the reset-consume flow. - Login rate-limiting: checkRateLimit keyed by submitted email, checked before any credential verification. Also fixes tests/helpers/auth.ts's shared loginAs() helper, which reused two fixed accounts across the whole integration suite via upsert — now rate-limited per email, that collided across ~30 files sharing one budget. Each call now gets a unique email; no call sites needed to change. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
19 lines
688 B
TypeScript
19 lines
688 B
TypeScript
import { randomBytes, createHash } from 'crypto';
|
|
|
|
export interface GeneratedResetToken {
|
|
token: string;
|
|
tokenHash: string;
|
|
}
|
|
|
|
/** 013-auth-hardening: the raw token is what gets "delivered" (logged, per the stub decision,
|
|
* research.md); only its SHA-256 hash is ever persisted (data-model.md) — mirrors this
|
|
* codebase's own password-hashing discipline, never storing a usable secret at rest. */
|
|
export function generateResetToken(): GeneratedResetToken {
|
|
const token = randomBytes(32).toString('hex');
|
|
return { token, tokenHash: hashResetToken(token) };
|
|
}
|
|
|
|
export function hashResetToken(token: string): string {
|
|
return createHash('sha256').update(token).digest('hex');
|
|
}
|