validator.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443
  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 (getType(mobile) !== 'string') {
  46. return {
  47. errCode
  48. }
  49. }
  50. if (mobile && !/^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 (getType(email) !== 'string') {
  59. return {
  60. errCode
  61. }
  62. }
  63. if (email && !/@/.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. // 特殊符号 https://www.ibm.com/support/pages/password-strength-rules ~!@#$%^&*_-+=`|\(){}[]:;"'<>,.?/
  190. // const specialChar = '~!@#$%^&*_-+=`|\(){}[]:;"\'<>,.?/'
  191. // const specialCharRegExp = /^[~!@#$%^&*_\-+=`|\\(){}[\]:;"'<>,.?/]$/
  192. // for (let i = 0, arr = specialChar.split(''); i < arr.length; i++) {
  193. // const char = arr[i]
  194. // if (!specialCharRegExp.test(char)) {
  195. // throw new Error('check special character error: ' + char)
  196. // }
  197. // }
  198. // 密码强度表达式
  199. const passwordRules = {
  200. // 密码必须包含大小写字母、数字和特殊符号
  201. super: /^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=.*[~!@#$%^&*_\-+=`|\\(){}[\]:;"'<>,.?/])[0-9a-zA-Z~!@#$%^&*_\-+=`|\\(){}[\]:;"'<>,.?/]{8,16}$/,
  202. // 密码必须包含字母、数字和特殊符号
  203. strong: /^(?=.*[0-9])(?=.*[a-zA-Z])(?=.*[~!@#$%^&*_\-+=`|\\(){}[\]:;"'<>,.?/])[0-9a-zA-Z~!@#$%^&*_\-+=`|\\(){}[\]:;"'<>,.?/]{8,16}$/,
  204. // 密码必须为字母、数字和特殊符号任意两种的组合
  205. medium: /^(?![0-9]+$)(?![a-zA-Z]+$)(?![~!@#$%^&*_\-+=`|\\(){}[\]:;"'<>,.?/]+$)[0-9a-zA-Z~!@#$%^&*_\-+=`|\\(){}[\]:;"'<>,.?/]{8,16}$/,
  206. // 密码必须包含字母和数字
  207. weak: /^(?=.*[0-9])(?=.*[a-zA-Z])[0-9a-zA-Z~!@#$%^&*_\-+=`|\\(){}[\]:;"'<>,.?/]{6,16}$/,
  208. }
  209. function createPasswordVerifier({
  210. passwordStrength = ''
  211. } = {}) {
  212. return function (password) {
  213. const passwordRegExp = passwordRules[passwordStrength]
  214. if (!passwordRegExp) {
  215. throw new Error('Invalid password strength config: ' + passwordStrength)
  216. }
  217. const errCode = ERROR.INVALID_PASSWORD
  218. if (!isValidString(password)) {
  219. return {
  220. errCode
  221. }
  222. }
  223. if (!passwordRegExp.test(password)) {
  224. return {
  225. errCode: errCode + '-' + passwordStrength
  226. }
  227. }
  228. }
  229. }
  230. function isEmpty(value) {
  231. return value === undefined ||
  232. value === null ||
  233. (typeof value === 'string' && value.trim() === '')
  234. }
  235. class Validator {
  236. constructor({
  237. passwordStrength = ''
  238. } = {}) {
  239. this.baseValidator = baseValidator
  240. this.customValidator = Object.create(null)
  241. if (passwordStrength) {
  242. this.mixin(
  243. 'password',
  244. createPasswordVerifier({
  245. passwordStrength
  246. })
  247. )
  248. }
  249. }
  250. mixin(type, handler) {
  251. this.customValidator[type] = handler
  252. }
  253. getRealBaseValidator(type) {
  254. return this.customValidator[type] || this.baseValidator[type]
  255. }
  256. _isMatchUnionType(val, rule) {
  257. if (!rule.children || rule.children.length === 0) {
  258. return true
  259. }
  260. const children = rule.children
  261. for (let i = 0; i < children.length; i++) {
  262. const child = children[i]
  263. const category = getRuleCategory(child)
  264. let pass = false
  265. switch (category) {
  266. case 'base':
  267. pass = this._isMatchBaseType(val, child)
  268. break
  269. case 'array':
  270. pass = this._isMatchArrayType(val, child)
  271. break
  272. default:
  273. break
  274. }
  275. if (pass) {
  276. return true
  277. }
  278. }
  279. return false
  280. }
  281. _isMatchBaseType(val, rule) {
  282. const method = this.getRealBaseValidator(rule.type)
  283. if (typeof method !== 'function') {
  284. throw new Error(`invalid schema type: ${rule.type}`)
  285. }
  286. const validateRes = method(val)
  287. if (validateRes && validateRes.errCode) {
  288. return false
  289. }
  290. return true
  291. }
  292. _isMatchArrayType(arr, rule) {
  293. if (getType(arr) !== 'array') {
  294. return false
  295. }
  296. if (rule.children && rule.children.length && arr.some(item => !this._isMatchUnionType(item, rule))) {
  297. return false
  298. }
  299. return true
  300. }
  301. get validator() {
  302. const _this = this
  303. return new Proxy({}, {
  304. get: (_, prop) => {
  305. if (typeof prop !== 'string') {
  306. return
  307. }
  308. const realBaseValidator = this.getRealBaseValidator(prop)
  309. if (realBaseValidator) {
  310. return realBaseValidator
  311. }
  312. const rule = parseValidatorName(prop)
  313. return function (val) {
  314. if (!_this._isMatchUnionType(val, rule)) {
  315. return {
  316. errCode: ERROR.INVALID_PARAM
  317. }
  318. }
  319. }
  320. }
  321. })
  322. }
  323. validate(value = {}, schema = {}) {
  324. for (const schemaKey in schema) {
  325. let schemaValue = schema[schemaKey]
  326. if (getType(schemaValue) === 'string') {
  327. schemaValue = {
  328. required: true,
  329. type: schemaValue
  330. }
  331. }
  332. const {
  333. required,
  334. type
  335. } = schemaValue
  336. // value内未传入了schemaKey或对应值为undefined
  337. if (isEmpty(value[schemaKey])) {
  338. if (required) {
  339. return {
  340. errCode: ERROR.PARAM_REQUIRED,
  341. errMsgValue: {
  342. param: schemaKey
  343. },
  344. schemaKey
  345. }
  346. } else {
  347. //delete value[schemaKey]
  348. continue
  349. }
  350. }
  351. const validateMethod = this.validator[type]
  352. if (!validateMethod) {
  353. throw new Error(`invalid schema type: ${type}`)
  354. }
  355. const validateRes = validateMethod(value[schemaKey])
  356. if (validateRes) {
  357. validateRes.schemaKey = schemaKey
  358. return validateRes
  359. }
  360. }
  361. }
  362. }
  363. function checkClientInfo(clientInfo) {
  364. const stringNotRequired = {
  365. required: false,
  366. type: 'string'
  367. }
  368. const numberNotRequired = {
  369. required: false,
  370. type: 'number'
  371. }
  372. const numberOrStringNotRequired = {
  373. required: false,
  374. type: 'number|string'
  375. }
  376. const schema = {
  377. uniPlatform: 'string',
  378. appId: 'string',
  379. deviceId: stringNotRequired,
  380. osName: stringNotRequired,
  381. locale: stringNotRequired,
  382. clientIP: stringNotRequired,
  383. appName: stringNotRequired,
  384. appVersion: stringNotRequired,
  385. appVersionCode: numberOrStringNotRequired,
  386. channel: numberOrStringNotRequired,
  387. userAgent: stringNotRequired,
  388. uniIdToken: stringNotRequired,
  389. deviceBrand: stringNotRequired,
  390. deviceModel: stringNotRequired,
  391. osVersion: stringNotRequired,
  392. osLanguage: stringNotRequired,
  393. osTheme: stringNotRequired,
  394. romName: stringNotRequired,
  395. romVersion: stringNotRequired,
  396. devicePixelRatio: numberNotRequired,
  397. windowWidth: numberNotRequired,
  398. windowHeight: numberNotRequired,
  399. screenWidth: numberNotRequired,
  400. screenHeight: numberNotRequired
  401. }
  402. const validateRes = new Validator().validate(clientInfo, schema)
  403. if (validateRes) {
  404. if (validateRes.errCode === ERROR.PARAM_REQUIRED) {
  405. console.warn('- 如果使用HBuilderX运行本地云函数/云对象功能时出现此提示,请改为使用客户端调用本地云函数方式调试,或更新HBuilderX到3.4.12及以上版本。\n- 如果是缺少clientInfo.appId,请检查项目manifest.json内是否配置了DCloud AppId')
  406. throw new Error(`"clientInfo.${validateRes.schemaKey}" is required.`)
  407. } else {
  408. throw new Error(`Invalid client info: clienInfo.${validateRes.schemaKey}`)
  409. }
  410. }
  411. }
  412. module.exports = {
  413. Validator,
  414. checkClientInfo
  415. }