/** * 登录密码校验工具模块 */ import ModifyPassword from '@af-mobile-client-vue3/views/user/my/comm/ModifyPassword.vue' import { showLoadingToast } from 'vant' import { createApp, h } from 'vue' // ============================================== 正则定义 ============================================== /** 用户名校义 */ export const REG_USER_NAME = /^[\u4E00-\u9FA5\w-]{1,20}$/ /** 手机号正则 */ export const REG_PHONE = /^1((3\d)|(4[014-9])|(5[0-35-9])|(6[2567])|(7[0-8])|(8\d)|(9[0-35-9]))\d{8}$/ /** 通用密码正则(任意字符) */ export const REG_PWD = /^.*$/ /** * 强密码正则 * - 至少包含一个小写字母 [a-z] * - 至少包含一个大写字母 [A-Z] * - 至少包含一个数字 [\d] * - 至少包含一个特殊字符 [!@#$%^&*(),.?":{}|<>~`+=_\\-] * - 密码长度为 12-16 位 */ export const REG_STRONG_PWD = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%^&*(),.?":{}|<>~`+=_\\-])[\w!@#$%^&*(),.?":{}|<>~`+=\\-]{12,16}$/ // ============================================== 辅助校验函数 ============================================== /** * 检查连续数字(3个或以上) * @param password 密码 * @returns 是否包含连续数字 */ export function hasConsecutiveNumbers(password: string): boolean { for (let i = 0; i <= password.length - 3; i++) { const substr = password.substring(i, i + 3) if (/^\d{3,}$/.test(substr)) { const digits = substr.split('').map(Number) let isConsecutive = true for (let j = 1; j < digits.length; j++) { if (digits[j] !== digits[j - 1] + 1) { isConsecutive = false break } } if (isConsecutive) return true } } return false } /** * 检查连续字母(3个或以上) * @param password 密码 * @returns 是否包含连续字母 */ export function hasConsecutiveLetters(password: string): boolean { const lowerPassword = password.toLowerCase() for (let i = 0; i <= lowerPassword.length - 3; i++) { const substr = lowerPassword.substring(i, i + 3) if (/^[a-z]{3,}$/.test(substr)) { let isConsecutive = true for (let j = 1; j < substr.length; j++) { if (substr.charCodeAt(j) !== substr.charCodeAt(j - 1) + 1) { isConsecutive = false break } } if (isConsecutive) return true } } return false } /** * 检查重复字符(3个或以上相同字符) * @param password 密码 * @returns 是否包含重复字符 */ function hasRepeatedChars(password: string): boolean { return /(.)\1{2,}/.test(password) } /** * 检查键盘连续按键(如 qwerty, asdf 等) * @param password 密码 * @returns 是否包含键盘模式 */ function hasKeyboardPattern(password: string): boolean { const keyboardRows: readonly string[] = ['qwertyuiop', 'asdfghjkl', 'zxcvbnm', '1234567890'] const lowerPassword = password.toLowerCase() for (const row of keyboardRows) { for (let i = 0; i <= row.length - 3; i++) { const pattern = row.substring(i, i + 3) if (lowerPassword.includes(pattern)) return true } } return false } // ============================================== 常见弱密码列表 ============================================== const COMMON_PASSWORDS: string[] = [ 'password', 'password123', '123456', '12345678', 'qwerty', 'abc123', 'Password1', 'password1', '123456789', 'welcome', 'admin', 'letmein', 'monkey', 'dragon', 'master', 'superman', 'qwerty123', 'admin123', 'root', 'pass', 'test', 'guest', 'user', '000000', '111111', '666666', '888888', '999999', 'iloveyou', 'welcome123', 'password!', 'Passw0rd!', 'Passw0rd', 'Password!', 'passw0rd', 'P@ssw0rd', 'P@ssword', 'Password@123', ] // ============================================== 密码校验结果类型 ============================================== /** 密码校验结果 */ export interface PasswordValidationResult { isValid: boolean errors: string[] } // ============================================== 统一密码校验 API ============================================== /** * 统一密码校验函数(核心校验逻辑) * 校验规则: * 1. 基本格式:12-16位,包含大小写字母、数字、特殊字符 * 2. 不能有超过3个的连续数字或字母 * 3. 不能有3个或以上相同字符 * 4. 不能包含键盘连续按键模式 * * @param password 密码 * @param commonPasswords 常见弱密码列表(可选,默认使用内置列表) * @returns 校验结果 */ export function validatePassword(password: string, commonPasswords: string[] = COMMON_PASSWORDS): PasswordValidationResult { const result: PasswordValidationResult = { isValid: true, errors: [], } // 1. 基本格式校验 if (!REG_STRONG_PWD.test(password)) { result.isValid = false result.errors.push('密码必须包含大小写字母、数字、特殊字符,且长度为12-16位') } // 2. 检查是否为常见密码 // if (commonPasswords.includes(password) || commonPasswords.includes(password.toLowerCase())) { // result.isValid = false // result.errors.push('不能使用常见密码') // } // 3. 检查连续数字或字母(3个或以上) if (hasConsecutiveNumbers(password) || hasConsecutiveLetters(password)) { result.isValid = false result.errors.push('密码中不能有超过3个的连续数字或字母') } // 4. 检查重复字符 if (hasRepeatedChars(password)) { result.isValid = false result.errors.push('不能包含3个或以上相同字符') } // 5. 检查键盘模式 if (hasKeyboardPattern(password)) { result.isValid = false result.errors.push('不能包含键盘连续按键模式') } return result } /** * 快速密码校验(仅校验格式,不校验常见密码和模式) * @param password 密码 * @returns 错误信息数组,空数组表示格式正确 */ export function validatePasswordFormat(password: string): string[] { if (REG_STRONG_PWD.test(password)) return [] const errors: string[] = [] if (!/.{12,16}/.test(password)) errors.push('密码长度必须为12-16位') if (!/[A-Z]/.test(password)) errors.push('密码中至少包含一个大写字母') if (!/[a-z]/.test(password)) errors.push('密码中至少包含一个小写字母') if (!/\d/.test(password)) errors.push('密码中至少包含一个数字') if (!/[!@#$%^&*(),.?":{}|<>~`+=_\\-]/.test(password)) errors.push('密码中至少包含一个特殊字符') if (hasConsecutiveNumbers(password) || hasConsecutiveLetters(password)) errors.push('密码中不能有超过3个的连续数字或字母') return errors } /** * 获取密码强度描述 * @param password 密码 * @returns 强度等级描述 */ export function getPasswordStrengthDesc(password: string): string { if (!password) return '请输入密码' const result = validatePassword(password) if (result.isValid) return '密码强度:强' // 计算缺少的条件数量 const missingCount = result.errors.length if (missingCount === 1) return '密码强度:中等' if (missingCount === 2) return '密码强度:弱' return '密码强度:非常弱' } // ============================================== 修改密码弹窗相关 ============================================== /** * 打开修改密码弹窗 * @param ename 用户名 * @param password 当前密码(可选,传入后旧密码字段自动填充且不显示) * @returns Promise 用户是否确认修改密码(true: 修改成功,false: 取消修改) */ export function openModifyPasswordModal(ename: string, password?: string): Promise { return new Promise((resolve) => { const app = createApp({ render() { return h(ModifyPassword, { 'visible': true, ename, password, 'onUpdate:visible': (visible: boolean) => { if (!visible) { resolve(false) app.unmount() } }, }) }, }) const div = document.createElement('div') document.body.appendChild(div) app.mount(div) }) } // ============================================== 登录密码校验流程 ============================================== /** * 检查密码是否需要修改(弱密码检测) * @param password 当前密码 * @param data 登录返回的完整数据(用于判断是否强制修改密码 m_c_pwd === 1) * @returns 是否需要修改密码 */ export async function checkPasswordNeedModify(password: string, data?: any): Promise { // 强制修改密码 if (data?.resources?.m_c_pwd === 1) return true const { useSettingStore } = await import('@af-mobile-client-vue3/stores/modules/setting') const settingStore = useSettingStore() const config = settingStore.getSetting() // 未开启强密码:直接不需要修改 if (!config?.strongPwd) return false // 强度校验 const passwordValidation = validatePassword(password, COMMON_PASSWORDS) if (passwordValidation.isValid) return false const err = passwordValidation.errors.join('\n') || '密码过于简单请修改密码后进行重新登陆' showLoadingToast({ zIndex: 2010, message: err, type: 'fail', }) return true } /** * 处理登录后的密码校验流程 * @param ename 用户名 * @param password 当前密码 * @param data 登录返回的完整数据(用于判断是否强制修改密码 m_c_pwd === 1) * @returns Promise 是否通过密码校验(true: 密码是强密码或修改成功,false: 取消修改) */ export async function handleLoginPasswordCheck(ename: string, password: string, data?: any): Promise { if (await checkPasswordNeedModify(password, data)) return await openModifyPasswordModal(ename, password) // 密码是强密码,直接返回true return true }