69 lines
1.8 KiB
TypeScript
69 lines
1.8 KiB
TypeScript
import * as bcrypt from 'bcrypt';
|
|
|
|
/**
|
|
* 密码值对象
|
|
* 包含密码验证规则和加密逻辑
|
|
*/
|
|
export class Password {
|
|
private static readonly MIN_LENGTH = 8;
|
|
private static readonly MAX_LENGTH = 32;
|
|
private static readonly SALT_ROUNDS = 12;
|
|
|
|
private constructor(public readonly hash: string) {}
|
|
|
|
/**
|
|
* 从明文密码创建(会进行加密)
|
|
*/
|
|
static async create(plainPassword: string): Promise<Password> {
|
|
Password.validatePlain(plainPassword);
|
|
const hash = await bcrypt.hash(plainPassword, Password.SALT_ROUNDS);
|
|
return new Password(hash);
|
|
}
|
|
|
|
/**
|
|
* 从已加密的 hash 重建
|
|
*/
|
|
static fromHash(hash: string): Password {
|
|
return new Password(hash);
|
|
}
|
|
|
|
/**
|
|
* 验证明文密码是否匹配
|
|
*/
|
|
async verify(plainPassword: string): Promise<boolean> {
|
|
return bcrypt.compare(plainPassword, this.hash);
|
|
}
|
|
|
|
/**
|
|
* 验证密码格式
|
|
*/
|
|
private static validatePlain(password: string): void {
|
|
if (password.length < Password.MIN_LENGTH) {
|
|
throw new Error(`密码长度不能少于 ${Password.MIN_LENGTH} 位`);
|
|
}
|
|
if (password.length > Password.MAX_LENGTH) {
|
|
throw new Error(`密码长度不能超过 ${Password.MAX_LENGTH} 位`);
|
|
}
|
|
// 至少包含一个字母和一个数字
|
|
if (!/[a-zA-Z]/.test(password) || !/\d/.test(password)) {
|
|
throw new Error('密码必须包含字母和数字');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 检查密码强度(仅验证格式,不加密)
|
|
*/
|
|
static checkStrength(password: string): { valid: boolean; message?: string } {
|
|
try {
|
|
Password.validatePlain(password);
|
|
return { valid: true };
|
|
} catch (error) {
|
|
return { valid: false, message: (error as Error).message };
|
|
}
|
|
}
|
|
|
|
toString(): string {
|
|
return '[PROTECTED]';
|
|
}
|
|
}
|