validator.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432
  1. const {
  2. isValidString,
  3. getType
  4. } = require('./utils.js')
  5. const {
  6. ERROR
  7. } = require('./error')
  8. const baseValidator = Object.create(null)
  9. baseValidator.username = function (username) {
  10. const errCode = ERROR.INVALID_USERNAME
  11. if (!isValidString(username)) {
  12. return {
  13. errCode
  14. }
  15. }
  16. if (/^\d+$/.test(username)) {
  17. // 用户名不能为纯数字
  18. return {
  19. errCode
  20. }
  21. };
  22. if (!/^[a-zA-Z0-9_-]+$/.test(username)) {
  23. // 用户名仅能使用数字、字母、“_”及“-”
  24. return {
  25. errCode
  26. }
  27. }
  28. }
  29. baseValidator.password = function (password) {
  30. const errCode = ERROR.INVALID_PASSWORD
  31. if (!isValidString(password)) {
  32. return {
  33. errCode
  34. }
  35. }
  36. if (password.length < 6) {
  37. // 密码长度不能小于6
  38. return {
  39. errCode
  40. }
  41. }
  42. }
  43. baseValidator.mobile = function (mobile) {
  44. const errCode = ERROR.INVALID_MOBILE
  45. if (!isValidString(mobile)) {
  46. return {
  47. errCode
  48. }
  49. }
  50. if (!/^1\d{10}$/.test(mobile)) {
  51. return {
  52. errCode
  53. }
  54. }
  55. }
  56. baseValidator.email = function (email) {
  57. const errCode = ERROR.INVALID_EMAIL
  58. if (!isValidString(email)) {
  59. return {
  60. errCode
  61. }
  62. }
  63. if (!/@/.test(email)) {
  64. return {
  65. errCode
  66. }
  67. }
  68. }
  69. baseValidator.nickname = function (nickname) {
  70. const errCode = ERROR.INVALID_NICKNAME
  71. if (nickname.indexOf('@') !== -1) {
  72. // 昵称不允许含@
  73. return {
  74. errCode
  75. }
  76. };
  77. if (/^\d+$/.test(nickname)) {
  78. // 昵称不能为纯数字
  79. return {
  80. errCode
  81. }
  82. };
  83. if (nickname.length > 100) {
  84. // 昵称不可超过100字符
  85. return {
  86. errCode
  87. }
  88. }
  89. }
  90. const baseType = ['string', 'boolean', 'number', 'null'] // undefined不会由客户端提交上来
  91. baseType.forEach((type) => {
  92. baseValidator[type] = function (val) {
  93. if (getType(val) === type) {
  94. return
  95. }
  96. return {
  97. errCode: ERROR.INVALID_PARAM
  98. }
  99. }
  100. })
  101. function tokenize(name) {
  102. let i = 0
  103. const result = []
  104. let token = ''
  105. while (i < name.length) {
  106. const char = name[i]
  107. switch (char) {
  108. case '|':
  109. case '<':
  110. case '>':
  111. token && result.push(token)
  112. result.push(char)
  113. token = ''
  114. break
  115. default:
  116. token += char
  117. break
  118. }
  119. i++
  120. if (i === name.length && token) {
  121. result.push(token)
  122. }
  123. }
  124. return result
  125. }
  126. /**
  127. * 处理validator名
  128. * @param {string} name
  129. */
  130. function parseValidatorName(name) {
  131. const tokenList = tokenize(name)
  132. let i = 0
  133. let currentToken = tokenList[i]
  134. const result = {
  135. type: 'root',
  136. children: [],
  137. parent: null
  138. }
  139. let lastRealm = result
  140. while (currentToken) {
  141. switch (currentToken) {
  142. case 'array': {
  143. const currentRealm = {
  144. type: 'array',
  145. children: [],
  146. parent: lastRealm
  147. }
  148. lastRealm.children.push(currentRealm)
  149. lastRealm = currentRealm
  150. break
  151. }
  152. case '<':
  153. if (lastRealm.type !== 'array') {
  154. throw new Error('Invalid validator token "<"')
  155. }
  156. break
  157. case '>':
  158. if (lastRealm.type !== 'array') {
  159. throw new Error('Invalid validator token ">"')
  160. }
  161. lastRealm = lastRealm.parent
  162. break
  163. case '|':
  164. if (lastRealm.type !== 'array' && lastRealm.type !== 'root') {
  165. throw new Error('Invalid validator token "|"')
  166. }
  167. break
  168. default:
  169. lastRealm.children.push({
  170. type: currentToken
  171. })
  172. break
  173. }
  174. i++
  175. currentToken = tokenList[i]
  176. }
  177. return result
  178. }
  179. function getRuleCategory(rule) {
  180. switch (rule.type) {
  181. case 'array':
  182. return 'array'
  183. case 'root':
  184. return 'root'
  185. default:
  186. return 'base'
  187. }
  188. }
  189. function isMatchUnionType(val, rule) {
  190. if (!rule.children || rule.children.length === 0) {
  191. return true
  192. }
  193. const children = rule.children
  194. for (let i = 0; i < children.length; i++) {
  195. const child = children[i]
  196. const category = getRuleCategory(child)
  197. let pass = false
  198. switch (category) {
  199. case 'base':
  200. pass = isMatchBaseType(val, child)
  201. break
  202. case 'array':
  203. pass = isMatchArrayType(val, child)
  204. break
  205. default:
  206. break
  207. }
  208. if (pass) {
  209. return true
  210. }
  211. }
  212. return false
  213. }
  214. function isMatchBaseType(val, rule) {
  215. if (typeof baseValidator[rule.type] !== 'function') {
  216. throw new Error(`invalid schema type: ${rule.type}`)
  217. }
  218. const validateRes = baseValidator[rule.type](val)
  219. if (validateRes && validateRes.errCode) {
  220. return false
  221. }
  222. return true
  223. }
  224. function isMatchArrayType(arr, rule) {
  225. if (getType(arr) !== 'array') {
  226. return false
  227. }
  228. if (rule.children && rule.children.length && arr.some(item => !isMatchUnionType(item, rule))) {
  229. return false
  230. }
  231. return true
  232. }
  233. // 特殊符号 https://www.ibm.com/support/pages/password-strength-rules ~!@#$%^&*_-+=`|\(){}[]:;"'<>,.?/
  234. // const specialChar = '~!@#$%^&*_-+=`|\(){}[]:;"\'<>,.?/'
  235. // const specialCharRegExp = /^[~!@#$%^&*_\-+=`|\\(){}[\]:;"'<>,.?/]$/
  236. // for (let i = 0, arr = specialChar.split(''); i < arr.length; i++) {
  237. // const char = arr[i]
  238. // if (!specialCharRegExp.test(char)) {
  239. // throw new Error('check special character error: ' + char)
  240. // }
  241. // }
  242. // 密码强度表达式
  243. const passwordRules = {
  244. // 密码必须包含大小写字母、数字和特殊符号
  245. super: /^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=.*[~!@#$%^&*_\-+=`|\\(){}[\]:;"'<>,.?/])[0-9a-zA-Z~!@#$%^&*_\-+=`|\\(){}[\]:;"'<>,.?/]{8,16}$/,
  246. // 密码必须包含字母、数字和特殊符号
  247. strong: /^(?=.*[0-9])(?=.*[a-zA-Z])(?=.*[~!@#$%^&*_\-+=`|\\(){}[\]:;"'<>,.?/])[0-9a-zA-Z~!@#$%^&*_\-+=`|\\(){}[\]:;"'<>,.?/]{8,16}$/,
  248. // 密码必须为字母、数字和特殊符号任意两种的组合
  249. medium: /^(?![0-9]+$)(?![a-zA-Z]+$)(?![~!@#$%^&*_\-+=`|\\(){}[\]:;"'<>,.?/]+$)[0-9a-zA-Z~!@#$%^&*_\-+=`|\\(){}[\]:;"'<>,.?/]{8,16}$/,
  250. // 密码必须包含字母和数字
  251. weak: /^(?=.*[0-9])(?=.*[a-zA-Z])[0-9a-zA-Z~!@#$%^&*_\-+=`|\\(){}[\]:;"'<>,.?/]{6,16}$/,
  252. }
  253. function createPasswordVerifier({
  254. passwordStrength = ''
  255. } = {}) {
  256. return function (password) {
  257. const passwordRegExp = passwordRules[passwordStrength]
  258. if (!passwordRegExp) {
  259. throw new Error('Invalid password strength config: ' + passwordStrength)
  260. }
  261. const errCode = ERROR.INVALID_PASSWORD
  262. if (!isValidString(password)) {
  263. return {
  264. errCode
  265. }
  266. }
  267. if (!passwordRegExp.test(password)) {
  268. return {
  269. errCode: errCode + '-' + passwordStrength
  270. }
  271. }
  272. }
  273. }
  274. class Validator {
  275. constructor({
  276. passwordStrength = ''
  277. } = {}) {
  278. this.baseValidator = baseValidator
  279. this.customValidator = Object.create(null)
  280. if (passwordStrength) {
  281. this.mixin(
  282. 'password',
  283. createPasswordVerifier({
  284. passwordStrength
  285. })
  286. )
  287. }
  288. }
  289. mixin(type, handler) {
  290. this.customValidator[type] = handler
  291. }
  292. getRealBaseValidator(type) {
  293. return this.customValidator[type] || this.baseValidator[type]
  294. }
  295. get validator() {
  296. return new Proxy({}, {
  297. get: (_, prop) => {
  298. if (typeof prop !== 'string') {
  299. return
  300. }
  301. const realBaseValidator = this.getRealBaseValidator(prop)
  302. if (realBaseValidator) {
  303. return realBaseValidator
  304. }
  305. const rule = parseValidatorName(prop)
  306. return function (val) {
  307. if (!isMatchUnionType(val, rule)) {
  308. return {
  309. errCode: ERROR.INVALID_PARAM
  310. }
  311. }
  312. }
  313. }
  314. })
  315. }
  316. validate(value = {}, schema = {}) {
  317. for (const schemaKey in schema) {
  318. let schemaValue = schema[schemaKey]
  319. if (getType(schemaValue) === 'string') {
  320. schemaValue = {
  321. required: true,
  322. type: schemaValue
  323. }
  324. }
  325. const {
  326. required,
  327. type
  328. } = schemaValue
  329. // value内未传入了schemaKey或对应值为undefined
  330. if (value[schemaKey] === undefined) {
  331. if (required) {
  332. return {
  333. errCode: ERROR.PARAM_REQUIRED,
  334. errMsgValue: {
  335. param: schemaKey
  336. },
  337. schemaKey
  338. }
  339. } else {
  340. continue
  341. }
  342. }
  343. const validateMethod = this.validator[type]
  344. if (!validateMethod) {
  345. throw new Error(`invalid schema type: ${type}`)
  346. }
  347. const validateRes = validateMethod(value[schemaKey])
  348. if (validateRes) {
  349. validateRes.schemaKey = schemaKey
  350. return validateRes
  351. }
  352. }
  353. }
  354. }
  355. function checkClientInfo(clientInfo) {
  356. const stringNotRequired = {
  357. required: false,
  358. type: 'string'
  359. }
  360. const numberNotRequired = {
  361. required: false,
  362. type: 'number'
  363. }
  364. const numberOrStringNotRequired = {
  365. required: false,
  366. type: 'number|string'
  367. }
  368. const schema = {
  369. uniPlatform: 'string',
  370. appId: 'string',
  371. deviceId: stringNotRequired,
  372. osName: stringNotRequired,
  373. locale: stringNotRequired,
  374. clientIP: stringNotRequired,
  375. appName: stringNotRequired,
  376. appVersion: stringNotRequired,
  377. appVersionCode: numberOrStringNotRequired,
  378. channel: numberOrStringNotRequired,
  379. userAgent: stringNotRequired,
  380. uniIdToken: stringNotRequired,
  381. deviceBrand: stringNotRequired,
  382. deviceModel: stringNotRequired,
  383. osVersion: stringNotRequired,
  384. osLanguage: stringNotRequired,
  385. osTheme: stringNotRequired,
  386. romName: stringNotRequired,
  387. romVersion: stringNotRequired,
  388. devicePixelRatio: numberNotRequired,
  389. windowWidth: numberNotRequired,
  390. windowHeight: numberNotRequired,
  391. screenWidth: numberNotRequired,
  392. screenHeight: numberNotRequired
  393. }
  394. const validateRes = new Validator().validate(clientInfo, schema)
  395. if (validateRes) {
  396. if (validateRes.errCode === ERROR.PARAM_REQUIRED) {
  397. console.warn('- 如果使用HBuilderX运行本地云函数/云对象功能时出现此提示,请改为使用客户端调用本地云函数方式调试,或更新HBuilderX到3.4.12及以上版本。\n- 如果是缺少clientInfo.appId,请检查项目manifest.json内是否配置了DCloud AppId')
  398. throw new Error(`"clientInfo.${validateRes.schemaKey}" is required.`)
  399. } else {
  400. throw new Error(`Invalid client info: clienInfo.${validateRes.schemaKey}`)
  401. }
  402. }
  403. }
  404. module.exports = {
  405. Validator,
  406. checkClientInfo
  407. }