Files
BYW-VoiceHub/app/utils/password-policy.ts
T

102 lines
2.8 KiB
TypeScript

const COMMON_PASSWORDS = new Set([
'12345678',
'password',
'password123',
'qwertyui',
'qwerty123',
'admin123',
'admin123456',
'11111111',
'00000000',
'abc12345',
'iloveyou',
'letmein',
'welcome',
'voicehub'
])
export const PASSWORD_MIN_LENGTH = 6
export const PASSWORD_MAX_LENGTH = 128
export const INITIAL_PASSWORD_MIN_CLASSES = 2
export interface PasswordPolicyOptions {
minClasses?: number
complexityCode?: string
}
function getUtf8ByteLength(value: string): number {
return new TextEncoder().encode(value).length
}
const INITIAL_PASSWORD_POLICY_OPTIONS: PasswordPolicyOptions = {
minClasses: INITIAL_PASSWORD_MIN_CLASSES,
complexityCode: 'AUTH_INITIAL_PASSWORD_COMPLEXITY_REQUIRED'
}
export function getPasswordPolicyViolation(password: unknown, options: PasswordPolicyOptions = {}) {
const minClasses = options.minClasses ?? 3
const complexityCode = options.complexityCode ?? 'AUTH_PASSWORD_COMPLEXITY_REQUIRED'
if (typeof password !== 'string' || !password) {
return { code: 'AUTH_NEW_PASSWORD_REQUIRED', message: '密码不能为空' }
}
if (password.length < PASSWORD_MIN_LENGTH) {
return {
code: 'AUTH_PASSWORD_TOO_SHORT',
message: `密码长度至少为${PASSWORD_MIN_LENGTH}位`
}
}
if (password.length > PASSWORD_MAX_LENGTH) {
return {
code: 'AUTH_PASSWORD_TOO_LONG',
message: `密码长度不能超过${PASSWORD_MAX_LENGTH}位`
}
}
// bcrypt 只处理前 72 字节,拒绝超出范围的输入,避免用户以为完整密码参与校验。
if (getUtf8ByteLength(password) > 72) {
return {
code: 'AUTH_PASSWORD_TOO_MANY_BYTES',
message: '密码有效长度不能超过72字节'
}
}
const normalized = password.toLowerCase().replace(/\s+/g, '')
if (COMMON_PASSWORDS.has(normalized)) {
return {
code: 'AUTH_PASSWORD_TOO_COMMON',
message: '该密码过于常见,请更换更安全的密码'
}
}
const categories = [
/[a-z]/.test(password),
/[A-Z]/.test(password),
/[0-9]/.test(password),
/[^A-Za-z0-9]/.test(password)
].filter(Boolean).length
if (categories < minClasses) {
return {
code: complexityCode,
message: `密码至少需要包含大写字母、小写字母、数字、特殊字符中的${minClasses === 2 ? '两' : '三'}类`
}
}
return null
}
export function getInitialPasswordPolicyViolation(password: unknown) {
return getPasswordPolicyViolation(password, INITIAL_PASSWORD_POLICY_OPTIONS)
}
export function validatePasswordPolicy(password: unknown): string | null {
return getPasswordPolicyViolation(password)?.message ?? null
}
export function validateInitialPasswordPolicy(password: unknown): string | null {
return getInitialPasswordPolicyViolation(password)?.message ?? null
}