You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
55 lines
1.5 KiB
55 lines
1.5 KiB
import bcrypt from "bcryptjs";
|
|
|
|
const SALT_ROUNDS = 12;
|
|
const PASSWORD_HISTORY_SIZE = 5;
|
|
|
|
export function hashPassword(password: string): Promise<string> {
|
|
return bcrypt.hash(password, SALT_ROUNDS);
|
|
}
|
|
|
|
export async function verifyPassword(
|
|
password: string,
|
|
hashedPassword: string
|
|
): Promise<boolean> {
|
|
return bcrypt.compare(password, hashedPassword);
|
|
}
|
|
|
|
export async function isPasswordInHistory(
|
|
password: string,
|
|
history: string[]
|
|
): Promise<boolean> {
|
|
if (history.length === 0) return false;
|
|
for (const h of history) {
|
|
if (await bcrypt.compare(password, h)) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
export function addPasswordToHistory(
|
|
newHash: string,
|
|
history: string[]
|
|
): string[] {
|
|
const updated = [newHash, ...history];
|
|
return updated.slice(0, PASSWORD_HISTORY_SIZE);
|
|
}
|
|
|
|
export interface PasswordStrengthResult {
|
|
valid: boolean;
|
|
errors: string[];
|
|
}
|
|
|
|
export function validatePasswordStrength(password: string): PasswordStrengthResult {
|
|
const errors: string[] = [];
|
|
if (password.length < 8) errors.push("至少8个字符");
|
|
if (!/[A-Z]/.test(password)) errors.push("需包含大写字母");
|
|
if (!/[a-z]/.test(password)) errors.push("需包含小写字母");
|
|
if (!/[0-9]/.test(password)) errors.push("需包含数字");
|
|
if (!/[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]/.test(password))
|
|
errors.push("需包含特殊字符");
|
|
return { valid: errors.length === 0, errors };
|
|
}
|
|
|
|
export function isLocked(lockoutUntil: Date | null): boolean {
|
|
if (!lockoutUntil) return false;
|
|
return Date.now() < lockoutUntil.getTime();
|
|
}
|