From b425d8cc294051497f483cb03c2c039b8d2e320a Mon Sep 17 00:00:00 2001 From: chenruilong Date: Thu, 2 Mar 2023 12:13:22 +0800 Subject: [PATCH] 1.1.7 --- pages.json | 14 +- uni_modules/uni-id-pages/changelog.md | 2 + .../uni-id-pages/common/check-id-card.js | 14 + uni_modules/uni-id-pages/common/store.js | 15 +- uni_modules/uni-id-pages/config.js | 90 +-- uni_modules/uni-id-pages/package.json | 2 +- .../realname-verify/face-verify-icon.svg | 1 + .../realname-verify/realname-verify.vue | 314 +++++++++++ .../uni-id-pages/pages/userinfo/userinfo.vue | 520 +++++++++--------- uni_modules/uni-id-pages/pages_init.json | 14 +- .../uni-id-co/common/constants.js | 11 + .../cloudfunctions/uni-id-co/common/error.js | 11 +- .../uni-id-co/common/sensitive-aes-cipher.js | 64 +++ .../cloudfunctions/uni-id-co/common/utils.js | 60 +- .../uni-id-co/config/permission.js | 9 + .../cloudfunctions/uni-id-co/index.obj.js | 62 ++- .../cloudfunctions/uni-id-co/lang/en.js | 11 +- .../cloudfunctions/uni-id-co/lang/zh-hans.js | 11 +- .../module/account/get-realname-info.js | 45 ++ .../uni-id-co/module/account/index.js | 3 +- .../get-auth-result.js | 134 +++++ .../get-certify-id.js | 99 ++++ .../module/facial-recognition-verify/index.js | 4 + .../cloudfunctions/uni-id-co/package.json | 5 +- .../database/opendb-frv-logs.schema.json | 44 ++ 25 files changed, 1237 insertions(+), 322 deletions(-) create mode 100644 uni_modules/uni-id-pages/common/check-id-card.js create mode 100644 uni_modules/uni-id-pages/pages/userinfo/realname-verify/face-verify-icon.svg create mode 100644 uni_modules/uni-id-pages/pages/userinfo/realname-verify/realname-verify.vue create mode 100644 uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/common/sensitive-aes-cipher.js create mode 100644 uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/account/get-realname-info.js create mode 100644 uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/facial-recognition-verify/get-auth-result.js create mode 100644 uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/facial-recognition-verify/get-certify-id.js create mode 100644 uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/facial-recognition-verify/index.js create mode 100644 uni_modules/uni-id-pages/uniCloud/database/opendb-frv-logs.schema.json diff --git a/pages.json b/pages.json index 089aaf9..5c7c7f2 100644 --- a/pages.json +++ b/pages.json @@ -23,14 +23,14 @@ "style": { "navigationBarTitleText": "绑定手机号码" } - }, + }, // #ifndef MP-WEIXIN { "path": "uni_modules/uni-id-pages/pages/userinfo/cropImage/cropImage", "style": { "navigationBarTitleText": "" } - }, + }, // #endif { "path": "uni_modules/uni-id-pages/pages/login/login-withoutpwd", @@ -55,7 +55,7 @@ "style": { "navigationBarTitleText": "注册" } - }, + }, // #ifndef MP-WEIXIN { "path": "uni_modules/uni-id-pages/pages/register/register-admin", @@ -63,7 +63,7 @@ "navigationBarTitleText": "注册管理员账号", "enablePullDownRefresh": false } - }, + }, // #endif { "path": "uni_modules/uni-id-pages/pages/register/register-by-email", @@ -101,6 +101,12 @@ "navigationBarTitleText": "设置密码", "enablePullDownRefresh": false } + },{ + "path": "uni_modules/uni-id-pages/pages/userinfo/realname-verify/realname-verify", + "style": { + "navigationBarTitleText": "实名认证", + "enablePullDownRefresh": false + } } ], "globalStyle": { diff --git a/uni_modules/uni-id-pages/changelog.md b/uni_modules/uni-id-pages/changelog.md index 9db0405..8d5a4e2 100644 --- a/uni_modules/uni-id-pages/changelog.md +++ b/uni_modules/uni-id-pages/changelog.md @@ -1,3 +1,5 @@ +## 1.1.7(2023-02-27) +- 【重要】新增 实名认证功能 [详情](https://uniapp.dcloud.net.cn/uniCloud/uni-id-summary.html#frv) ## 1.1.6(2023-02-24) - uni-id-co 新增 注册用户时允许配置默认角色 [文档](https://uniapp.dcloud.net.cn/uniCloud/uni-id-summary.html#config-defult-role) - uni-id-co 优化 `updateUserInfoByExternal`接口,允许修改头像、性别 diff --git a/uni_modules/uni-id-pages/common/check-id-card.js b/uni_modules/uni-id-pages/common/check-id-card.js new file mode 100644 index 0000000..cba63f9 --- /dev/null +++ b/uni_modules/uni-id-pages/common/check-id-card.js @@ -0,0 +1,14 @@ +export default function checkIdCard (idCardNumber) { + if (!idCardNumber || typeof idCardNumber !== 'string' || idCardNumber.length !== 18) return false + + const coefficient = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2] + const checkCode = [1, 0, 'x', 9, 8, 7, 6, 5, 4, 3, 2] + const code = idCardNumber.substring(17) + + let sum = 0 + for (let i = 0; i < 17; i++) { + sum += Number(idCardNumber.charAt(i)) * coefficient[i] + } + + return checkCode[sum % 11].toString() === code.toLowerCase() +} diff --git a/uni_modules/uni-id-pages/common/store.js b/uni_modules/uni-id-pages/common/store.js index 6191424..25a07b3 100644 --- a/uni_modules/uni-id-pages/common/store.js +++ b/uni_modules/uni-id-pages/common/store.js @@ -37,12 +37,21 @@ export const mutations = { }) } else { + const uniIdCo = uniCloud.importObject("uni-id-co", { + customUI: true + }) try { let res = await usersTable.where("'_id' == $cloudEnv_uid") - .field('mobile,nickname,username,email,avatar_file') - .get() + .field('mobile,nickname,username,email,avatar_file') + .get() + + const realNameRes = await uniIdCo.getRealNameInfo() + // console.log('fromDbData',res.result.data); - this.setUserInfo(res.result.data[0]) + this.setUserInfo({ + ...res.result.data[0], + realNameAuth: realNameRes + }) } catch (e) { this.setUserInfo({},{cover:true}) console.error(e.message, e.errCode); diff --git a/uni_modules/uni-id-pages/config.js b/uni_modules/uni-id-pages/config.js index b03a2bf..e847774 100644 --- a/uni_modules/uni-id-pages/config.js +++ b/uni_modules/uni-id-pages/config.js @@ -1,50 +1,50 @@ export default { - //调试模式 - "debug": false, - /* + // 调试模式 + debug: false, + /* 登录类型 未列举到的或运行环境不支持的,将被自动隐藏。 如果需要在不同平台有不同的配置,直接用条件编译即可 */ - "isAdmin": false, // 区分管理端与用户端 - "loginTypes": [ - // "qq", - // "xiaomi", - // "sinaweibo", - // "taobao", - // "facebook", - // "google", - // "alipay", - // "douyin", + isAdmin: false, // 区分管理端与用户端 + loginTypes: [ + // "qq", + // "xiaomi", + // "sinaweibo", + // "taobao", + // "facebook", + // "google", + // "alipay", + // "douyin", - // #ifdef APP - "univerify", - // #endif - "weixin", - "username", - // #ifdef APP - "apple", - // #endif - "smsCode" - ], - //政策协议 - "agreements": { - "serviceUrl": "https://xxx", //用户服务协议链接 - "privacyUrl": "https://xxx", //隐私政策条款链接 - // 哪些场景下显示,1.注册(包括登录并注册,如:微信登录、苹果登录、短信验证码登录)、2.登录(如:用户名密码登录) - "scope": [ - 'register', 'login' - ] - }, - // 提供各类服务接入(如微信登录服务)的应用id - "appid": { - "weixin": { - // 微信公众号的appid,来源:登录微信公众号(https://mp.weixin.qq.com)-> 设置与开发 -> 基本配置 -> 公众号开发信息 -> AppID - "h5": "xxxxxx", - // 微信开放平台的appid,来源:登录微信开放平台(https://open.weixin.qq.com) -> 管理中心 -> 网站应用 -> 选择对应的应用名称,点击查看 -> AppID - "web": "xxxxxx" - } - }, - /** + // #ifdef APP + 'univerify', + // #endif + 'weixin', + 'username', + // #ifdef APP + 'apple', + // #endif + 'smsCode' + ], + // 政策协议 + agreements: { + serviceUrl: 'https://xxx', // 用户服务协议链接 + privacyUrl: 'https://xxx', // 隐私政策条款链接 + // 哪些场景下显示,1.注册(包括登录并注册,如:微信登录、苹果登录、短信验证码登录)、2.登录(如:用户名密码登录) + scope: [ + 'register', 'login', 'realNameVerify' + ] + }, + // 提供各类服务接入(如微信登录服务)的应用id + appid: { + weixin: { + // 微信公众号的appid,来源:登录微信公众号(https://mp.weixin.qq.com)-> 设置与开发 -> 基本配置 -> 公众号开发信息 -> AppID + h5: 'xxxxxx', + // 微信开放平台的appid,来源:登录微信开放平台(https://open.weixin.qq.com) -> 管理中心 -> 网站应用 -> 选择对应的应用名称,点击查看 -> AppID + web: 'xxxxxx' + } + }, + /** * 密码强度 * super(超强:密码必须包含大小写字母、数字和特殊符号,长度范围:8-16位之间) * strong(强: 密密码必须包含字母、数字和特殊符号,长度范围:8-16位之间) @@ -52,8 +52,8 @@ export default { * weak(弱:密码必须包含字母和数字,长度范围:6-16位之间) * 为空或false则不验证密码强度 */ - "passwordStrength":"medium", - /** + passwordStrength: 'medium', + /** * 登录后允许用户设置密码(只针对未设置密码得用户) * 开启此功能将 setPasswordAfterLogin 设置为 true 即可 * "setPasswordAfterLogin": false @@ -63,5 +63,5 @@ export default { * "allowSkip": true * } * */ - "setPasswordAfterLogin": false + setPasswordAfterLogin: false } diff --git a/uni_modules/uni-id-pages/package.json b/uni_modules/uni-id-pages/package.json index f783f10..dd3eb04 100644 --- a/uni_modules/uni-id-pages/package.json +++ b/uni_modules/uni-id-pages/package.json @@ -1,7 +1,7 @@ { "id": "uni-id-pages", "displayName": "uni-id-pages", - "version": "1.1.6", + "version": "1.1.7", "description": "云端一体简单、统一、可扩展的用户中心页面模版", "keywords": [ "用户管理", diff --git a/uni_modules/uni-id-pages/pages/userinfo/realname-verify/face-verify-icon.svg b/uni_modules/uni-id-pages/pages/userinfo/realname-verify/face-verify-icon.svg new file mode 100644 index 0000000..df30eb4 --- /dev/null +++ b/uni_modules/uni-id-pages/pages/userinfo/realname-verify/face-verify-icon.svg @@ -0,0 +1 @@ + diff --git a/uni_modules/uni-id-pages/pages/userinfo/realname-verify/realname-verify.vue b/uni_modules/uni-id-pages/pages/userinfo/realname-verify/realname-verify.vue new file mode 100644 index 0000000..cefbb23 --- /dev/null +++ b/uni_modules/uni-id-pages/pages/userinfo/realname-verify/realname-verify.vue @@ -0,0 +1,314 @@ + + + + + diff --git a/uni_modules/uni-id-pages/pages/userinfo/userinfo.vue b/uni_modules/uni-id-pages/pages/userinfo/userinfo.vue index 5c06983..729484a 100644 --- a/uni_modules/uni-id-pages/pages/userinfo/userinfo.vue +++ b/uni_modules/uni-id-pages/pages/userinfo/userinfo.vue @@ -1,256 +1,272 @@ - - - - diff --git a/uni_modules/uni-id-pages/pages_init.json b/uni_modules/uni-id-pages/pages_init.json index e1a3d23..13b54ca 100644 --- a/uni_modules/uni-id-pages/pages_init.json +++ b/uni_modules/uni-id-pages/pages_init.json @@ -18,13 +18,13 @@ "style": { "navigationBarTitleText": "绑定手机号码" } - }, + }, { "path": "uni_modules/uni-id-pages/pages/userinfo/cropImage/cropImage", "style": { "navigationBarTitleText": "" } - }, + }, { "path": "uni_modules/uni-id-pages/pages/login/login-withoutpwd", "style": { @@ -81,20 +81,26 @@ "navigationBarTitleText": "修改密码", "enablePullDownRefresh": false } - }, + }, { "path": "uni_modules/uni-id-pages/pages/register/register-admin", "style": { "navigationBarTitleText": "注册管理员账号", "enablePullDownRefresh": false } - }, + }, { "path": "uni_modules/uni-id-pages/pages/userinfo/set-pwd/set-pwd", "style": { "navigationBarTitleText": "设置密码", "enablePullDownRefresh": false } + },{ + "path": "uni_modules/uni-id-pages/pages/userinfo/realname-verify/realname-verify", + "style": { + "navigationBarTitleText": "实名认证", + "enablePullDownRefresh": false + } } ] } diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/common/constants.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/common/constants.js index f7ec549..afce8b8 100644 --- a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/common/constants.js +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/common/constants.js @@ -8,6 +8,8 @@ const deviceCollectionName = 'uni-id-device' const deviceCollection = db.collection(deviceCollectionName) const openDataCollectionName = 'opendb-open-data' const openDataCollection = db.collection(openDataCollectionName) +const frvLogsCollectionName = 'opendb-frv-logs' +const frvLogsCollection = db.collection(frvLogsCollectionName) const USER_IDENTIFIER = { _id: 'uid', @@ -78,6 +80,13 @@ const EMAIL_SCENE = { BIND_EMAIL: 'bind-email' } +const REAL_NAME_STATUS = { + NOT_CERTIFIED: 0, + WAITING_CERTIFIED: 1, + CERTIFIED: 2, + CERTIFY_FAILED: 3 +} + const EXTERNAL_DIRECT_CONNECT_PROVIDER = 'externalDirectConnect' module.exports = { @@ -87,11 +96,13 @@ module.exports = { verifyCollection, deviceCollection, openDataCollection, + frvLogsCollection, USER_IDENTIFIER, USER_STATUS, CAPTCHA_SCENE, LOG_TYPE, SMS_SCENE, EMAIL_SCENE, + REAL_NAME_STATUS, EXTERNAL_DIRECT_CONNECT_PROVIDER } diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/common/error.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/common/error.js index cf267de..ef40377 100644 --- a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/common/error.js +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/common/error.js @@ -39,7 +39,16 @@ const ERROR = { UNBIND_MOBILE_NOT_EXISTS: 'uni-id-unbind-mobile-not-exists', UNSUPPORTED_REQUEST: 'uni-id-unsupported-request', ILLEGAL_REQUEST: 'uni-id-illegal-request', - CONFIG_FIELD_REQUIRED: 'uni-id-config-field-required' + CONFIG_FIELD_REQUIRED: 'uni-id-config-field-required', + CONFIG_FIELD_INVALID: 'uni-frv-config-field-invalid', + FRV_FAIL: 'uni-id-frv-fail', + FRV_PROCESSING: 'uni-id-frv-processing', + REAL_NAME_VERIFIED: 'uni-id-realname-verified', + ID_CARD_EXISTS: 'uni-id-idcard-exists', + INVALID_ID_CARD: 'uni-id-invalid-idcard', + INVALID_REAL_NAME: 'uni-id-invalid-realname', + UNKNOWN_ERROR: 'uni-id-unknown-error', + REAL_NAME_VERIFY_UPPER_LIMIT: 'uni-id-realname-verify-upper-limit' } function isUniIdError (errCode) { diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/common/sensitive-aes-cipher.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/common/sensitive-aes-cipher.js new file mode 100644 index 0000000..b2d6d95 --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/common/sensitive-aes-cipher.js @@ -0,0 +1,64 @@ +const crypto = require('crypto') +const { ERROR } = require('./error') + +function checkSecret (secret) { + if (!secret) { + throw { + errCode: ERROR.CONFIG_FIELD_REQUIRED, + errMsgValue: { + field: 'sensitiveInfoEncryptSecret' + } + } + } + + if (secret.length !== 32) { + throw { + errCode: ERROR.CONFIG_FIELD_INVALID, + errMsgValue: { + field: 'sensitiveInfoEncryptSecret' + } + } + } +} +function encryptData (text = '') { + if (!text) return text + + const encryptSecret = this.config.sensitiveInfoEncryptSecret + + checkSecret(encryptSecret) + + const iv = encryptSecret.slice(-16) + + const cipher = crypto.createCipheriv('aes-256-cbc', encryptSecret, iv) + + const encrypted = Buffer.concat([ + cipher.update(Buffer.from(text, 'utf-8')), + cipher.final() + ]) + + return encrypted.toString('base64') +} + +function decryptData (text = '') { + if (!text) return text + + const encryptSecret = this.config.sensitiveInfoEncryptSecret + + checkSecret(encryptSecret) + + const iv = encryptSecret.slice(-16) + + const cipher = crypto.createDecipheriv('aes-256-cbc', encryptSecret, iv) + + const decrypted = Buffer.concat([ + cipher.update(Buffer.from(text, 'base64')), + cipher.final() + ]) + + return decrypted.toString('utf-8') +} + +module.exports = { + encryptData, + decryptData +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/common/utils.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/common/utils.js index 8996aa5..11dc6e7 100644 --- a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/common/utils.js +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/common/utils.js @@ -183,7 +183,7 @@ function isMatchUserApp (userAppList, matchAppList) { return true } if (getType(userAppList) !== 'array') { - return false + return false } if (userAppList.includes('*')) { return true @@ -194,6 +194,58 @@ function isMatchUserApp (userAppList, matchAppList) { return userAppList.some(item => matchAppList.includes(item)) } +function checkIdCard (idCardNumber) { + if (!idCardNumber || typeof idCardNumber !== 'string' || idCardNumber.length !== 18) return false + + const coefficient = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2] + const checkCode = [1, 0, 'x', 9, 8, 7, 6, 5, 4, 3, 2] + const code = idCardNumber.substring(17) + + let sum = 0 + for (let i = 0; i < 17; i++) { + sum += Number(idCardNumber.charAt(i)) * coefficient[i] + } + + return checkCode[sum % 11].toString() === code.toLowerCase() +} + +function catchAwait (fn, finallyFn) { + if (!fn) return [new Error('no function')] + + if (Promise.prototype.finally === undefined) { + // eslint-disable-next-line no-extend-native + Promise.prototype.finally = function (finallyFn) { + return this.then( + res => Promise.resolve(finallyFn()).then(() => res), + error => Promise.resolve(finallyFn()).then(() => { throw error }) + ) + } + } + + return fn + .then((data) => [undefined, data]) + .catch((error) => [error]) + .finally(() => typeof finallyFn === 'function' && finallyFn()) +} + +function dataDesensitization (value = '', options = {}) { + const { onlyLast = false } = options + const [firstIndex, middleIndex, lastIndex] = onlyLast ? [0, 0, -1] : [0, 1, -1] + + if (!value) return value + const first = value.slice(firstIndex, middleIndex) + const middle = value.slice(middleIndex, lastIndex) + const last = value.slice(lastIndex) + const star = Array.from(new Array(middle.length), (v) => '*').join('') + + return first + star + last +} + +function getCurrentDateTimestamp (date = Date.now(), targetTimezone = 8) { + const oneHour = 60 * 60 * 1000 + return parseInt((date + targetTimezone * oneHour) / (24 * oneHour)) * (24 * oneHour) - targetTimezone * oneHour +} + module.exports = { getType, isValidString, @@ -210,5 +262,9 @@ module.exports = { getVerifyCode, coverMobile, getNonceStr, - isMatchUserApp + isMatchUserApp, + checkIdCard, + catchAwait, + dataDesensitization, + getCurrentDateTimestamp } diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/config/permission.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/config/permission.js index 8a06222..229a264 100644 --- a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/config/permission.js +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/config/permission.js @@ -77,5 +77,14 @@ module.exports = { }, setPwd: { auth: true + }, + getFrvCertifyId: { + auth: true + }, + getFrvAuthResult: { + auth: true + }, + getRealNameInfo: { + auth: true } } diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/index.obj.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/index.obj.js index 8b15223..6c96196 100644 --- a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/index.obj.js +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/index.obj.js @@ -1,7 +1,8 @@ const uniIdCommon = require('uni-id-common') const uniCaptcha = require('uni-captcha') const { - getType + getType, + checkIdCard } = require('./common/utils') const { checkClientInfo, @@ -9,7 +10,8 @@ const { } = require('./common/validator') const ConfigUtils = require('./lib/utils/config') const { - isUniIdError + isUniIdError, + ERROR } = require('./common/error') const middleware = require('./middleware/index') const universal = require('./common/universal') @@ -55,7 +57,8 @@ const { resetPwdBySms, resetPwdByEmail, closeAccount, - getAccountInfo + getAccountInfo, + getRealNameInfo } = require('./module/account/index') const { createCaptcha, @@ -80,12 +83,15 @@ const { const { getSupportedLoginType } = require('./module/dev/index') - const { externalRegister, externalLogin, updateUserInfoByExternal } = require('./module/external') +const { + getFrvCertifyId, + getFrvAuthResult +} = require('./module/facial-recognition-verify') module.exports = { async _before () { @@ -132,6 +138,26 @@ module.exports = { this.validator = new Validator({ passwordStrength: this.config.passwordStrength }) + + // 扩展 validator 增加 验证身份证号码合法性 + this.validator.mixin('idCard', function (idCard) { + if (!checkIdCard(idCard)) { + return { + errCode: ERROR.INVALID_ID_CARD + } + } + }) + this.validator.mixin('realName', function (realName) { + if ( + typeof realName !== 'string' || + realName.length < 2 || + !/^[\u4e00-\u9fa5]{1,10}(·?[\u4e00-\u9fa5]{1,10}){0,5}$/.test(realName) + ) { + return { + errCode: ERROR.INVALID_REAL_NAME + } + } + }) /** * 示例:覆盖密码验证规则 */ @@ -633,5 +659,31 @@ module.exports = { * @param {string} params.avatar 头像 * @returns {object} */ - updateUserInfoByExternal + updateUserInfoByExternal, + /** + * 获取认证ID + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#get-frv-certify-id + * @param {Object} params + * @param {String} params.realName 真实姓名 + * @param {String} params.idCard 身份证号码 + * @returns + */ + getFrvCertifyId, + /** + * 查询认证结果 + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#get-frv-auth-result + * @param {Object} params + * @param {String} params.certifyId 认证ID + * @param {String} params.needAlivePhoto 是否获取认证照片,Y_O (原始图片)、Y_M(虚化,背景马赛克)、N(不返图) + * @returns + */ + getFrvAuthResult, + /** + * 获取实名信息 + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#get-realname-info + * @param {Object} params + * @param {Boolean} params.decryptData 是否解密数据 + * @returns + */ + getRealNameInfo } diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lang/en.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lang/en.js index 6f1dae3..6825461 100644 --- a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lang/en.js +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lang/en.js @@ -44,7 +44,16 @@ const sentence = { 'uni-id-unbind-password-not-exists': 'Please set a password first', 'uni-id-unsupported-request': 'Unsupported request', 'uni-id-illegal-request': 'Illegal request', - 'uni-id-config-field-required': 'Config field required: {field}' + 'uni-id-config-field-required': 'Config field required: {field}', + 'uni-id-config-field-invalid': 'Config field: {field} is invalid', + 'uni-id-frv-fail': 'Real name certify failed', + 'uni-id-frv-processing': 'Waiting for face recognition', + 'uni-id-realname-verified': 'This account has been verified', + 'uni-id-idcard-exists': 'The ID number has been bound to the account', + 'uni-id-invalid-idcard': 'ID number is invalid', + 'uni-id-invalid-realname': 'The name can only be Chinese characters', + 'uni-id-unknown-error': 'unknown error', + 'uni-id-realname-verify-upper-limit': 'The number of real-name certify on the day has reached the upper limit' } module.exports = { diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lang/zh-hans.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lang/zh-hans.js index f7ab079..911ce20 100644 --- a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lang/zh-hans.js +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lang/zh-hans.js @@ -46,7 +46,16 @@ const sentence = { 'uni-id-unbind-password-not-exists': '请先设置密码在尝试解绑', 'uni-id-unsupported-request': '不支持的请求方式', 'uni-id-illegal-request': '非法请求', - 'uni-id-config-field-required': '缺少配置项: {field}' + 'uni-id-frv-fail': '实名认证失败', + 'uni-id-frv-processing': '等待人脸识别', + 'uni-id-realname-verified': '该账号已实名认证', + 'uni-id-idcard-exists': '该证件号码已绑定账号', + 'uni-id-invalid-idcard': '身份证号码不合法', + 'uni-id-invalid-realname': '姓名只能是汉字', + 'uni-id-unknown-error': '未知错误', + 'uni-id-realname-verify-upper-limit': '当日实名认证次数已达上限', + 'uni-id-config-field-required': '缺少配置项: {field}', + 'uni-id-config-field-invalid': '配置项: {field}无效' } module.exports = { diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/account/get-realname-info.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/account/get-realname-info.js new file mode 100644 index 0000000..0ea8f05 --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/account/get-realname-info.js @@ -0,0 +1,45 @@ +const { userCollection } = require('../../common/constants') +const { ERROR } = require('../../common/error') +const { decryptData } = require('../../common/sensitive-aes-cipher') +const { dataDesensitization } = require('../../common/utils') + +/** + * 获取实名信息 + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#get-realname-info + * @param {Object} params + * @param {Boolean} params.decryptData 是否解密数据 + * @returns + */ +module.exports = async function (params = {}) { + const schema = { + decryptData: { + required: false, + type: 'boolean' + } + } + + this.middleware.validate(params, schema) + + const { decryptData: isDecryptData = true } = params + + const { + uid + } = this.authInfo + const getUserRes = await userCollection.doc(uid).get() + const userRecord = getUserRes && getUserRes.data && getUserRes.data[0] + if (!userRecord) { + throw { + errCode: ERROR.ACCOUNT_NOT_EXISTS + } + } + + const { realname_auth: realNameAuth = {} } = userRecord + + return { + errCode: 0, + type: realNameAuth.type, + authStatus: realNameAuth.auth_status, + realName: isDecryptData ? dataDesensitization(decryptData.call(this, realNameAuth.real_name), { onlyLast: true }) : realNameAuth.real_name, + identity: isDecryptData ? dataDesensitization(decryptData.call(this, realNameAuth.identity)) : realNameAuth.identity + } +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/account/index.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/account/index.js index b4e06d6..0e55385 100644 --- a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/account/index.js +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/account/index.js @@ -4,5 +4,6 @@ module.exports = { resetPwdBySms: require('./reset-pwd-by-sms'), resetPwdByEmail: require('./reset-pwd-by-email'), closeAccount: require('./close-account'), - getAccountInfo: require('./get-account-info') + getAccountInfo: require('./get-account-info'), + getRealNameInfo: require('./get-realname-info') } diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/facial-recognition-verify/get-auth-result.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/facial-recognition-verify/get-auth-result.js new file mode 100644 index 0000000..b509f5c --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/facial-recognition-verify/get-auth-result.js @@ -0,0 +1,134 @@ +const { userCollection, REAL_NAME_STATUS, frvLogsCollection } = require('../../common/constants') +const { dataDesensitization, catchAwait } = require('../../common/utils') +const { encryptData, decryptData } = require('../../common/sensitive-aes-cipher') +const { ERROR } = require('../../common/error') + +/** + * 查询认证结果 + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#get-frv-auth-result + * @param {Object} params + * @param {String} params.certifyId 认证ID + * @returns + */ +module.exports = async function (params) { + const schema = { + certifyId: 'string' + } + + this.middleware.validate(params, schema) + + const { uid } = this.authInfo // 从authInfo中取出uid属性 + const { certifyId } = params // 从params中取出certifyId属性 + + const user = await userCollection.doc(uid).get() // 根据uid查询用户信息 + const userInfo = user.data && user.data[0] // 从查询结果中获取userInfo对象 + + // 如果用户不存在,抛出账户不存在的错误 + if (!userInfo) { + throw { + errCode: ERROR.ACCOUNT_NOT_EXISTS + } + } + + const { realname_auth: realNameAuth = {} } = userInfo + + // 如果用户已经实名认证,抛出已实名认证的错误 + if (realNameAuth.auth_status === REAL_NAME_STATUS.CERTIFIED) { + throw { + errCode: ERROR.REAL_NAME_VERIFIED + } + } + + // 初始化实人认证服务 + const frvManager = uniCloud.getFacialRecognitionVerifyManager({ + requestId: this.getUniCloudRequestId() + }) + + // 调用frvManager的getAuthResult方法,获取认证结果 + const [error, res] = await catchAwait(frvManager.getAuthResult({ + certifyId + })) + + // 如果出现错误,抛出未知错误并打印日志 + if (error) { + console.log(ERROR.UNKNOWN_ERROR, 'error: ', error) + throw error + } + + // 如果认证状态为“PROCESSING”,抛出认证正在处理中的错误 + if (res.authState === 'PROCESSING') { + throw { + errCode: ERROR.FRV_PROCESSING + } + } + + // 如果认证状态为“FAIL”,更新认证日志的状态并抛出认证失败的错误 + if (res.authState === 'FAIL') { + await frvLogsCollection.where({ + certify_id: certifyId + }).update({ + status: REAL_NAME_STATUS.CERTIFY_FAILED + }) + + throw { + errCode: ERROR.FRV_FAIL + } + } + + // 如果认证状态不为“SUCCESS”,抛出未知错误并打印日志 + if (res.authState !== 'SUCCESS') { + console.log(ERROR.UNKNOWN_ERROR, 'source res: ', res) + throw { + errCode: ERROR.UNKNOWN_ERROR + } + } + + // 根据certifyId查询认证记录 + const frvLogs = await frvLogsCollection.where({ + certify_id: certifyId + }).get() + + const log = frvLogs.data && frvLogs.data[0] + + const updateData = { + realname_auth: { + auth_status: REAL_NAME_STATUS.CERTIFIED, + real_name: log.real_name, + identity: log.identity, + auth_date: Date.now(), + type: 0 + } + } + + // 如果获取到了认证照片的地址,则会对其进行下载,并使用uniCloud.uploadFile方法将其上传到云存储,并将上传后的fileID保存起来。 + if (res.pictureUrl) { + const pictureRes = await uniCloud.httpclient.request(res.pictureUrl) + if (pictureRes.status < 400) { + const { + fileID + } = await uniCloud.uploadFile({ + cloudPath: `user/id-card/${uid}.b64`, + fileContent: Buffer.from(encryptData.call(this, pictureRes.data.toString('base64'))) + }) + updateData.realname_auth.in_hand = fileID + } + } + + await Promise.all([ + // 更新用户认证状态 + userCollection.doc(uid).update(updateData), + // 更新实人认证记录状态 + frvLogsCollection.where({ + certify_id: certifyId + }).update({ + status: REAL_NAME_STATUS.CERTIFIED + }) + ]) + + return { + errCode: 0, + authStatus: REAL_NAME_STATUS.CERTIFIED, + realName: dataDesensitization(decryptData.call(this, log.real_name), { onlyLast: true }), // 对姓名进行脱敏处理 + identity: dataDesensitization(decryptData.call(this, log.identity)) // 对身份证号进行脱敏处理 + } +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/facial-recognition-verify/get-certify-id.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/facial-recognition-verify/get-certify-id.js new file mode 100644 index 0000000..cb8b48b --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/facial-recognition-verify/get-certify-id.js @@ -0,0 +1,99 @@ +const { userCollection, REAL_NAME_STATUS, frvLogsCollection, dbCmd } = require('../../common/constants') +const { ERROR } = require('../../common/error') +const { encryptData } = require('../../common/sensitive-aes-cipher') +const { getCurrentDateTimestamp } = require('../../common/utils') + +// const CertifyIdExpired = 25 * 60 * 1000 // certifyId 过期时间为30分钟,在25分时置为过期 + +/** + * 获取认证ID + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#get-frv-certify-id + * @param {Object} params + * @param {String} params.realName 真实姓名 + * @param {String} params.idCard 身份证号码 + * @param {String} params.metaInfo 客户端初始化时返回的metaInfo + * @returns + */ +module.exports = async function (params) { + const schema = { + realName: 'realName', + idCard: 'idCard', + metaInfo: 'string' + } + + this.middleware.validate(params, schema) + + const { realName: originalRealName, idCard: originalIdCard, metaInfo } = params // 解构出传入参数的真实姓名、身份证号码、其他元数据 + const realName = encryptData.call(this, originalRealName) // 对真实姓名进行加密处理 + const idCard = encryptData.call(this, originalIdCard) // 对身份证号码进行加密处理 + + const { uid } = this.authInfo // 获取当前用户的 ID + const idCardCertifyLimit = this.config.idCardCertifyLimit || 1 // 获取身份证认证限制次数,默认为1次 + const realNameCertifyLimit = this.config.realNameCertifyLimit || 5 // 获取实名认证限制次数,默认为5次 + const frvNeedAlivePhoto = this.config.frvNeedAlivePhoto || false // 是否需要拍摄活体照片,默认为 false + + const user = await userCollection.doc(uid).get() // 获取用户信息 + const userInfo = user.data && user.data[0] // 获取用户信息对象中的实名认证信息 + const { realname_auth: realNameAuth = {} } = userInfo // 解构出实名认证信息中的认证状态对象,默认为空对象 + + // 如果用户已经实名认证过,不能再次认证 + if (realNameAuth.auth_status === REAL_NAME_STATUS.CERTIFIED) { + throw { + errCode: ERROR.REAL_NAME_VERIFIED + } + } + + // 查询已经使用同一个身份证认证的账号数量,如果超过限制则不能认证 + const idCardAccount = await userCollection.where({ + realname_auth: { + type: 0, // 用户认证状态是个人 + auth_status: REAL_NAME_STATUS.CERTIFIED, // 认证状态为已认证 + identity: idCard // 身份证号码和传入参数的身份证号码相同 + } + }).get() + if (idCardAccount.data.length >= idCardCertifyLimit) { + throw { + errCode: ERROR.ID_CARD_EXISTS + } + } + + // 查询用户今天已经进行的实名认证次数,如果超过限制则不能认证 + const userFrvLogs = await frvLogsCollection.where({ + user_id: uid, + created_date: dbCmd.gt(getCurrentDateTimestamp()) // 查询今天的认证记录 + }).get() + + // 限制用户每日认证次数 + if (userFrvLogs.data && userFrvLogs.data.length >= realNameCertifyLimit) { + throw { + errCode: ERROR.REAL_NAME_VERIFY_UPPER_LIMIT + } + } + + // 初始化实人认证服务 + const frvManager = uniCloud.getFacialRecognitionVerifyManager({ + requestId: this.getUniCloudRequestId() // 获取当前 + }) + // 调用实人认证服务,获取认证 ID + const res = await frvManager.getCertifyId({ + realName: originalRealName, + idCard: originalIdCard, + needPicture: frvNeedAlivePhoto, + metaInfo + }) + + // 将认证记录插入到实名认证日志中 + await frvLogsCollection.add({ + user_id: uid, + certify_id: res.certifyId, + real_name: realName, + identity: idCard, + status: REAL_NAME_STATUS.WAITING_CERTIFIED, + created_date: Date.now() + }) + + // 返回认证ID + return { + certifyId: res.certifyId + } +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/facial-recognition-verify/index.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/facial-recognition-verify/index.js new file mode 100644 index 0000000..63f6b1f --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/facial-recognition-verify/index.js @@ -0,0 +1,4 @@ +module.exports = { + getFrvCertifyId: require('./get-certify-id'), + getFrvAuthResult: require('./get-auth-result') +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/package.json b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/package.json index aa3aa3f..15741a9 100644 --- a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/package.json +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/package.json @@ -1,6 +1,6 @@ { "name": "uni-id-co", - "version": "1.1.6", + "version": "1.1.7", "description": "", "main": "index.js", "keywords": [], @@ -15,7 +15,8 @@ }, "extensions": { "uni-cloud-sms": {}, - "uni-cloud-redis": {} + "uni-cloud-redis": {}, + "uni-cloud-verify": {} }, "cloudfunction-config": { "keepRunningAfterReturn": false diff --git a/uni_modules/uni-id-pages/uniCloud/database/opendb-frv-logs.schema.json b/uni_modules/uni-id-pages/uniCloud/database/opendb-frv-logs.schema.json new file mode 100644 index 0000000..239fd82 --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/database/opendb-frv-logs.schema.json @@ -0,0 +1,44 @@ +{ + "bsonType": "object", + "permission": { + "read": "doc._id == auth.uid || 'CREATE_UNI_ID_USERS' in auth.permission", + "create": "'CREATE_UNI_ID_USERS' in auth.permission", + "update": "doc._id == auth.uid || 'UPDATE_UNI_ID_USERS' in auth.permission", + "delete": "'DELETE_UNI_ID_USERS' in auth.permission" + }, + "properties": { + "_id": { + "description": "存储文档 ID(用户 ID),系统自动生成" + }, + "certify_id": { + "bsonType": "string", + "description": "认证id" + }, + "user_id": { + "bsonType": "string", + "description": "用户id" + }, + "real_name": { + "bsonType": "string", + "description": "姓名" + }, + "identity": { + "bsonType": "string", + "description": "身份证号码" + }, + "status": { + "bsonType": "int", + "description": "认证状态:0 未认证 1 等待认证 2 认证通过 3 认证失败", + "maximum": 3, + "minimum": 0 + }, + "created_date": { + "bsonType": "timestamp", + "description": "创建时间", + "forceDefaultValue": { + "$env": "now" + } + } + }, + "required": [] +} -- GitLab