You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
canteen/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/common/validator.js

440 lines
10 KiB

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
}