From c499600db6a2af3366cbdc03f81b31b28ad7358e Mon Sep 17 00:00:00 2001 From: chenruilong Date: Wed, 8 Feb 2023 11:40:16 +0800 Subject: [PATCH] =?UTF-8?q?feat(uni-id-co):=20=E6=96=B0=E5=A2=9E=20?= =?UTF-8?q?=E5=AE=9E=E4=BA=BA=E8=AE=A4=E8=AF=81=E7=9B=B8=E5=85=B3=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../uni-id-co/common/constants.js | 20 ++- .../cloudfunctions/uni-id-co/common/error.js | 10 +- .../uni-id-co/common/sensitive-aes-cipher.js | 57 +++++++++ .../cloudfunctions/uni-id-co/common/utils.js | 53 +++++++- .../uni-id-co/config/permission.js | 9 ++ .../cloudfunctions/uni-id-co/index.obj.js | 89 +++++++++++-- .../cloudfunctions/uni-id-co/lang/en.js | 10 +- .../cloudfunctions/uni-id-co/lang/zh-hans.js | 10 +- .../module/account/get-realname-info.js | 45 +++++++ .../uni-id-co/module/account/index.js | 3 +- .../get-auth-result.js | 120 ++++++++++++++++++ .../get-certify-id.js | 105 +++++++++++++++ .../module/facial-recognition-verify/index.js | 4 + .../cloudfunctions/uni-id-co/package.json | 5 +- .../database/opendb-frv-logs.schema.json | 44 +++++++ 15 files changed, 564 insertions(+), 20 deletions(-) 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/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 a23dee4..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,8 +8,11 @@ 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', username: 'username', mobile: 'mobile', email: 'email', @@ -22,7 +25,8 @@ const USER_IDENTIFIER = { 'qq_openid.app': 'qq-account', 'qq_openid.mp': 'qq-account', ali_openid: 'alipay-account', - apple_openid: 'alipay-account' + apple_openid: 'alipay-account', + identities: 'idp' } const USER_STATUS = { @@ -76,6 +80,15 @@ 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 = { db, dbCmd, @@ -83,10 +96,13 @@ module.exports = { verifyCollection, deviceCollection, openDataCollection, + frvLogsCollection, USER_IDENTIFIER, USER_STATUS, CAPTCHA_SCENE, LOG_TYPE, SMS_SCENE, - EMAIL_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 d026fe7..ec3d38e 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 @@ -38,7 +38,15 @@ const ERROR = { UNBIND_PASSWORD_NOT_EXISTS: 'uni-id-unbind-password-not-exists', UNBIND_MOBILE_NOT_EXISTS: 'uni-id-unbind-mobile-not-exists', UNSUPPORTED_REQUEST: 'uni-id-unsupported-request', - ILLEGAL_REQUEST: 'uni-id-illegal-request' + ILLEGAL_REQUEST: 'uni-id-illegal-request', + 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..098016d --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/common/sensitive-aes-cipher.js @@ -0,0 +1,57 @@ +const crypto = require('crypto') + +function checkSecret (secret) { + if (!secret) { + throw { + errCode: '请在config.json中配置sensitiveInfoEncryptSecret字段' + } + } + + if (secret.length < 32) { + throw { + errCode: 'sensitiveInfoEncryptSecret字段长度不能小于32位' + } + } +} +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..9eb51a5 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,51 @@ function isMatchUserApp (userAppList, matchAppList) { return userAppList.some(item => matchAppList.includes(item)) } +function checkIdCard (idCardNumber) { + if (!idCardNumber) 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')] + + 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 getCurrentDate () { + const date = new Date() + const year = date.getFullYear() + const month = date.getMonth() + 1 + const day = date.getDate() + + return new Date(`${year}/${month}/${day}`) +} module.exports = { getType, isValidString, @@ -210,5 +255,9 @@ module.exports = { getVerifyCode, coverMobile, getNonceStr, - isMatchUserApp + isMatchUserApp, + checkIdCard, + catchAwait, + dataDesensitization, + getCurrentDate } 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 69013a4..3faf877 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,11 +83,15 @@ const { const { getSupportedLoginType } = require('./module/dev/index') - const { externalRegister, - externalLogin + externalLogin, + updateUserInfoByExternal } = require('./module/external') +const { + getFrvCertifyId, + getFrvAuthResult +} = require('./module/facial-recognition-verify') module.exports = { async _before () { @@ -131,6 +138,22 @@ 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 (!/^[\u4e00-\u9fa5]+$/.test(realName)) { + return { + errCode: ERROR.INVALID_REAL_NAME + } + } + }) /** * 示例:覆盖密码验证规则 */ @@ -602,15 +625,61 @@ module.exports = { */ setPwd, /** - * 外部用户注册,将自身系统的用户账号导入uniId,为其创建一个对应uniId的账号(unieid),使得该账号可以使用依赖uniId的系统及功能。 + * 外部注册用户 * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#external-register - * @returns + * @param {object} params + * @param {string} params.externalUid 业务系统的用户id + * @param {string} params.nickname 昵称 + * @param {string} params.gender 性别 + * @param {string} params.avatar 头像 + * @returns {object} */ externalRegister, /** - * 外部用户登录,使用unieid即可登录 + * 外部用户登录 * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#external-login + * @param {object} params + * @param {string} params.userId uni-id体系用户id + * @param {string} params.externalUid 业务系统的用户id + * @returns {object} + */ + externalLogin, + /** + * 使用 userId 或 externalUid 获取用户信息 + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#external-update-userinfo + * @param {object} params + * @param {string} params.userId uni-id体系的用户id + * @param {string} params.externalUid 业务系统的用户id + * @param {string} params.nickname 昵称 + * @param {string} params.gender 性别 + * @param {string} params.avatar 头像 + * @returns {object} + */ + 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 - * */ - externalLogin + */ + 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 d329d01..6248e38 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 @@ -43,7 +43,15 @@ const sentence = { 'uni-id-unbind-mobile-not-exists': 'This is the only way to login at the moment, please bind your phone number and then try to unbind', '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-illegal-request': 'Illegal request', + '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 a42f06d..bfffac7 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 @@ -43,7 +43,15 @@ const sentence = { 'uni-id-unbind-mobile-not-exists': '这是当前唯一登录方式,请绑定手机号后再尝试解绑', 'uni-id-unbind-password-not-exists': '请先设置密码在尝试解绑', 'uni-id-unsupported-request': '不支持的请求方式', - 'uni-id-illegal-request': '非法请求' + 'uni-id-illegal-request': '非法请求', + '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': '当日实名认证次数已达上限' } 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..f9e3e11 --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/facial-recognition-verify/get-auth-result.js @@ -0,0 +1,120 @@ +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 + * @param {String} params.needAlivePhoto 是否获取认证照片,Y_O (原始图片)、Y_M(虚化,背景马赛克)、N(不返图) + * @returns + */ +module.exports = async function (params) { + const schema = { + certifyId: 'string', + needAlivePhoto: { + required: false, + type: 'string' + } + } + + this.middleware.validate(params, schema) + + const { uid } = this.authInfo + const { certifyId, needAlivePhoto } = params + + 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 frvManager = uniCloud.getFacialRecognitionVerifyManager({ + requestId: this.getUniCloudRequestId() + }) + + const [error, res] = await catchAwait(frvManager.getAuthResult({ + certifyId, + needAlivePhoto + })) + + if (error) { + console.log(ERROR.UNKNOWN_ERROR, 'error: ', error) + throw error + } + + if (res.authState === 'PROCESSING') { + throw { + errCode: ERROR.FRV_PROCESSING + } + } + + if (res.authState === 'FAIL') { + await frvLogsCollection.where({ + certify_id: certifyId + }).update({ + status: REAL_NAME_STATUS.CERTIFY_FAILED + }) + + throw { + errCode: ERROR.FRV_FAIL + } + } + + if (res.authState === 'SUCCESS') { + 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 + } + } + + if (res.base64Photo) { + const { + fileID + } = await uniCloud.uploadFile({ + cloudPath: `user/id-card/${uid}.bin`, + fileContent: Buffer.from(encryptData.call(this, res.base64Photo)) + }) + 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)) + } + } + + console.log(ERROR.UNKNOWN_ERROR, 'source res: ', res) + + throw { + errCode: ERROR.UNKNOWN_ERROR + } +} 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..101834d --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/facial-recognition-verify/get-certify-id.js @@ -0,0 +1,105 @@ +const { userCollection, REAL_NAME_STATUS, frvLogsCollection, dbCmd } = require('../../common/constants') +const { ERROR } = require('../../common/error') +const { encryptData } = require('../../common/sensitive-aes-cipher') +const { getCurrentDate } = require('../../common/utils') + +/** + * 获取认证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 + */ +module.exports = async function (params) { + const schema = { + realName: 'realName', + idCard: 'idCard' + } + + this.middleware.validate(params, schema) + + const { realName: originalRealName, idCard: originalIdCard } = params + const realName = encryptData.call(this, originalRealName) + const idCard = encryptData.call(this, originalIdCard) + + const { uid } = this.authInfo + const idCardCertifyLimit = this.config.idCardCertifyLimit || 1 + const readNameCertifyLimit = this.config.readNameCertifyLimit || 5 + 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 frvLogs = await frvLogsCollection.where({ + real_name: realName, + identity: idCard, + status: REAL_NAME_STATUS.WAITING_CERTIFIED, + created_date: dbCmd.gt(Date.now() - (22 * 60 * 60 * 1000)) + }).get() + + // 用户发起了人脸识别但未刷脸并且 certifyId 还在有效期内就还可以用上次的 certifyId + if (frvLogs.data.length) { + const record = frvLogs.data[0] + if (realName === record.real_name && idCard === record.identity) { + return { + certifyId: record.certify_id + } + } + } + + const userFrvLogs = await frvLogsCollection.where({ + user_id: uid, + created_date: dbCmd.gt(getCurrentDate().getTime()) + }).get() + + // 限制用户每日认证次数 + if (userFrvLogs.data && userFrvLogs.data.length >= readNameCertifyLimit) { + throw { + errCode: ERROR.REAL_NAME_VERIFY_UPPER_LIMIT + } + } + + const frvManager = uniCloud.getFacialRecognitionVerifyManager({ + requestId: this.getUniCloudRequestId() + }) + + const res = await frvManager.getCertifyId({ + realName: originalRealName, + idCard: originalIdCard + }) + + 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() + }) + + 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 e4b8b33..4091c9b 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 @@ -14,10 +14,11 @@ "uni-open-bridge-common": "file:../../../../uni-open-bridge-common/uniCloud/cloudfunctions/common/uni-open-bridge-common" }, "extensions": { + "uni-cloud-redis": {}, "uni-cloud-sms": {}, - "uni-cloud-redis": {} + "uni-cloud-verify": {} }, "cloudfunction-config": { "keepRunningAfterReturn": false } -} +} \ No newline at end of file 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