diff --git a/README.md b/README.md index 9d861929..98286ea9 100644 --- a/README.md +++ b/README.md @@ -62,13 +62,14 @@ formatMoney('1000'); // => 1,000.00 - [isVehicle](https://doly-dev.github.io/util-helpers/module-Validator.html#.isVehicle) - 车牌号 - [isBankCard](https://doly-dev.github.io/util-helpers/module-Validator.html#.isBankCard) - 银行卡 - [isSocialCreditCode](https://doly-dev.github.io/util-helpers/module-Validator.html#.isSocialCreditCode) - 统一社会信用代码,也叫三证合一组织代码 - - [isPassword](https://doly-dev.github.io/util-helpers/module-Validator.html#.isPassword) 密码强度 + - [isPassword](https://doly-dev.github.io/util-helpers/module-Validator.html#.isPassword) 密码强度(即将废弃,请使用[validatePassword](https://doly-dev.github.io/util-helpers/module-Validator.html#.validatePassword)) - [isPassport](https://doly-dev.github.io/util-helpers/module-Validator.html#.isPassport) - 护照号 - [isChinese](https://doly-dev.github.io/util-helpers/module-Validator.html#.isChinese) - 中文 - [isIPv4](https://doly-dev.github.io/util-helpers/module-Validator.html#.isIPv4) - IPv4 - [isIPv6](https://doly-dev.github.io/util-helpers/module-Validator.html#.isIPv6) - IPv6 - [isUrl](https://doly-dev.github.io/util-helpers/module-Validator.html#.isUrl) - URL - [isBusinessLicense](https://doly-dev.github.io/util-helpers/module-Validator.html#.isBusinessLicense) - 营业执照,也叫工商注册号 + - [validatePassword](https://doly-dev.github.io/util-helpers/module-Validator.html#.validatePassword) - 验证密码 - 调试相关 - setDisableWarning - 禁止警告提示 diff --git a/src/index.js b/src/index.js index 59953577..a5b551ca 100644 --- a/src/index.js +++ b/src/index.js @@ -21,6 +21,7 @@ export { default as isIPv4 } from './isIPv4'; export { default as isIPv6 } from './isIPv6'; export { default as isUrl } from './isUrl'; export { default as isBusinessLicense } from './isBusinessLicense'; +export { default as validatePassword } from './validatePassword'; /** * 数据处理 diff --git a/src/isPassword.js b/src/isPassword.js index ed849f4e..585cf4b1 100644 --- a/src/isPassword.js +++ b/src/isPassword.js @@ -82,6 +82,7 @@ function hasDisabled(val, chars = '') { * @static * @alias module:Validator.isPassword * @since 1.1.0 + * @deprecated 将在下次大版本更新后废弃,请使用 validatePassword * @param {string} value 要检测的值 * @param {object} [options] 配置项 * @param {number} [options.level=2] 密码强度 1-包含一种字符 2-包含两种字符 3-包含三种字符。(大写字母、小写字母、数字、特殊字符) diff --git a/src/utils/config.js b/src/utils/config.js index 6f4b1be8..e3542d59 100644 --- a/src/utils/config.js +++ b/src/utils/config.js @@ -1,9 +1,15 @@ const config = { - // 禁用警告 - disableWarning: false + // 禁用warning提示 + disableWarning: true } -// 设置禁止警告提示 +/** + * 设置禁止warning提示 + * @static + * @alias module:Debug.formatBankCard + * @since 3.6.1 + * @param {boolean} bool 是否禁止warning提示 + */ function setDisableWarning(bool) { config.disableWarning(!!bool); } diff --git a/src/validatePassword.js b/src/validatePassword.js new file mode 100644 index 00000000..f7e460c7 --- /dev/null +++ b/src/validatePassword.js @@ -0,0 +1,209 @@ +import { config } from './utils/config'; + +const regNumber = /[\d]/; +const regLowerCaseLetter = /[a-z]/; +const regUpperCaseLetter = /[A-Z]/; +const regAllNumberAndLetter = /[\d|a-z]/ig; + +// 是否包含数字 +function hasNumber(val) { + return regNumber.test(val); +} + +// 是否包含小写字母 +function hasLowerCaseLetter(val) { + return regLowerCaseLetter.test(val); +} + +// 是否包含大写字母 +function hasUpperCaseLetter(val) { + return regUpperCaseLetter.test(val); +} + +// 是否为十六进制 +function hasHex(val) { + return val.indexOf('\\x') > -1 || val.indexOf('\\u') > -1; +} + +// 是否包含特殊字符 +function hasSpecialCharacter(val, chars = '') { + if (!chars) { + return false; + } + + const specialChars = val.replace(regAllNumberAndLetter, ''); + const regChars = hasHex(chars) ? new RegExp(`[${chars}]`) : null; + + if (regChars) { + return regChars.test(specialChars); + } + + let ret = false; + specialChars.split('').some((charItem) => { + if (chars.indexOf(charItem) > -1) { + ret = true; + } + return ret; + }); + return ret; +} + +// 是否包含非法字符 +function hasUnallowableCharacter(val, chars = '') { + const specialChars = val.replace(regAllNumberAndLetter, ''); + + if (!chars && specialChars) { + return true; + } + + const regChars = hasHex(chars) ? new RegExp(`[^${chars}]`) : null; + if (regChars) { + return regChars.test(specialChars); + } + let ret = false; + specialChars.split('').some((charItem) => { + if (chars.indexOf(charItem) === -1) { + ret = true; + } + return ret; + }); + return ret; +} + +/** + * 验证密码(数字、大小写字母、特殊字符、非法字符) + * + * @see {@link https://baike.baidu.com/item/ASCII#3|ASCII} + * @static + * @alias module:Validator.validatePassword + * @since 3.7.0 + * @param {string} value 要检测的值 + * @param {object} [options] 配置项 + * @param {number} [options.level=2] 密码强度 1-包含一种字符 2-包含两种字符 3-包含三种字符。(大写字母、小写字母、数字、特殊字符) + * @param {boolean} [options.ignoreCase=false] 是否忽略大小写,为 ture 时,大小写字母视为一种字符 + * @param {string} [options.special=!@#$%^&*()-=_+[]\|{},./?<>~] 支持的特殊字符 + * @returns {object} 验证结果 + * @example + * + * validatePassword('a12345678'); + * // => + * { + * validated: true, // 验证结果,根据密码强度、是否包含非法字符得出 + * level: 2, // 强度级别 + * containes: { + * number: true, // 包含数字 + * lowerCaseLetter: true, // 包含小写字母 + * upperCaseLetter: false, // 包含大写字母 + * specialCharacter: false, // 包含特殊字符 + * unallowableCharacter: false // 包含非法字符 + * } + * } + * + * validatePassword('a12345678', {level: 3}); + * // => + * { + * validated: false, + * level: 2, + * containes: { + * number: true, + * lowerCaseLetter: true, + * upperCaseLetter: false, + * specialCharacter: false, + * unallowableCharacter: false + * } + * } + * + * validatePassword('_Aa一二三45678', {level: 3, ignoreCase: true}); + * // => + * { + * validated: false, + * level: 3, + * containes: { + * number: true, + * lowerCaseLetter: true, + * upperCaseLetter: true, + * specialCharacter: true, + * unallowableCharacter: true + * } + * } + * + * // 自定义特殊字符 + * validatePassword('_Aa一二三45678', {level: 3, ignoreCase: true, special: '_一二三'}); + * // => + * { + * validated: true, + * level: 3, + * containes: { + * number: true, + * lowerCaseLetter: true, + * upperCaseLetter: true, + * specialCharacter: true, + * unallowableCharacter: false + * } + * } + */ +function validatePassword(value, { + level = 2, + ignoreCase = false, + special = "\\x21-\\x2F\\x3A-\\x40\\x5B-\\x60\\x7B-\\x7E" +} = {}) { + let valStr = value; + + if (typeof value !== 'string') { + if (!config.disableWarning) { + console.warn(`[validatePassword] value must be a string.`); + } + valStr = ''; + } + + let currentLevel = 0; + + // 包含数字 + const containesNumber = hasNumber(valStr); + // 包含小写字母 + const containesLowerCaseLetter = hasLowerCaseLetter(valStr); + // 包含大写字母 + const containesUpperCaseLetter = hasUpperCaseLetter(valStr); + // 包含特殊字符 + const containesSpecialCharacter = hasSpecialCharacter(valStr, special); + // 包含非法字符 + const containesUnallowableCharacter = hasUnallowableCharacter(valStr, special); + + if (containesNumber) { + currentLevel += 1; + } + + if (ignoreCase) { // 不区分大小写 + if (containesLowerCaseLetter || containesUpperCaseLetter) { + currentLevel += 1; + } + } else { // 区分大小写 + if (containesLowerCaseLetter) { + currentLevel += 1; + } + if (containesUpperCaseLetter) { + currentLevel += 1; + } + } + + if (containesSpecialCharacter) { + currentLevel += 1; + } + + // 验证结果 + const validated = currentLevel >= level && !containesUnallowableCharacter; + + return { + validated, + level: currentLevel, + containes: { + number: containesNumber, + lowerCaseLetter: containesLowerCaseLetter, + upperCaseLetter: containesUpperCaseLetter, + specialCharacter: containesSpecialCharacter, + unallowableCharacter: containesUnallowableCharacter + } + } +} + +export default validatePassword; \ No newline at end of file diff --git a/test/validator/validatePassword.test.js b/test/validator/validatePassword.test.js new file mode 100644 index 00000000..d5627c36 --- /dev/null +++ b/test/validator/validatePassword.test.js @@ -0,0 +1,310 @@ +import { + expect +} from 'chai'; + +import validatePassword from '../../src/validatePassword'; + +function checkEqual(value, opts, equalObj) { + const result = validatePassword(value, opts); + // console.log(result); + const { validated, level, containes } = equalObj; + const { number, lowerCaseLetter, upperCaseLetter, specialCharacter, unallowableCharacter } = containes; + + expect(result.validated).to.be.equal(validated); + expect(result.level).to.be.equal(level); + expect(result.containes.number).to.be.equal(number); + expect(result.containes.lowerCaseLetter).to.be.equal(lowerCaseLetter); + expect(result.containes.upperCaseLetter).to.be.equal(upperCaseLetter); + expect(result.containes.specialCharacter).to.be.equal(specialCharacter); + expect(result.containes.unallowableCharacter).to.be.equal(unallowableCharacter); +} + +describe('validatePassword', () => { + it('非字符串', () => { + checkEqual(true, {}, { + validated: false, + level: 0, + containes: { + number: false, + lowerCaseLetter: false, + upperCaseLetter: false, + specialCharacter: false, + unallowableCharacter: false + } + }); + checkEqual(1234, {}, { + validated: false, + level: 0, + containes: { + number: false, + lowerCaseLetter: false, + upperCaseLetter: false, + specialCharacter: false, + unallowableCharacter: false + } + }); + }); + it('1级强度 "1234787"', () => { + checkEqual('1234787', { level: 1 }, { + validated: true, + level: 1, + containes: { + number: true, + lowerCaseLetter: false, + upperCaseLetter: false, + specialCharacter: false, + unallowableCharacter: false + } + }); + }); + it('1级强度 "a1234787"', () => { + checkEqual('a1234787', { level: 1 }, { + validated: true, + level: 2, + containes: { + number: true, + lowerCaseLetter: true, + upperCaseLetter: false, + specialCharacter: false, + unallowableCharacter: false + } + }); + }); + it('1级强度 "ab1234787"', () => { + checkEqual('ab1234787', { level: 1 }, { + validated: true, + level: 2, + containes: { + number: true, + lowerCaseLetter: true, + upperCaseLetter: false, + specialCharacter: false, + unallowableCharacter: false + } + }); + }); + it('1级强度 "1a234787"', () => { + checkEqual('1a234787', { level: 1 }, { + validated: true, + level: 2, + containes: { + number: true, + lowerCaseLetter: true, + upperCaseLetter: false, + specialCharacter: false, + unallowableCharacter: false + } + }); + }); + it('1级强度 "a1_234787"', () => { + checkEqual('a1_234787', { level: 1 }, { + validated: true, + level: 3, + containes: { + number: true, + lowerCaseLetter: true, + upperCaseLetter: false, + specialCharacter: true, + unallowableCharacter: false + } + }); + }); + it('1级强度 "abc"', () => { + checkEqual('abc', { level: 1 }, { + validated: true, + level: 1, + containes: { + number: false, + lowerCaseLetter: true, + upperCaseLetter: false, + specialCharacter: false, + unallowableCharacter: false + } + }); + }); + it('1级强度 "*_ )"', () => { + checkEqual('*_ )', { level: 1 }, { + validated: false, + level: 1, + containes: { + number: false, + lowerCaseLetter: false, + upperCaseLetter: false, + specialCharacter: true, + unallowableCharacter: true + } + }); + }); + it('1级强度 "!@#$%^&*()-=_+[]\|{},./?<>~`"', () => { + checkEqual('!@#$%^&*()-=_+[]\|{},./?<>~`', { level: 1 }, { + validated: true, + level: 1, + containes: { + number: false, + lowerCaseLetter: false, + upperCaseLetter: false, + specialCharacter: true, + unallowableCharacter: false + } + }); + }); + it('2级强度 "!@#$%^&*()-=_+[]\|{},./?<>~`"', () => { + checkEqual('!@#$%^&*()-=_+[]\|{},./?<>~`', { level: 2 }, { + validated: false, + level: 1, + containes: { + number: false, + lowerCaseLetter: false, + upperCaseLetter: false, + specialCharacter: true, + unallowableCharacter: false + } + }); + }); + + it('2级强度 "a12345678"', () => { + checkEqual('a12345678', { level: 2 }, { + validated: true, + level: 2, + containes: { + number: true, + lowerCaseLetter: true, + upperCaseLetter: false, + specialCharacter: false, + unallowableCharacter: false + } + }); + }); + it('2级强度 "a1_234787"', () => { + checkEqual('a1_234787', { level: 2 }, { + validated: true, + level: 3, + containes: { + number: true, + lowerCaseLetter: true, + upperCaseLetter: false, + specialCharacter: true, + unallowableCharacter: false + } + }); + }); + it('2级强度 "aa_234787"', () => { + checkEqual('aa_234787', { level: 2 }, { + validated: true, + level: 3, + containes: { + number: true, + lowerCaseLetter: true, + upperCaseLetter: false, + specialCharacter: true, + unallowableCharacter: false + } + }); + }); + + it('3级强度 "a12345678"', () => { + checkEqual('a12345678', { level: 3 }, { + validated: false, + level: 2, + containes: { + number: true, + lowerCaseLetter: true, + upperCaseLetter: false, + specialCharacter: false, + unallowableCharacter: false + } + }); + }); + it('3级强度,不忽略大小写 "Aa12345678"', () => { + checkEqual('Aa12345678', { level: 3 }, { + validated: true, + level: 3, + containes: { + number: true, + lowerCaseLetter: true, + upperCaseLetter: true, + specialCharacter: false, + unallowableCharacter: false + } + }); + }); + it('3级强度,忽略大小写 "Aa12345678"', () => { + checkEqual('Aa12345678', { level: 3, ignoreCase: true }, { + validated: false, + level: 2, + containes: { + number: true, + lowerCaseLetter: true, + upperCaseLetter: true, + specialCharacter: false, + unallowableCharacter: false + } + }); + }); + it('3级强度,忽略大小写 "_Aa12345678" => true', () => { + checkEqual('_Aa12345678', { level: 3, ignoreCase: true }, { + validated: true, + level: 3, + containes: { + number: true, + lowerCaseLetter: true, + upperCaseLetter: true, + specialCharacter: true, + unallowableCharacter: false + } + }); + }); + it('3级强度,非法字符 "_ Aa12345678"', () => { + checkEqual('_ Aa12345678', { level: 3, ignoreCase: true }, { + validated: false, + level: 3, + containes: { + number: true, + lowerCaseLetter: true, + upperCaseLetter: true, + specialCharacter: true, + unallowableCharacter: true + } + }); + }); + it('3级强度,非法字符 "_Aa一二三45678"', () => { + checkEqual('_Aa一二三45678', { level: 3, ignoreCase: true }, { + validated: false, + level: 3, + containes: { + number: true, + lowerCaseLetter: true, + upperCaseLetter: true, + specialCharacter: true, + unallowableCharacter: true + } + }); + }); + + it('3级强度,自定义特殊字符1', () => { + checkEqual('_Aa一二三45678', { level: 3, ignoreCase: true, special: '_一二三' }, { + validated: true, + level: 3, + containes: { + number: true, + lowerCaseLetter: true, + upperCaseLetter: true, + specialCharacter: true, + unallowableCharacter: false + } + }); + }); + it('3级强度,自定义特殊字符2', () => { + checkEqual('_Aa一二三45678=', { level: 3, ignoreCase: true, special: '_一二三' }, { + validated: false, + level: 3, + containes: { + number: true, + lowerCaseLetter: true, + upperCaseLetter: true, + specialCharacter: true, + unallowableCharacter: true + } + }); + }); +}) \ No newline at end of file diff --git a/types/index.d.ts b/types/index.d.ts index 01d2229e..4f491c67 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -1,6 +1,17 @@ // ----- 数据验证 start ----- type Validator = (value: string) => boolean; type EnableLooseValidator = (value: string, options?: { loose?: boolean; }) => boolean; +interface ValidateResult { + validated: boolean; + level: number; + containes: { + number: boolean; + lowerCaseLetter: boolean; + upperCaseLetter: boolean; + specialCharacter: boolean; + unallowableCharacter: boolean; + } +} export declare const isMobile: Validator; export declare const isTelephone: Validator; @@ -19,6 +30,7 @@ export declare const isIPv4: Validator; export declare const isIPv6: Validator; export declare const isUrl: Validator; export declare const isBusinessLicense: EnableLooseValidator; +export declare const validatePassword: (value: string, options?: { level?: number; ignoreCase?: boolean; special?: string; }) => ValidateResult; // ----- 数据验证 end -----