提交 7382e31f 编写于 作者: C chenruilong

Merge branch 'realname'

# Conflicts:
#	uni_modules/uni-id-pages/pages/userinfo/userinfo.vue
#	uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/middleware/verify-request-sign.js
export default 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()
}
\ No newline at end of file
......@@ -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);
......
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
}
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1675667510055" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4003" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M807.936 106.656h-76a24.32 24.32 0 0 0-17.92 7.936 27.744 27.744 0 0 0-7.424 19.104c0 6.944 2.464 13.792 7.424 19.104a24.32 24.32 0 0 0 17.92 7.904h76v81.088c0 6.944 2.432 13.76 7.424 19.104a24.32 24.32 0 0 0 35.808 0 27.744 27.744 0 0 0 7.424-19.104V160.704c0-29.824-22.72-54.048-50.656-54.048zM833.248 512a25.12 25.12 0 0 0-17.92 7.392 25.12 25.12 0 0 0-7.392 17.92v76h-76a25.12 25.12 0 0 0-17.92 7.424c-1.344 1.344-2.08 3.072-3.072 4.704-28.576-27.52-60.704-50.112-96.256-65.152 72.192-43.136 117.888-126.08 103.872-219.296-13.216-87.456-81.056-160.576-167.648-178.656a228.16 228.16 0 0 0-46.944-4.896 217.056 217.056 0 0 0-217.12 217.12c0 79.264 42.976 147.936 106.368 185.824-35.456 15.04-67.648 37.632-96.256 65.152-0.96-1.632-1.696-3.36-3.072-4.704a25.12 25.12 0 0 0-17.92-7.424H200v-76a25.12 25.12 0 0 0-7.424-17.92 25.12 25.12 0 0 0-17.92-7.424 25.12 25.12 0 0 0-17.92 7.424 25.12 25.12 0 0 0-7.392 17.92v76c0 27.936 22.72 50.656 50.656 50.656H262.4c-42.336 54.816-71.712 123.488-80.96 200.192-3.424 28.224 19.104 53.12 47.488 53.12h550.048c28.416 0 50.848-24.96 47.488-53.12-9.216-76.8-38.624-145.472-80.96-200.288h62.4c27.968 0 50.688-22.72 50.688-50.656V537.28a25.12 25.12 0 0 0-7.424-17.92 25.12 25.12 0 0 0-17.92-7.392zM174.72 268.8a24.32 24.32 0 0 0 17.888-7.904 27.744 27.744 0 0 0 7.424-19.104V160.704h76a24.32 24.32 0 0 0 17.92-7.904 27.744 27.744 0 0 0 7.392-19.104 27.744 27.744 0 0 0-7.424-19.104 24.32 24.32 0 0 0-17.92-7.936H200c-27.968 0-50.656 24.224-50.656 54.08v81.056c0 6.944 2.432 13.76 7.392 19.104a24.32 24.32 0 0 0 17.92 7.904z" fill="#72a7ff" p-id="4004"></path></svg>
\ No newline at end of file
<template>
<view class="uni-content">
<template v-if="verifyFail">
<view class="face-icon">
<image src="./face-verify-icon.svg" class="face-icon-image" />
</view>
<view class="error-title">{{verifyFailTitle}}</view>
<view class="error-description">{{verifyFailContent}}</view>
<button type="primary" @click="retry" v-if="verifyFailCode !== 10013">重新开始验证</button>
<button type="primary" @click="retry" v-else>返回</button>
<view class="dev-tip" v-if="isDev">请在控制台查看详细错误(此提示仅在开发环境展示)</view>
</template>
<template v-else>
<view class="self-required" v-if="realName">确认<text class="name">{{realNameDesensitized}}</text>本人操作</view>
<view class="face-icon">
<image src="./face-verify-icon.svg" class="face-icon-image" />
</view>
<view class="suggestion">
<view class="text">温馨提示</view>
<view class="text" v-for="text in suggestion">
<text class="dot"></text>
<text>{{text}}</text>
</view>
</view>
<view class="footer">
<button type="primary" :disabled="verifyButtonDisabled" @click="getCertifyId">开始人脸识别</button>
</view>
</template>
</view>
</template>
<script>
import {
mutations
} from '@/uni_modules/uni-id-pages/common/store.js'
import checkIdCard from '@/uni_modules/uni-id-pages/common/check-id-card.js'
const uniIdCo = uniCloud.importObject('uni-id-co')
const tempFrvInfoKey = 'uni-id-pages-temp-frv'
export default {
data() {
return {
realName: "",
idCard: "",
certifyId: "",
suggestion: [
"请正对屏幕并使脸位于取景框内",
"请保持光线充足,避免光照过强或过弱",
"请根据画面提示完成动作检测",
"人脸识别过程中请保持手机稳定",
"请注意拍摄角度,距离摄像头不要过远或过近"
],
verifyFail: false,
verifyFailCode: 0,
verifyFailTitle: "",
verifyFailContent: ""
}
},
computed: {
realNameDesensitized() {
const firstChar = this.realName.slice(0, -1)
const lastChar = this.realName.slice(-1)
return Array.from(new Array(firstChar.length), (v) => '*').join('') + lastChar
},
verifyButtonDisabled() {
return !this.realName || !this.idCard
},
isDev () {
return process.env.NODE_ENV === 'development'
}
},
onLoad(e) {
this.realName = e.realName || ''
this.idCard = e.idCard || ''
},
methods: {
async getCertifyId() {
if (!this.realName || !this.idCard) {
uni.showModal({
title: "验证失败",
content: "缺少姓名或身份证号码",
showCancel: false
})
return
}
if (!checkIdCard(this.idCard)) {
uni.showToast({
title: "身份证不合法",
icon: "none"
})
return
}
if (!/^[\u4e00-\u9fa5]+$/.test(this.realName)) {
uni.showToast({
title: "姓名只能是汉字",
icon: "none"
})
return
}
const res = await uniIdCo.getFrvCertifyId({
realName: this.realName,
idCard: this.idCard
})
this.certifyId = res.certifyId
this.startFacialRecognitionVerify()
uni.setStorage({
key: tempFrvInfoKey,
data: {
realName: this.realName,
idCard: this.idCard
}
});
},
startFacialRecognitionVerify() {
// 每次刷脸重置状态
this.verifyFailCode = 0
this.verifyFail = false
this.verifyFailTitle = ''
this.verifyFailContent = ''
// #ifdef APP
uni.startFacialRecognitionVerify({
certifyId: this.certifyId,
quitAlertMessage: " ",
quitAlertTitle: "确定退出吗?",
success: (e) => {
this.getFrvAuthResult()
},
fail: (e) => {
let title = "验证失败"
let content
console.log(`[frv-debug] certifyId auth error: certifyId -> ${this.certifyId}, error -> ${JSON.stringify(e, null, 4)}`)
switch (e.errCode) {
case 10001:
content = '认证ID为空'
break
case 10010:
title = '刷脸异常'
content = e.cause.message || '错误代码: 10010'
break
case 10011:
title = '验证中断'
content = e.cause.message || '错误代码: 10011'
break
case 10012:
content = '网络异常'
break
case 10013:
this.verifyFailCode = e.errCode
this.verifyFailContent = e.cause.message || '错误代码: 10013'
this.getFrvAuthResult()
console.log(`[frv-debug] 刷脸失败, certifyId -> ${this.certifyId}, 如在开发环境请检查用户的姓名、身份证号与刷脸用户是否为同一用户。如遇到认证ID已使用请检查opendb-frv-logs表中certifyId状态`)
return
case 10020:
content = '设备设置时间异常'
break
default:
title = ''
content = `验证未知错误 (${e.errCode})`
break
}
this.verifyFail = true
this.verifyFailCode = e.errCode
this.verifyFailTitle = title
this.verifyFailContent = content
}
})
// #endif
},
async getFrvAuthResult() {
const uniIdCo = uniCloud.importObject('uni-id-co', {
customUI: true
})
try {
uni.showLoading({
title: "验证中...",
mask: false
})
const res = await uniIdCo.getFrvAuthResult({
certifyId: this.certifyId,
needAlivePhoto: 'Y_O'
})
const {
errCode,
...rest
} = res
if (this.verifyFailContent) {
console.log(`[frv-debug] 客户端刷脸失败,由实人认证服务查询具体原因,原因:${this.verifyFailContent}`)
}
uni.showModal({
content: "实名认证成功",
showCancel: false,
success: () => {
mutations.setUserInfo({
realNameAuth: rest
})
uni.navigateBack(-1)
}
})
uni.removeStorage({
key: tempFrvInfoKey
})
} catch (e) {
this.verifyFail = true
this.verifyFailTitle = e.errMsg
console.error(JSON.stringify(e));
} finally {
uni.hideLoading()
}
},
retry() {
if (this.verifyFailCode !== 10013) {
this.startFacialRecognitionVerify()
} else {
uni.navigateBack(-1)
}
}
},
}
</script>
<style lang="scss">
@import "@/uni_modules/uni-id-pages/common/login-page.scss";
.self-required {
margin-top: 20px;
font-size: 18px;
font-weight: bold;
text-align: center;
color: #333333;
.name {
color: #2979ff
}
}
.face-icon {
width: 100px;
height: 100px;
margin: 50px auto 30px;
}
.face-icon-image {
width: 100%;
height: 100%;
display: block;
}
.suggestion {
color: #999999;
font-size: 13px;
margin-bottom: 20px;
.text {
line-height: 20px;
display: flex;
align-items: center;
.dot {
width: 5px;
height: 5px;
background: #2979ff;
border-radius: 50%;
margin-right: 5px;
}
}
}
.error-title {
font-size: 18px;
text-align: center;
font-weight: bold;
}
.error-description {
font-size: 13px;
color: #999999;
margin: 10px 0 20px;
text-align: center;
}
.dev-tip {
margin-top: 20px;
font-size: 13px;
color: #999;
text-align: center;
}
</style>
<template>
<view>
<template v-if="isCertify">
<uni-list>
<uni-list-item class="item" title="姓名" :rightText="userInfo.realNameAuth.realName"></uni-list-item>
<uni-list-item class="item" title="身份证号码" :rightText="userInfo.realNameAuth.identity"></uni-list-item>
</uni-list>
</template>
<template v-else>
<view class="uni-content">
<text class="title">实名认证</text>
<uni-forms>
<uni-forms-item name="realName">
<uni-easyinput placeholder="姓名" class="input-box" v-model="realName" :clearable="false">
</uni-easyinput>
</uni-forms-item>
<uni-forms-item name="idCard">
<uni-easyinput placeholder="身份证号码" class="input-box" v-model="idCard" :clearable="false">
</uni-easyinput>
</uni-forms-item>
</uni-forms>
<uni-id-pages-agreements scope="realNameVerify" ref="agreements" style="margin-bottom: 20px;">
</uni-id-pages-agreements>
<button type="primary" :disabled="!certifyIdNext" @click="goToFaceVerifyPage">确定</button>
</view>
</template>
</view>
</template>
<script>
import checkIdCard from '@/uni_modules/uni-id-pages/common/check-id-card.js'
import mixin from '@/uni_modules/uni-id-pages/common/login-page.mixin.js';
import {store} from '@/uni_modules/uni-id-pages/common/store.js'
const uniIdCo = uniCloud.importObject('uni-id-co')
const tempFrvInfoKey = 'uni-id-pages-temp-frv'
export default {
mixins: [mixin],
data() {
return {
realName: '',
idCard: ''
}
},
computed: {
userInfo() {
return store.userInfo
},
certifyIdNext() {
return Boolean(this.realName) && Boolean(this.idCard) && (this.needAgreements && this.agree)
},
isCertify() {
return this.userInfo.realNameAuth && this.userInfo.realNameAuth.authStatus === 2
}
},
onLoad() {
const tempFrvInfo = uni.getStorageSync(tempFrvInfoKey);
if (tempFrvInfo) {
this.realName = tempFrvInfo.realName
this.idCard = tempFrvInfo.idCard
}
},
methods: {
async goToFaceVerifyPage() {
if (!this.certifyIdNext) return
// #ifndef APP
return uni.showModal({
content: "暂不支持实名认证",
showCancel: false
})
// #endif
if (!checkIdCard(this.idCard)) {
uni.showToast({
title: "身份证不合法",
icon: "none"
})
return
}
if (!/^[\u4e00-\u9fa5]+$/.test(this.realName)) {
uni.showToast({
title: "姓名只能是汉字",
icon: "none"
})
return
}
uni.navigateTo({
url: '/uni_modules/uni-id-pages/pages/userinfo/face-verify/face-verify?realName=' + this
.realName + '&idCard=' + this.idCard
});
}
}
}
</script>
<style lang="scss">
@import "@/uni_modules/uni-id-pages/common/login-page.scss";
.checkbox-box,
.uni-label-pointer {
align-items: center;
display: flex;
flex-direction: row;
}
.item {
flex-direction: row;
}
.text {
line-height: 26px;
}
.checkbox-box ::v-deep .uni-checkbox-input {
border-radius: 100%;
}
.checkbox-box ::v-deep .uni-checkbox-input.uni-checkbox-input-checked {
border-color: $uni-color-primary;
color: #FFFFFF !important;
background-color: $uni-color-primary;
}
.agreements {
margin-bottom: 20px;
}
</style>
<!-- 用户资料页 -->
<template>
<view class="uni-content">
<view class="avatar">
<uni-id-pages-avatar width="260rpx" height="260rpx"></uni-id-pages-avatar>
</view>
<uni-list>
<uni-list-item class="item" @click="setNickname('')" title="昵称" :rightText="userInfo.nickname||'未设置'" link>
</uni-list-item>
<uni-list-item class="item" @click="bindMobile" title="手机号" :rightText="userInfo.mobile||'未绑定'" link>
</uni-list-item>
<uni-list-item v-if="userInfo.email" class="item" title="电子邮箱" :rightText="userInfo.email">
</uni-list-item>
<uni-list-item v-if="hasPwd" class="item" @click="changePassword" title="修改密码" link>
</uni-list-item>
</uni-list>
<!-- #ifndef MP -->
<uni-list class="mt10">
<uni-list-item @click="deactivate" title="注销账号" link="navigateTo"></uni-list-item>
</uni-list>
<!-- #endif -->
<uni-popup ref="dialog" type="dialog">
<uni-popup-dialog mode="input" :value="userInfo.nickname" @confirm="setNickname" :inputType="setNicknameIng?'nickname':'text'"
title="设置昵称" placeholder="请输入要设置的昵称">
</uni-popup-dialog>
</uni-popup>
<uni-id-pages-bind-mobile ref="bind-mobile-by-sms" @success="bindMobileSuccess"></uni-id-pages-bind-mobile>
<template v-if="showLoginManage">
<button v-if="userInfo._id" @click="logout">退出登录</button>
<button v-else @click="login">去登录</button>
</template>
</view>
</template>
<script>
const uniIdCo = uniCloud.importObject("uni-id-co")
import {
store,
mutations
} from '@/uni_modules/uni-id-pages/common/store.js'
export default {
computed: {
userInfo() {
return store.userInfo
}
},
data() {
return {
univerifyStyle: {
authButton: {
"title": "本机号码一键绑定", // 授权按钮文案
},
otherLoginButton: {
"title": "其他号码绑定",
}
},
// userInfo: {
// mobile:'',
// nickname:''
// },
hasPwd: false,
<!-- 用户资料页 -->
<template>
<view class="uni-content">
<view class="avatar">
<uni-id-pages-avatar width="260rpx" height="260rpx"></uni-id-pages-avatar>
</view>
<uni-list>
<uni-list-item class="item" @click="setNickname('')" title="昵称" :rightText="userInfo.nickname||'未设置'" link>
</uni-list-item>
<uni-list-item class="item" @click="bindMobile" title="手机号" :rightText="userInfo.mobile||'未绑定'" link>
</uni-list-item>
<uni-list-item v-if="userInfo.email" class="item" title="电子邮箱" :rightText="userInfo.email">
</uni-list-item>
<!-- #ifdef APP -->
<!-- 如未开通实人认证服务,可以将实名认证入口注释 -->
<uni-list-item class="item" @click="realNameVerify" title="实名认证" :rightText="realNameStatus !== 2 ? '未认证': '已认证'" link>
</uni-list-item>
<!-- #endif -->
<uni-list-item v-if="hasPwd" class="item" @click="changePassword" title="修改密码" link>
</uni-list-item>
</uni-list>
<!-- #ifndef MP -->
<uni-list class="mt10">
<uni-list-item @click="deactivate" title="注销账号" link="navigateTo"></uni-list-item>
</uni-list>
<!-- #endif -->
<uni-popup ref="dialog" type="dialog">
<uni-popup-dialog mode="input" :value="userInfo.nickname" @confirm="setNickname" :inputType="setNicknameIng?'nickname':'text'"
title="设置昵称" placeholder="请输入要设置的昵称">
</uni-popup-dialog>
</uni-popup>
<uni-id-pages-bind-mobile ref="bind-mobile-by-sms" @success="bindMobileSuccess"></uni-id-pages-bind-mobile>
<template v-if="showLoginManage">
<button v-if="userInfo._id" @click="logout">退出登录</button>
<button v-else @click="login">去登录</button>
</template>
</view>
</template>
<script>
const uniIdCo = uniCloud.importObject("uni-id-co")
import {
store,
mutations
} from '@/uni_modules/uni-id-pages/common/store.js'
export default {
computed: {
userInfo() {
return store.userInfo
},
realNameStatus () {
if (!this.userInfo.realNameAuth) {
return 0
}
return this.userInfo.realNameAuth.authStatus
}
},
data() {
return {
univerifyStyle: {
authButton: {
"title": "本机号码一键绑定", // 授权按钮文案
},
otherLoginButton: {
"title": "其他号码绑定",
}
},
// userInfo: {
// mobile:'',
// nickname:''
// },
hasPwd: false,
showLoginManage: false ,//通过页面传参隐藏登录&退出登录按钮
setNicknameIng:false
}
},
async onShow() {
this.univerifyStyle.authButton.title = "本机号码一键绑定"
this.univerifyStyle.otherLoginButton.title = "其他号码绑定"
},
async onLoad(e) {
if (e.showLoginManage) {
this.showLoginManage = true //通过页面传参隐藏登录&退出登录按钮
}
//判断当前用户是否有密码,否则就不显示密码修改功能
let res = await uniIdCo.getAccountInfo()
this.hasPwd = res.isPasswordSet
},
methods: {
login() {
uni.navigateTo({
url: '/uni_modules/uni-id-pages/pages/login/login-withoutpwd',
complete: (e) => {
// console.log(e);
}
})
},
logout() {
mutations.logout()
},
bindMobileSuccess() {
mutations.updateUserInfo()
},
changePassword() {
uni.navigateTo({
url: '/uni_modules/uni-id-pages/pages/userinfo/change_pwd/change_pwd',
complete: (e) => {
// console.log(e);
}
})
},
bindMobile() {
// #ifdef APP-PLUS
uni.preLogin({
provider: 'univerify',
success: this.univerify(), //预登录成功
fail: (res) => { // 预登录失败
// 不显示一键登录选项(或置灰)
console.log(res)
this.bindMobileBySmsCode()
}
})
// #endif
// #ifdef MP-WEIXIN
this.$refs['bind-mobile-by-sms'].open()
// #endif
// #ifdef H5
//...去用验证码绑定
this.bindMobileBySmsCode()
// #endif
},
univerify() {
uni.login({
"provider": 'univerify',
"univerifyStyle": this.univerifyStyle,
success: async e => {
uniIdCo.bindMobileByUniverify(e.authResult).then(res => {
mutations.updateUserInfo()
}).catch(e => {
console.log(e);
}).finally(e => {
// console.log(e);
uni.closeAuthView()
})
},
fail: (err) => {
console.log(err);
if (err.code == '30002' || err.code == '30001') {
this.bindMobileBySmsCode()
}
}
})
},
bindMobileBySmsCode() {
uni.navigateTo({
url: './bind-mobile/bind-mobile'
})
},
setNickname(nickname) {
if (nickname) {
mutations.updateUserInfo({
nickname
setNicknameIng:false
}
},
async onShow() {
this.univerifyStyle.authButton.title = "本机号码一键绑定"
this.univerifyStyle.otherLoginButton.title = "其他号码绑定"
},
async onLoad(e) {
if (e.showLoginManage) {
this.showLoginManage = true //通过页面传参隐藏登录&退出登录按钮
}
//判断当前用户是否有密码,否则就不显示密码修改功能
let res = await uniIdCo.getAccountInfo()
this.hasPwd = res.isPasswordSet
},
methods: {
login() {
uni.navigateTo({
url: '/uni_modules/uni-id-pages/pages/login/login-withoutpwd',
complete: (e) => {
// console.log(e);
}
})
},
logout() {
mutations.logout()
},
bindMobileSuccess() {
mutations.updateUserInfo()
},
changePassword() {
uni.navigateTo({
url: '/uni_modules/uni-id-pages/pages/userinfo/change_pwd/change_pwd',
complete: (e) => {
// console.log(e);
}
})
},
bindMobile() {
// #ifdef APP-PLUS
uni.preLogin({
provider: 'univerify',
success: this.univerify(), //预登录成功
fail: (res) => { // 预登录失败
// 不显示一键登录选项(或置灰)
console.log(res)
this.bindMobileBySmsCode()
}
})
// #endif
// #ifdef MP-WEIXIN
this.$refs['bind-mobile-by-sms'].open()
// #endif
// #ifdef H5
//...去用验证码绑定
this.bindMobileBySmsCode()
// #endif
},
univerify() {
uni.login({
"provider": 'univerify',
"univerifyStyle": this.univerifyStyle,
success: async e => {
uniIdCo.bindMobileByUniverify(e.authResult).then(res => {
mutations.updateUserInfo()
}).catch(e => {
console.log(e);
}).finally(e => {
// console.log(e);
uni.closeAuthView()
})
},
fail: (err) => {
console.log(err);
if (err.code == '30002' || err.code == '30001') {
this.bindMobileBySmsCode()
}
}
})
},
bindMobileBySmsCode() {
uni.navigateTo({
url: './bind-mobile/bind-mobile'
})
},
setNickname(nickname) {
if (nickname) {
mutations.updateUserInfo({
nickname
})
this.setNicknameIng = false
this.$refs.dialog.close()
this.setNicknameIng = false
this.$refs.dialog.close()
} else {
this.setNicknameIng = true
this.$refs.dialog.open()
}
},
deactivate() {
uni.navigateTo({
url: "/uni_modules/uni-id-pages/pages/userinfo/deactivate/deactivate"
})
},
async bindThirdAccount(provider) {
const uniIdCo = uniCloud.importObject("uni-id-co")
const bindField = {
weixin: 'wx_openid',
alipay: 'ali_openid',
apple: 'apple_openid',
qq: 'qq_openid'
} [provider.toLowerCase()]
if (this.userInfo[bindField]) {
await uniIdCo['unbind' + provider]()
await mutations.updateUserInfo()
} else {
uni.login({
provider: provider.toLowerCase(),
onlyAuthorize: true,
success: async e => {
const res = await uniIdCo['bind' + provider]({
code: e.code
})
if (res.errCode) {
uni.showToast({
title: res.errMsg || '绑定失败',
duration: 3000
})
}
await mutations.updateUserInfo()
},
fail: async (err) => {
console.log(err);
uni.hideLoading()
}
})
}
}
}
}
</script>
<style lang="scss" scoped>
@import "@/uni_modules/uni-id-pages/common/login-page.scss";
.uni-content {
padding: 0;
}
/* #ifndef APP-NVUE */
view {
display: flex;
box-sizing: border-box;
flex-direction: column;
}
@media screen and (min-width: 690px) {
.uni-content {
padding: 0;
max-width: 690px;
margin-left: calc(50% - 345px);
border: none;
max-height: none;
border-radius: 0;
box-shadow: none;
}
}
/* #endif */
.avatar {
align-items: center;
justify-content: center;
margin: 22px 0;
width: 100%;
}
.item {
flex: 1;
flex-direction: row;
justify-content: space-between;
align-items: center;
}
button {
margin: 10%;
margin-top: 40px;
border-radius: 0;
background-color: #FFFFFF;
width: 80%;
}
.mt10 {
margin-top: 10px;
}
this.setNicknameIng = true
this.$refs.dialog.open()
}
},
deactivate() {
uni.navigateTo({
url: "/uni_modules/uni-id-pages/pages/userinfo/deactivate/deactivate"
})
},
async bindThirdAccount(provider) {
const uniIdCo = uniCloud.importObject("uni-id-co")
const bindField = {
weixin: 'wx_openid',
alipay: 'ali_openid',
apple: 'apple_openid',
qq: 'qq_openid'
} [provider.toLowerCase()]
if (this.userInfo[bindField]) {
await uniIdCo['unbind' + provider]()
await mutations.updateUserInfo()
} else {
uni.login({
provider: provider.toLowerCase(),
onlyAuthorize: true,
success: async e => {
const res = await uniIdCo['bind' + provider]({
code: e.code
})
if (res.errCode) {
uni.showToast({
title: res.errMsg || '绑定失败',
duration: 3000
})
}
await mutations.updateUserInfo()
},
fail: async (err) => {
console.log(err);
uni.hideLoading()
}
})
}
},
realNameVerify () {
uni.navigateTo({
url: "/uni_modules/uni-id-pages/pages/userinfo/realname-verify/realname-verify"
})
}
}
}
</script>
<style lang="scss" scoped>
@import "@/uni_modules/uni-id-pages/common/login-page.scss";
.uni-content {
padding: 0;
}
/* #ifndef APP-NVUE */
view {
display: flex;
box-sizing: border-box;
flex-direction: column;
}
@media screen and (min-width: 690px) {
.uni-content {
padding: 0;
max-width: 690px;
margin-left: calc(50% - 345px);
border: none;
max-height: none;
border-radius: 0;
box-shadow: none;
}
}
/* #endif */
.avatar {
align-items: center;
justify-content: center;
margin: 22px 0;
width: 100%;
}
.item {
flex: 1;
flex-direction: row;
justify-content: space-between;
align-items: center;
}
button {
margin: 10%;
margin-top: 40px;
border-radius: 0;
background-color: #FFFFFF;
width: 80%;
}
.mt10 {
margin-top: 10px;
}
</style>
......@@ -95,6 +95,18 @@
"navigationBarTitleText": "设置密码",
"enablePullDownRefresh": false
}
},{
"path": "uni_modules/uni-id-pages/pages/userinfo/realname-verify/realname-verify",
"style": {
"navigationBarTitleText": "实名认证",
"enablePullDownRefresh": false
}
},{
"path": "uni_modules/uni-id-pages/pages/userinfo/face-verify/face-verify",
"style": {
"navigationBarTitleText": "人脸识别验证",
"enablePullDownRefresh": false
}
}
]
}
......@@ -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
}
......@@ -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) {
......
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
}
......@@ -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
}
......@@ -77,5 +77,14 @@ module.exports = {
},
setPwd: {
auth: true
},
getFrvCertifyId: {
auth: true
},
getFrvAuthResult: {
auth: true
},
getRealNameInfo: {
auth: true
}
}
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
}
......@@ -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 = {
......
......@@ -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 = {
......
const {
db,
dbCmd,
userCollection
} = require('../../common/constants')
......@@ -85,6 +84,8 @@ function getUserQueryCondition (userRecord = {}) {
username: username.toLowerCase()
})
}
} else if (key === 'identities') {
queryItem.identities = dbCmd.elemMatch(value)
}
condition.push(queryItem)
}
......
......@@ -2,16 +2,18 @@ const crypto = require('crypto')
const { ERROR } = require('../common/error')
const needSignFunctions = new Set([
'externalRegister',
'externalLogin'
'externalLogin',
'updateUserInfoByExternal'
])
module.exports = function () {
const methodName = this.getMethodName()
const { source } = this.getUniversalClientInfo()
// 指定接口需要鉴权
if (!needSignFunctions.has(methodName)) return
// 非 HTTP 方式请求不需要鉴权
// 非 HTTP 方式请求拒绝访问
if (source !== 'http') {
throw {
errCode: ERROR.ILLEGAL_REQUEST
......
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
}
}
......@@ -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')
}
......@@ -83,10 +83,10 @@ module.exports = async function (params = {}) {
username,
dcloud_appid: authorizedApp,
nickname,
role: role,
role,
mobile,
email,
tags: tags,
tags,
status
}
......@@ -132,7 +132,6 @@ module.exports = async function (params = {}) {
await userCollection.doc(uid).update(realData)
return {
errCode: 0
}
......
module.exports = {
externalRegister: require('./register'),
externalLogin: require('./login')
externalLogin: require('./login'),
updateUserInfoByExternal: require('./update-user-info')
}
const { preLogin, postLogin } = require('../../lib/utils/login')
const { EXTERNAL_DIRECT_CONNECT_PROVIDER } = require('../../common/constants')
const { ERROR } = require('../../common/error')
/**
* 外部用户登录
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#external-login
* @param {object} params
* @param {string} params.uid uni-id体系用户id
* @param {string} params.externalUid 业务系统的用户id
* @returns {object}
*/
module.exports = async function (params = {}) {
const schema = {
unieid: 'username'
uid: {
required: false,
type: 'string'
},
externalUid: {
required: false,
type: 'string'
}
}
this.middleware.validate(params, schema)
const {
unieid
uid,
externalUid
} = params
const user = await preLogin.call(this, {
user: {
username: unieid
if (!uid && !externalUid) {
throw {
errCode: ERROR.PARAM_REQUIRED,
errMsgValue: {
param: 'uid or externalUid'
}
}
}
let query
if (uid) {
query = {
_id: uid
}
} else {
query = {
identities: {
provider: EXTERNAL_DIRECT_CONNECT_PROVIDER,
uid: externalUid
}
}
}
const user = await preLogin.call(this, {
user: query
})
const result = await postLogin.call(this, {
......@@ -24,6 +63,6 @@ module.exports = async function (params = {}) {
return {
errCode: result.errCode,
newToken: result.newToken,
unieid
uid: result.uid
}
}
const { preRegister, postRegister } = require('../../lib/utils/register')
const { EXTERNAL_DIRECT_CONNECT_PROVIDER } = require('../../common/constants')
/**
* 外部注册用户
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#external-register
* @param {object} params
* @param {string} params.externalUid 业务系统的用户id
* @param {string} params.nickname 昵称
* @param {string} params.gender 性别
* @param {string} params.avatar 头像
* @returns {object}
*/
module.exports = async function (params = {}) {
const schema = {
unieid: 'username',
externalUid: 'string',
nickname: {
required: false,
type: 'nickname'
......@@ -20,7 +31,7 @@ module.exports = async function (params = {}) {
this.middleware.validate(params, schema)
const {
unieid,
externalUid,
avatar,
gender,
nickname
......@@ -28,25 +39,39 @@ module.exports = async function (params = {}) {
await preRegister.call(this, {
user: {
username: unieid
identities: {
provider: EXTERNAL_DIRECT_CONNECT_PROVIDER,
uid: externalUid
}
}
})
const result = await postRegister.call(this, {
user: {
username: unieid,
avatar,
gender,
nickname
nickname,
identities: [
{
provider: EXTERNAL_DIRECT_CONNECT_PROVIDER,
userInfo: {
avatar,
gender,
nickname
},
uid: externalUid
}
]
}
})
return {
errCode: result.errCode,
newToken: result.newToken,
unieid,
externalUid,
avatar,
gender,
nickname
nickname,
uid: result.uid
}
}
const { userCollection, EXTERNAL_DIRECT_CONNECT_PROVIDER } = require('../../common/constants')
const { ERROR } = require('../../common/error')
const { findUser } = require('../../lib/utils/account')
const PasswordUtils = require('../../lib/utils/password')
/**
* 使用 uid 或 externalUid 获取用户信息
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#external-update-userinfo
* @param {object} params
* @param {string} params.uid uni-id体系的用户id
* @param {string} params.externalUid 业务系统的用户id
* @param {string} params.nickname 昵称
* @param {string} params.gender 性别
* @param {string} params.avatar 头像
* @returns {object}
*/
module.exports = async function (params = {}) {
const schema = {
uid: {
required: false,
type: 'string'
},
externalUid: {
required: false,
type: 'string'
},
username: {
required: false,
type: 'string'
},
password: {
required: false,
type: 'password'
},
authorizedApp: {
required: false,
type: 'array<string>'
}, // 指定允许登录的app,传空数组或不传时表示可以不可以在任何端登录
nickname: {
required: false,
type: 'nickname'
},
role: {
require: false,
type: 'array<string>'
},
mobile: {
required: false,
type: 'mobile'
},
email: {
required: false,
type: 'email'
},
tags: {
required: false,
type: 'array<string>'
},
status: {
required: false,
type: 'number'
}
}
this.middleware.validate(params, schema)
const {
uid,
externalUid,
username,
password,
authorizedApp,
nickname,
role,
mobile,
email,
tags,
status
} = params
if (!uid && !externalUid) {
throw {
errCode: ERROR.PARAM_REQUIRED,
errMsgVal: {
param: 'uid or externalUid'
}
}
}
let query
if (uid) {
query = {
_id: uid
}
} else {
query = {
identities: {
provider: EXTERNAL_DIRECT_CONNECT_PROVIDER,
uid: externalUid
}
}
}
// 更新的用户数据字段
const data = {
username,
dcloud_appid: authorizedApp,
nickname,
role,
mobile,
email,
tags,
status
}
const realData = Object.keys(data).reduce((res, key) => {
const item = data[key]
if (item !== undefined) {
res[key] = item
}
return res
}, {})
// 更新用户名时验证用户名是否重新
if (username) {
const {
userMatched
} = await findUser({
userQuery: {
username
},
authorizedApp
})
if (userMatched.filter(user => user._id !== uid).length) {
throw {
errCode: ERROR.ACCOUNT_EXISTS
}
}
}
if (password) {
const passwordUtils = new PasswordUtils({
clientInfo: this.getUniversalClientInfo(),
passwordSecret: this.config.passwordSecret
})
const {
passwordHash,
version
} = passwordUtils.generatePasswordHash({
password
})
realData.password = passwordHash
realData.password_secret_version = version
}
await userCollection.where(query).update(realData)
return {
errCode: 0
}
}
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
}
}
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
}
}
module.exports = {
getFrvCertifyId: require('./get-certify-id'),
getFrvAuthResult: require('./get-auth-result')
}
......@@ -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
{
"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": []
}
......@@ -459,7 +459,15 @@
"read": false,
"write": false
}
},
"identities": {
"bsonType": "array",
"description": "三方平台身份信息;一个对象代表一个身份,参数支持: provider 身份源, userInfo 三方用户信息, openid 三方openid, unionid 三方unionid, uid 三方uid",
"permission": {
"read": "'READ_UNI_ID_USERS' in auth.permission",
"write": "'CREATE_UNI_ID_USERS' in auth.permission || 'UPDATE_UNI_ID_USERS' in auth.permission"
}
}
},
"required": []
}
\ No newline at end of file
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册