|
|
const {
|
|
|
isValidString,
|
|
|
getType
|
|
|
} = require('./utils.js')
|
|
|
const {
|
|
|
ERROR
|
|
|
} = require('./error')
|
|
|
|
|
|
const baseValidator = Object.create(null)
|
|
|
|
|
|
baseValidator.username = function (username) {
|
|
|
const errCode = ERROR.INVALID_USERNAME
|
|
|
if (!isValidString(username)) {
|
|
|
return {
|
|
|
errCode
|
|
|
}
|
|
|
}
|
|
|
if (/^\d+$/.test(username)) {
|
|
|
// 用户名不能为纯数字
|
|
|
return {
|
|
|
errCode
|
|
|
}
|
|
|
};
|
|
|
if (!/^[a-zA-Z0-9_-]+$/.test(username)) {
|
|
|
// 用户名仅能使用数字、字母、“_”及“-”
|
|
|
return {
|
|
|
errCode
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
baseValidator.password = function (password) {
|
|
|
const errCode = ERROR.INVALID_PASSWORD
|
|
|
if (!isValidString(password)) {
|
|
|
return {
|
|
|
errCode
|
|
|
}
|
|
|
}
|
|
|
if (password.length < 6) {
|
|
|
// 密码长度不能小于6
|
|
|
return {
|
|
|
errCode
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
baseValidator.mobile = function (mobile) {
|
|
|
const errCode = ERROR.INVALID_MOBILE
|
|
|
if (getType(mobile) !== 'string') {
|
|
|
return {
|
|
|
errCode
|
|
|
}
|
|
|
}
|
|
|
if (mobile && !/^1\d{10}$/.test(mobile)) {
|
|
|
return {
|
|
|
errCode
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
baseValidator.email = function (email) {
|
|
|
const errCode = ERROR.INVALID_EMAIL
|
|
|
if (getType(email) !== 'string') {
|
|
|
return {
|
|
|
errCode
|
|
|
}
|
|
|
}
|
|
|
if (email && !/@/.test(email)) {
|
|
|
return {
|
|
|
errCode
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
baseValidator.nickname = function (nickname) {
|
|
|
const errCode = ERROR.INVALID_NICKNAME
|
|
|
if (nickname.indexOf('@') !== -1) {
|
|
|
// 昵称不允许含@
|
|
|
return {
|
|
|
errCode
|
|
|
}
|
|
|
};
|
|
|
if (/^\d+$/.test(nickname)) {
|
|
|
// 昵称不能为纯数字
|
|
|
return {
|
|
|
errCode
|
|
|
}
|
|
|
};
|
|
|
if (nickname.length > 100) {
|
|
|
// 昵称不可超过100字符
|
|
|
return {
|
|
|
errCode
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
const baseType = ['string', 'boolean', 'number', 'null'] // undefined不会由客户端提交上来
|
|
|
|
|
|
baseType.forEach((type) => {
|
|
|
baseValidator[type] = function (val) {
|
|
|
if (getType(val) === type) {
|
|
|
return
|
|
|
}
|
|
|
return {
|
|
|
errCode: ERROR.INVALID_PARAM
|
|
|
}
|
|
|
}
|
|
|
})
|
|
|
|
|
|
function tokenize(name) {
|
|
|
let i = 0
|
|
|
const result = []
|
|
|
let token = ''
|
|
|
while (i < name.length) {
|
|
|
const char = name[i]
|
|
|
switch (char) {
|
|
|
case '|':
|
|
|
case '<':
|
|
|
case '>':
|
|
|
token && result.push(token)
|
|
|
result.push(char)
|
|
|
token = ''
|
|
|
break
|
|
|
default:
|
|
|
token += char
|
|
|
break
|
|
|
}
|
|
|
i++
|
|
|
if (i === name.length && token) {
|
|
|
result.push(token)
|
|
|
}
|
|
|
}
|
|
|
return result
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 处理validator名
|
|
|
* @param {string} name
|
|
|
*/
|
|
|
function parseValidatorName(name) {
|
|
|
const tokenList = tokenize(name)
|
|
|
let i = 0
|
|
|
let currentToken = tokenList[i]
|
|
|
const result = {
|
|
|
type: 'root',
|
|
|
children: [],
|
|
|
parent: null
|
|
|
}
|
|
|
let lastRealm = result
|
|
|
while (currentToken) {
|
|
|
switch (currentToken) {
|
|
|
case 'array': {
|
|
|
const currentRealm = {
|
|
|
type: 'array',
|
|
|
children: [],
|
|
|
parent: lastRealm
|
|
|
}
|
|
|
lastRealm.children.push(currentRealm)
|
|
|
lastRealm = currentRealm
|
|
|
break
|
|
|
}
|
|
|
case '<':
|
|
|
if (lastRealm.type !== 'array') {
|
|
|
throw new Error('Invalid validator token "<"')
|
|
|
}
|
|
|
break
|
|
|
case '>':
|
|
|
if (lastRealm.type !== 'array') {
|
|
|
throw new Error('Invalid validator token ">"')
|
|
|
}
|
|
|
lastRealm = lastRealm.parent
|
|
|
break
|
|
|
case '|':
|
|
|
if (lastRealm.type !== 'array' && lastRealm.type !== 'root') {
|
|
|
throw new Error('Invalid validator token "|"')
|
|
|
}
|
|
|
break
|
|
|
default:
|
|
|
lastRealm.children.push({
|
|
|
type: currentToken
|
|
|
})
|
|
|
break
|
|
|
}
|
|
|
i++
|
|
|
currentToken = tokenList[i]
|
|
|
}
|
|
|
return result
|
|
|
}
|
|
|
|
|
|
function getRuleCategory(rule) {
|
|
|
switch (rule.type) {
|
|
|
case 'array':
|
|
|
return 'array'
|
|
|
case 'root':
|
|
|
return 'root'
|
|
|
default:
|
|
|
return 'base'
|
|
|
}
|
|
|
}
|
|
|
|
|
|
function isMatchUnionType(val, rule) {
|
|
|
if (!rule.children || rule.children.length === 0) {
|
|
|
return true
|
|
|
}
|
|
|
const children = rule.children
|
|
|
for (let i = 0; i < children.length; i++) {
|
|
|
const child = children[i]
|
|
|
const category = getRuleCategory(child)
|
|
|
let pass = false
|
|
|
switch (category) {
|
|
|
case 'base':
|
|
|
pass = isMatchBaseType(val, child)
|
|
|
break
|
|
|
case 'array':
|
|
|
pass = isMatchArrayType(val, child)
|
|
|
break
|
|
|
default:
|
|
|
break
|
|
|
}
|
|
|
if (pass) {
|
|
|
return true
|
|
|
}
|
|
|
}
|
|
|
return false
|
|
|
}
|
|
|
|
|
|
function isMatchBaseType(val, rule) {
|
|
|
if (typeof baseValidator[rule.type] !== 'function') {
|
|
|
throw new Error(`invalid schema type: ${rule.type}`)
|
|
|
}
|
|
|
const validateRes = baseValidator[rule.type](val)
|
|
|
if (validateRes && validateRes.errCode) {
|
|
|
return false
|
|
|
}
|
|
|
return true
|
|
|
}
|
|
|
|
|
|
function isMatchArrayType(arr, rule) {
|
|
|
if (getType(arr) !== 'array') {
|
|
|
return false
|
|
|
}
|
|
|
if (rule.children && rule.children.length && arr.some(item => !isMatchUnionType(item, rule))) {
|
|
|
return false
|
|
|
}
|
|
|
return true
|
|
|
}
|
|
|
|
|
|
// 特殊符号 https://www.ibm.com/support/pages/password-strength-rules ~!@#$%^&*_-+=`|\(){}[]:;"'<>,.?/
|
|
|
// const specialChar = '~!@#$%^&*_-+=`|\(){}[]:;"\'<>,.?/'
|
|
|
// const specialCharRegExp = /^[~!@#$%^&*_\-+=`|\\(){}[\]:;"'<>,.?/]$/
|
|
|
// for (let i = 0, arr = specialChar.split(''); i < arr.length; i++) {
|
|
|
// const char = arr[i]
|
|
|
// if (!specialCharRegExp.test(char)) {
|
|
|
// throw new Error('check special character error: ' + char)
|
|
|
// }
|
|
|
// }
|
|
|
|
|
|
// 密码强度表达式
|
|
|
const passwordRules = {
|
|
|
// 密码必须包含大小写字母、数字和特殊符号
|
|
|
super: /^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=.*[~!@#$%^&*_\-+=`|\\(){}[\]:;"'<>,.?/])[0-9a-zA-Z~!@#$%^&*_\-+=`|\\(){}[\]:;"'<>,.?/]{8,16}$/,
|
|
|
// 密码必须包含字母、数字和特殊符号
|
|
|
strong: /^(?=.*[0-9])(?=.*[a-zA-Z])(?=.*[~!@#$%^&*_\-+=`|\\(){}[\]:;"'<>,.?/])[0-9a-zA-Z~!@#$%^&*_\-+=`|\\(){}[\]:;"'<>,.?/]{8,16}$/,
|
|
|
// 密码必须为字母、数字和特殊符号任意两种的组合
|
|
|
medium: /^(?![0-9]+$)(?![a-zA-Z]+$)(?![~!@#$%^&*_\-+=`|\\(){}[\]:;"'<>,.?/]+$)[0-9a-zA-Z~!@#$%^&*_\-+=`|\\(){}[\]:;"'<>,.?/]{8,16}$/,
|
|
|
// 密码必须包含字母和数字
|
|
|
weak: /^(?=.*[0-9])(?=.*[a-zA-Z])[0-9a-zA-Z~!@#$%^&*_\-+=`|\\(){}[\]:;"'<>,.?/]{6,16}$/,
|
|
|
|
|
|
}
|
|
|
|
|
|
function createPasswordVerifier({
|
|
|
passwordStrength = ''
|
|
|
} = {}) {
|
|
|
return function (password) {
|
|
|
const passwordRegExp = passwordRules[passwordStrength]
|
|
|
if (!passwordRegExp) {
|
|
|
throw new Error('Invalid password strength config: ' + passwordStrength)
|
|
|
}
|
|
|
const errCode = ERROR.INVALID_PASSWORD
|
|
|
if (!isValidString(password)) {
|
|
|
return {
|
|
|
errCode
|
|
|
}
|
|
|
}
|
|
|
if (!passwordRegExp.test(password)) {
|
|
|
return {
|
|
|
errCode: errCode + '-' + passwordStrength
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
function isEmpty(value) {
|
|
|
return value === undefined ||
|
|
|
value === null ||
|
|
|
(typeof value === 'string' && value.trim() === '')
|
|
|
}
|
|
|
|
|
|
class Validator {
|
|
|
constructor({
|
|
|
passwordStrength = ''
|
|
|
} = {}) {
|
|
|
this.baseValidator = baseValidator
|
|
|
this.customValidator = Object.create(null)
|
|
|
if (passwordStrength) {
|
|
|
this.mixin(
|
|
|
'password',
|
|
|
createPasswordVerifier({
|
|
|
passwordStrength
|
|
|
})
|
|
|
)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
mixin(type, handler) {
|
|
|
this.customValidator[type] = handler
|
|
|
}
|
|
|
|
|
|
getRealBaseValidator(type) {
|
|
|
return this.customValidator[type] || this.baseValidator[type]
|
|
|
}
|
|
|
|
|
|
get validator() {
|
|
|
return new Proxy({}, {
|
|
|
get: (_, prop) => {
|
|
|
if (typeof prop !== 'string') {
|
|
|
return
|
|
|
}
|
|
|
const realBaseValidator = this.getRealBaseValidator(prop)
|
|
|
if (realBaseValidator) {
|
|
|
return realBaseValidator
|
|
|
}
|
|
|
const rule = parseValidatorName(prop)
|
|
|
return function (val) {
|
|
|
if (!isMatchUnionType(val, rule)) {
|
|
|
return {
|
|
|
errCode: ERROR.INVALID_PARAM
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
})
|
|
|
}
|
|
|
|
|
|
validate(value = {}, schema = {}) {
|
|
|
for (const schemaKey in schema) {
|
|
|
let schemaValue = schema[schemaKey]
|
|
|
if (getType(schemaValue) === 'string') {
|
|
|
schemaValue = {
|
|
|
required: true,
|
|
|
type: schemaValue
|
|
|
}
|
|
|
}
|
|
|
const {
|
|
|
required,
|
|
|
type
|
|
|
} = schemaValue
|
|
|
// value内未传入了schemaKey或对应值为undefined
|
|
|
if (isEmpty(value[schemaKey])) {
|
|
|
if (required) {
|
|
|
return {
|
|
|
errCode: ERROR.PARAM_REQUIRED,
|
|
|
errMsgValue: {
|
|
|
param: schemaKey
|
|
|
},
|
|
|
schemaKey
|
|
|
}
|
|
|
} else {
|
|
|
//delete value[schemaKey]
|
|
|
continue
|
|
|
}
|
|
|
}
|
|
|
const validateMethod = this.validator[type]
|
|
|
if (!validateMethod) {
|
|
|
throw new Error(`invalid schema type: ${type}`)
|
|
|
}
|
|
|
const validateRes = validateMethod(value[schemaKey])
|
|
|
if (validateRes) {
|
|
|
validateRes.schemaKey = schemaKey
|
|
|
return validateRes
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
function checkClientInfo(clientInfo) {
|
|
|
const stringNotRequired = {
|
|
|
required: false,
|
|
|
type: 'string'
|
|
|
}
|
|
|
const numberNotRequired = {
|
|
|
required: false,
|
|
|
type: 'number'
|
|
|
}
|
|
|
const numberOrStringNotRequired = {
|
|
|
required: false,
|
|
|
type: 'number|string'
|
|
|
}
|
|
|
const schema = {
|
|
|
uniPlatform: 'string',
|
|
|
appId: 'string',
|
|
|
deviceId: stringNotRequired,
|
|
|
osName: stringNotRequired,
|
|
|
locale: stringNotRequired,
|
|
|
clientIP: stringNotRequired,
|
|
|
appName: stringNotRequired,
|
|
|
appVersion: stringNotRequired,
|
|
|
appVersionCode: numberOrStringNotRequired,
|
|
|
channel: numberOrStringNotRequired,
|
|
|
userAgent: stringNotRequired,
|
|
|
uniIdToken: stringNotRequired,
|
|
|
deviceBrand: stringNotRequired,
|
|
|
deviceModel: stringNotRequired,
|
|
|
osVersion: stringNotRequired,
|
|
|
osLanguage: stringNotRequired,
|
|
|
osTheme: stringNotRequired,
|
|
|
romName: stringNotRequired,
|
|
|
romVersion: stringNotRequired,
|
|
|
devicePixelRatio: numberNotRequired,
|
|
|
windowWidth: numberNotRequired,
|
|
|
windowHeight: numberNotRequired,
|
|
|
screenWidth: numberNotRequired,
|
|
|
screenHeight: numberNotRequired
|
|
|
}
|
|
|
const validateRes = new Validator().validate(clientInfo, schema)
|
|
|
if (validateRes) {
|
|
|
if (validateRes.errCode === ERROR.PARAM_REQUIRED) {
|
|
|
console.warn('- 如果使用HBuilderX运行本地云函数/云对象功能时出现此提示,请改为使用客户端调用本地云函数方式调试,或更新HBuilderX到3.4.12及以上版本。\n- 如果是缺少clientInfo.appId,请检查项目manifest.json内是否配置了DCloud AppId')
|
|
|
throw new Error(`"clientInfo.${validateRes.schemaKey}" is required.`)
|
|
|
} else {
|
|
|
throw new Error(`Invalid client info: clienInfo.${validateRes.schemaKey}`)
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
module.exports = {
|
|
|
Validator,
|
|
|
checkClientInfo
|
|
|
}
|