Files
publisher-dashboard/apps/api-server/src/utils/password.ts
RevIQ 829d365e80 Implement auth procedures with code review fixes
Add complete auth backend (Workstream D):
- Auth middleware for session/API key authentication
- Signup with password or passkey (WebAuthn)
- Login flow with device trust and email confirmation
- Password reset and email verification
- Session management and logout

Utilities created:
- cookies.ts: Cookie helpers and configuration
- crypto.ts: Token generation and hashing
- password.ts: zxcvbn validation, argon2id hashing
- geo.ts: IP/location extraction from headers
- email.ts: Stubbed email sending
- session.ts: Session creation and device trust

Code review improvements applied:
- Use ORPCError instead of Error in procedures
- Add ast-grep rule to enforce ORPCError usage
- Remove error info leakage (generic messages)
- Optimize N+1 query with JOIN in login-password
- Extract signupWithPassword/signupWithPasskey for testability
- Add 15-minute WebAuthn challenge expiry check
- Strengthen CookieOptions type definitions

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-09 15:19:15 +08:00

68 lines
1.6 KiB
TypeScript

import zxcvbn from "zxcvbn";
export interface PasswordValidationResult {
valid: boolean;
feedback: string[];
score: number;
}
/**
* Validate password strength using zxcvbn
* @param password - The password to validate
* @param userInputs - User-specific inputs to penalize (email, display name)
* @returns Validation result with feedback if invalid
*/
export const validatePassword = (
password: string,
userInputs: string[] = [],
): PasswordValidationResult => {
const result = zxcvbn(password, userInputs);
if (result.score < 3) {
const feedback =
result.feedback.suggestions.length > 0
? result.feedback.suggestions
: [
"Password is too weak. Try a longer phrase or add numbers and symbols.",
];
return {
valid: false,
feedback,
score: result.score,
};
}
return {
valid: true,
feedback: [],
score: result.score,
};
};
/**
* Hash a password using Bun's built-in argon2id
* @param password - The plaintext password to hash
* @returns The hashed password
*/
export const hashPassword = async (password: string): Promise<string> => {
return Bun.password.hash(password, {
algorithm: "argon2id",
memoryCost: 65536, // 64 MiB
timeCost: 3,
});
};
/**
* Verify a password against a stored hash
* @param password - The plaintext password to verify
* @param hash - The stored password hash
* @returns True if the password matches the hash
*/
export const verifyPassword = async (
password: string,
hash: string,
): Promise<boolean> => {
return Bun.password.verify(password, hash);
};