首次完整推送,
V:1.20240808.006
This commit is contained in:
@ -0,0 +1,108 @@
|
||||
const db = uniCloud.database()
|
||||
const dbCmd = db.command
|
||||
const userCollectionName = 'uni-id-users'
|
||||
const userCollection = db.collection(userCollectionName)
|
||||
const verifyCollectionName = 'opendb-verify-codes'
|
||||
const verifyCollection = db.collection(verifyCollectionName)
|
||||
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',
|
||||
wx_unionid: 'wechat-account',
|
||||
'wx_openid.app': 'wechat-account',
|
||||
'wx_openid.mp': 'wechat-account',
|
||||
'wx_openid.h5': 'wechat-account',
|
||||
'wx_openid.web': 'wechat-account',
|
||||
qq_unionid: 'qq-account',
|
||||
'qq_openid.app': 'qq-account',
|
||||
'qq_openid.mp': 'qq-account',
|
||||
ali_openid: 'alipay-account',
|
||||
apple_openid: 'alipay-account',
|
||||
identities: 'idp'
|
||||
}
|
||||
|
||||
const USER_STATUS = {
|
||||
NORMAL: 0,
|
||||
BANNED: 1,
|
||||
AUDITING: 2,
|
||||
AUDIT_FAILED: 3,
|
||||
CLOSED: 4
|
||||
}
|
||||
|
||||
const CAPTCHA_SCENE = {
|
||||
REGISTER: 'register',
|
||||
LOGIN_BY_PWD: 'login-by-pwd',
|
||||
LOGIN_BY_SMS: 'login-by-sms',
|
||||
RESET_PWD_BY_SMS: 'reset-pwd-by-sms',
|
||||
RESET_PWD_BY_EMAIL: 'reset-pwd-by-email',
|
||||
SEND_SMS_CODE: 'send-sms-code',
|
||||
SEND_EMAIL_CODE: 'send-email-code',
|
||||
BIND_MOBILE_BY_SMS: 'bind-mobile-by-sms',
|
||||
SET_PWD_BY_SMS: 'set-pwd-by-sms'
|
||||
}
|
||||
|
||||
const LOG_TYPE = {
|
||||
LOGOUT: 'logout',
|
||||
LOGIN: 'login',
|
||||
REGISTER: 'register',
|
||||
RESET_PWD_BY_SMS: 'reset-pwd',
|
||||
RESET_PWD_BY_EMAIL: 'reset-pwd',
|
||||
BIND_MOBILE: 'bind-mobile',
|
||||
BIND_WEIXIN: 'bind-weixin',
|
||||
BIND_QQ: 'bind-qq',
|
||||
BIND_APPLE: 'bind-apple',
|
||||
BIND_ALIPAY: 'bind-alipay',
|
||||
UNBIND_WEIXIN: 'unbind-weixin',
|
||||
UNBIND_QQ: 'unbind-qq',
|
||||
UNBIND_ALIPAY: 'unbind-alipay',
|
||||
UNBIND_APPLE: 'unbind-apple'
|
||||
}
|
||||
|
||||
const SMS_SCENE = {
|
||||
LOGIN_BY_SMS: 'login-by-sms',
|
||||
RESET_PWD_BY_SMS: 'reset-pwd-by-sms',
|
||||
BIND_MOBILE_BY_SMS: 'bind-mobile-by-sms',
|
||||
SET_PWD_BY_SMS: 'set-pwd-by-sms'
|
||||
}
|
||||
|
||||
const EMAIL_SCENE = {
|
||||
REGISTER: 'register',
|
||||
LOGIN_BY_EMAIL: 'login-by-email',
|
||||
RESET_PWD_BY_EMAIL: 'reset-pwd-by-email',
|
||||
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,
|
||||
userCollection,
|
||||
verifyCollection,
|
||||
deviceCollection,
|
||||
openDataCollection,
|
||||
frvLogsCollection,
|
||||
USER_IDENTIFIER,
|
||||
USER_STATUS,
|
||||
CAPTCHA_SCENE,
|
||||
LOG_TYPE,
|
||||
SMS_SCENE,
|
||||
EMAIL_SCENE,
|
||||
REAL_NAME_STATUS,
|
||||
EXTERNAL_DIRECT_CONNECT_PROVIDER
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
const ERROR = {
|
||||
ACCOUNT_EXISTS: 'uni-id-account-exists',
|
||||
ACCOUNT_NOT_EXISTS: 'uni-id-account-not-exists',
|
||||
ACCOUNT_NOT_EXISTS_IN_CURRENT_APP: 'uni-id-account-not-exists-in-current-app',
|
||||
ACCOUNT_CONFLICT: 'uni-id-account-conflict',
|
||||
ACCOUNT_BANNED: 'uni-id-account-banned',
|
||||
ACCOUNT_AUDITING: 'uni-id-account-auditing',
|
||||
ACCOUNT_AUDIT_FAILED: 'uni-id-account-audit-failed',
|
||||
ACCOUNT_CLOSED: 'uni-id-account-closed',
|
||||
CAPTCHA_REQUIRED: 'uni-id-captcha-required',
|
||||
PASSWORD_ERROR: 'uni-id-password-error',
|
||||
PASSWORD_ERROR_EXCEED_LIMIT: 'uni-id-password-error-exceed-limit',
|
||||
INVALID_USERNAME: 'uni-id-invalid-username',
|
||||
INVALID_PASSWORD: 'uni-id-invalid-password',
|
||||
INVALID_PASSWORD_SUPER: 'uni-id-invalid-password-super',
|
||||
INVALID_PASSWORD_STRONG: 'uni-id-invalid-password-strong',
|
||||
INVALID_PASSWORD_MEDIUM: 'uni-id-invalid-password-medium',
|
||||
INVALID_PASSWORD_WEAK: 'uni-id-invalid-password-weak',
|
||||
INVALID_MOBILE: 'uni-id-invalid-mobile',
|
||||
INVALID_EMAIL: 'uni-id-invalid-email',
|
||||
INVALID_NICKNAME: 'uni-id-invalid-nickname',
|
||||
INVALID_PARAM: 'uni-id-invalid-param',
|
||||
PARAM_REQUIRED: 'uni-id-param-required',
|
||||
GET_THIRD_PARTY_ACCOUNT_FAILED: 'uni-id-get-third-party-account-failed',
|
||||
GET_THIRD_PARTY_USER_INFO_FAILED: 'uni-id-get-third-party-user-info-failed',
|
||||
MOBILE_VERIFY_CODE_ERROR: 'uni-id-mobile-verify-code-error',
|
||||
EMAIL_VERIFY_CODE_ERROR: 'uni-id-email-verify-code-error',
|
||||
ADMIN_EXISTS: 'uni-id-admin-exists',
|
||||
PERMISSION_ERROR: 'uni-id-permission-error',
|
||||
SYSTEM_ERROR: 'uni-id-system-error',
|
||||
SET_INVITE_CODE_FAILED: 'uni-id-set-invite-code-failed',
|
||||
INVALID_INVITE_CODE: 'uni-id-invalid-invite-code',
|
||||
CHANGE_INVITER_FORBIDDEN: 'uni-id-change-inviter-forbidden',
|
||||
BIND_CONFLICT: 'uni-id-bind-conflict',
|
||||
UNBIND_FAIL: 'uni-id-unbind-failed',
|
||||
UNBIND_NOT_SUPPORTED: 'uni-id-unbind-not-supported',
|
||||
UNBIND_UNIQUE_LOGIN: 'uni-id-unbind-unique-login',
|
||||
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',
|
||||
CONFIG_FIELD_REQUIRED: 'uni-id-config-field-required',
|
||||
CONFIG_FIELD_INVALID: 'uni-id-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) {
|
||||
return Object.values(ERROR).includes(errCode)
|
||||
}
|
||||
|
||||
class UniCloudError extends Error {
|
||||
constructor (options) {
|
||||
super(options.message)
|
||||
this.errMsg = options.message || ''
|
||||
this.errCode = options.code
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
ERROR,
|
||||
isUniIdError,
|
||||
UniCloudError
|
||||
}
|
@ -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
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
const { ERROR } = require('./error')
|
||||
|
||||
function getHttpClientInfo () {
|
||||
const requestId = this.getUniCloudRequestId()
|
||||
const { clientIP, userAgent, source, secretType = 'none' } = this.getClientInfo()
|
||||
const { clientInfo = {} } = JSON.parse(this.getHttpInfo().body)
|
||||
|
||||
return {
|
||||
...clientInfo,
|
||||
clientIP,
|
||||
userAgent,
|
||||
source,
|
||||
secretType,
|
||||
requestId
|
||||
}
|
||||
}
|
||||
|
||||
function getHttpUniIdToken () {
|
||||
const { uniIdToken = '' } = JSON.parse(this.getHttpInfo().body)
|
||||
|
||||
return uniIdToken
|
||||
}
|
||||
|
||||
function verifyHttpMethod () {
|
||||
const { headers, httpMethod } = this.getHttpInfo()
|
||||
|
||||
if (!/^application\/json/.test(headers['content-type']) || httpMethod.toUpperCase() !== 'POST') {
|
||||
throw {
|
||||
errCode: ERROR.UNSUPPORTED_REQUEST,
|
||||
errMsg: 'unsupported request'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function universal () {
|
||||
if (this.getClientInfo().source === 'http') {
|
||||
verifyHttpMethod.call(this)
|
||||
this.getParams()[0] = JSON.parse(this.getHttpInfo().body).params
|
||||
this.getUniversalClientInfo = getHttpClientInfo.bind(this)
|
||||
this.getUniversalUniIdToken = getHttpUniIdToken.bind(this)
|
||||
} else {
|
||||
this.getUniversalClientInfo = this.getClientInfo
|
||||
this.getUniversalUniIdToken = this.getUniIdToken
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = universal
|
@ -0,0 +1,263 @@
|
||||
function batchFindObjctValue (obj = {}, keys = []) {
|
||||
const values = {}
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const key = keys[i]
|
||||
const keyPath = key.split('.')
|
||||
let currentKey = keyPath.shift()
|
||||
let result = obj
|
||||
while (currentKey) {
|
||||
if (!result) {
|
||||
break
|
||||
}
|
||||
result = result[currentKey]
|
||||
currentKey = keyPath.shift()
|
||||
}
|
||||
values[key] = result
|
||||
}
|
||||
return values
|
||||
}
|
||||
|
||||
function getType (val) {
|
||||
return Object.prototype.toString.call(val).slice(8, -1).toLowerCase()
|
||||
}
|
||||
|
||||
function hasOwn (obj, key) {
|
||||
return Object.prototype.hasOwnProperty.call(obj, key)
|
||||
}
|
||||
|
||||
function isValidString (val) {
|
||||
return val && getType(val) === 'string'
|
||||
}
|
||||
|
||||
function isPlainObject (obj) {
|
||||
return getType(obj) === 'object'
|
||||
}
|
||||
|
||||
function isFn (fn) {
|
||||
// 务必注意AsyncFunction
|
||||
return typeof fn === 'function'
|
||||
}
|
||||
|
||||
// 获取文件后缀,只添加几种图片类型供客服消息接口使用
|
||||
const mime2ext = {
|
||||
'image/png': 'png',
|
||||
'image/jpeg': 'jpg',
|
||||
'image/gif': 'gif',
|
||||
'image/svg+xml': 'svg',
|
||||
'image/bmp': 'bmp',
|
||||
'image/webp': 'webp'
|
||||
}
|
||||
|
||||
function getExtension (contentType) {
|
||||
return mime2ext[contentType]
|
||||
}
|
||||
|
||||
const isSnakeCase = /_(\w)/g
|
||||
const isCamelCase = /[A-Z]/g
|
||||
|
||||
function snake2camel (value) {
|
||||
return value.replace(isSnakeCase, (_, c) => (c ? c.toUpperCase() : ''))
|
||||
}
|
||||
|
||||
function camel2snake (value) {
|
||||
return value.replace(isCamelCase, str => '_' + str.toLowerCase())
|
||||
}
|
||||
|
||||
function parseObjectKeys (obj, type) {
|
||||
let parserReg, parser
|
||||
switch (type) {
|
||||
case 'snake2camel':
|
||||
parser = snake2camel
|
||||
parserReg = isSnakeCase
|
||||
break
|
||||
case 'camel2snake':
|
||||
parser = camel2snake
|
||||
parserReg = isCamelCase
|
||||
break
|
||||
}
|
||||
for (const key in obj) {
|
||||
if (hasOwn(obj, key)) {
|
||||
if (parserReg.test(key)) {
|
||||
const keyCopy = parser(key)
|
||||
obj[keyCopy] = obj[key]
|
||||
delete obj[key]
|
||||
if (isPlainObject(obj[keyCopy])) {
|
||||
obj[keyCopy] = parseObjectKeys(obj[keyCopy], type)
|
||||
} else if (Array.isArray(obj[keyCopy])) {
|
||||
obj[keyCopy] = obj[keyCopy].map((item) => {
|
||||
return parseObjectKeys(item, type)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return obj
|
||||
}
|
||||
|
||||
function snake2camelJson (obj) {
|
||||
return parseObjectKeys(obj, 'snake2camel')
|
||||
}
|
||||
|
||||
function camel2snakeJson (obj) {
|
||||
return parseObjectKeys(obj, 'camel2snake')
|
||||
}
|
||||
|
||||
function getOffsetDate (offset) {
|
||||
return new Date(
|
||||
Date.now() + (new Date().getTimezoneOffset() + (offset || 0) * 60) * 60000
|
||||
)
|
||||
}
|
||||
|
||||
function getDateStr (date, separator = '-') {
|
||||
date = date || new Date()
|
||||
const dateArr = []
|
||||
dateArr.push(date.getFullYear())
|
||||
dateArr.push(('00' + (date.getMonth() + 1)).substr(-2))
|
||||
dateArr.push(('00' + date.getDate()).substr(-2))
|
||||
return dateArr.join(separator)
|
||||
}
|
||||
|
||||
function getTimeStr (date, separator = ':') {
|
||||
date = date || new Date()
|
||||
const timeArr = []
|
||||
timeArr.push(('00' + date.getHours()).substr(-2))
|
||||
timeArr.push(('00' + date.getMinutes()).substr(-2))
|
||||
timeArr.push(('00' + date.getSeconds()).substr(-2))
|
||||
return timeArr.join(separator)
|
||||
}
|
||||
|
||||
function getFullTimeStr (date) {
|
||||
date = date || new Date()
|
||||
return getDateStr(date) + ' ' + getTimeStr(date)
|
||||
}
|
||||
|
||||
function getDistinctArray (arr) {
|
||||
return Array.from(new Set(arr))
|
||||
}
|
||||
|
||||
/**
|
||||
* 拼接url
|
||||
* @param {string} base 基础路径
|
||||
* @param {string} path 在基础路径上拼接的路径
|
||||
* @returns
|
||||
*/
|
||||
function resolveUrl (base, path) {
|
||||
if (/^https?:/.test(path)) {
|
||||
return path
|
||||
}
|
||||
return base + path
|
||||
}
|
||||
|
||||
function getVerifyCode (len = 6) {
|
||||
let code = ''
|
||||
for (let i = 0; i < len; i++) {
|
||||
code += Math.floor(Math.random() * 10)
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
||||
function coverMobile (mobile) {
|
||||
if (typeof mobile !== 'string') {
|
||||
return mobile
|
||||
}
|
||||
return mobile.slice(0, 3) + '****' + mobile.slice(7)
|
||||
}
|
||||
|
||||
function getNonceStr (length = 16) {
|
||||
let str = ''
|
||||
while (str.length < length) {
|
||||
str += Math.random().toString(32).substring(2)
|
||||
}
|
||||
return str.substring(0, length)
|
||||
}
|
||||
|
||||
function isMatchUserApp (userAppList, matchAppList) {
|
||||
if (userAppList === undefined || userAppList === null) {
|
||||
return true
|
||||
}
|
||||
if (getType(userAppList) !== 'array') {
|
||||
return false
|
||||
}
|
||||
if (userAppList.includes('*')) {
|
||||
return true
|
||||
}
|
||||
if (getType(matchAppList) === 'string') {
|
||||
matchAppList = [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,
|
||||
batchFindObjctValue,
|
||||
isPlainObject,
|
||||
isFn,
|
||||
getDistinctArray,
|
||||
getFullTimeStr,
|
||||
resolveUrl,
|
||||
getOffsetDate,
|
||||
camel2snakeJson,
|
||||
snake2camelJson,
|
||||
getExtension,
|
||||
getVerifyCode,
|
||||
coverMobile,
|
||||
getNonceStr,
|
||||
isMatchUserApp,
|
||||
checkIdCard,
|
||||
catchAwait,
|
||||
dataDesensitization,
|
||||
getCurrentDateTimestamp
|
||||
}
|
@ -0,0 +1,443 @@
|
||||
const {
|
||||
isValidString,
|
||||
getType
|
||||
} = require('./utils.js')
|
||||
const {
|
||||
ERROR
|
||||
} = require('./error')
|
||||
|
||||
const baseValidator = Object.create(null)
|
||||
|
||||
baseValidator.username = function (username) {
|
||||
const errCode = ERROR.INVALID_USERNAME
|
||||
if (!isValidString(username)) {
|
||||
return {
|
||||
errCode
|
||||
}
|
||||
}
|
||||
if (/^\d+$/.test(username)) {
|
||||
// 用户名不能为纯数字
|
||||
return {
|
||||
errCode
|
||||
}
|
||||
};
|
||||
if (!/^[a-zA-Z0-9_-]+$/.test(username)) {
|
||||
// 用户名仅能使用数字、字母、“_”及“-”
|
||||
return {
|
||||
errCode
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
baseValidator.password = function (password) {
|
||||
const errCode = ERROR.INVALID_PASSWORD
|
||||
if (!isValidString(password)) {
|
||||
return {
|
||||
errCode
|
||||
}
|
||||
}
|
||||
if (password.length < 6) {
|
||||
// 密码长度不能小于6
|
||||
return {
|
||||
errCode
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
baseValidator.mobile = function (mobile) {
|
||||
const errCode = ERROR.INVALID_MOBILE
|
||||
if (getType(mobile) !== 'string') {
|
||||
return {
|
||||
errCode
|
||||
}
|
||||
}
|
||||
if (mobile && !/^1\d{10}$/.test(mobile)) {
|
||||
return {
|
||||
errCode
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
baseValidator.email = function (email) {
|
||||
const errCode = ERROR.INVALID_EMAIL
|
||||
if (getType(email) !== 'string') {
|
||||
return {
|
||||
errCode
|
||||
}
|
||||
}
|
||||
if (email && !/@/.test(email)) {
|
||||
return {
|
||||
errCode
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
baseValidator.nickname = function (nickname) {
|
||||
const errCode = ERROR.INVALID_NICKNAME
|
||||
if (nickname.indexOf('@') !== -1) {
|
||||
// 昵称不允许含@
|
||||
return {
|
||||
errCode
|
||||
}
|
||||
};
|
||||
if (/^\d+$/.test(nickname)) {
|
||||
// 昵称不能为纯数字
|
||||
return {
|
||||
errCode
|
||||
}
|
||||
};
|
||||
if (nickname.length > 100) {
|
||||
// 昵称不可超过100字符
|
||||
return {
|
||||
errCode
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const baseType = ['string', 'boolean', 'number', 'null'] // undefined不会由客户端提交上来
|
||||
|
||||
baseType.forEach((type) => {
|
||||
baseValidator[type] = function (val) {
|
||||
if (getType(val) === type) {
|
||||
return
|
||||
}
|
||||
return {
|
||||
errCode: ERROR.INVALID_PARAM
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
function tokenize(name) {
|
||||
let i = 0
|
||||
const result = []
|
||||
let token = ''
|
||||
while (i < name.length) {
|
||||
const char = name[i]
|
||||
switch (char) {
|
||||
case '|':
|
||||
case '<':
|
||||
case '>':
|
||||
token && result.push(token)
|
||||
result.push(char)
|
||||
token = ''
|
||||
break
|
||||
default:
|
||||
token += char
|
||||
break
|
||||
}
|
||||
i++
|
||||
if (i === name.length && token) {
|
||||
result.push(token)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理validator名
|
||||
* @param {string} name
|
||||
*/
|
||||
function parseValidatorName(name) {
|
||||
const tokenList = tokenize(name)
|
||||
let i = 0
|
||||
let currentToken = tokenList[i]
|
||||
const result = {
|
||||
type: 'root',
|
||||
children: [],
|
||||
parent: null
|
||||
}
|
||||
let lastRealm = result
|
||||
while (currentToken) {
|
||||
switch (currentToken) {
|
||||
case 'array': {
|
||||
const currentRealm = {
|
||||
type: 'array',
|
||||
children: [],
|
||||
parent: lastRealm
|
||||
}
|
||||
lastRealm.children.push(currentRealm)
|
||||
lastRealm = currentRealm
|
||||
break
|
||||
}
|
||||
case '<':
|
||||
if (lastRealm.type !== 'array') {
|
||||
throw new Error('Invalid validator token "<"')
|
||||
}
|
||||
break
|
||||
case '>':
|
||||
if (lastRealm.type !== 'array') {
|
||||
throw new Error('Invalid validator token ">"')
|
||||
}
|
||||
lastRealm = lastRealm.parent
|
||||
break
|
||||
case '|':
|
||||
if (lastRealm.type !== 'array' && lastRealm.type !== 'root') {
|
||||
throw new Error('Invalid validator token "|"')
|
||||
}
|
||||
break
|
||||
default:
|
||||
lastRealm.children.push({
|
||||
type: currentToken
|
||||
})
|
||||
break
|
||||
}
|
||||
i++
|
||||
currentToken = tokenList[i]
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
function getRuleCategory(rule) {
|
||||
switch (rule.type) {
|
||||
case 'array':
|
||||
return 'array'
|
||||
case 'root':
|
||||
return 'root'
|
||||
default:
|
||||
return 'base'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 特殊符号 https://www.ibm.com/support/pages/password-strength-rules ~!@#$%^&*_-+=`|\(){}[]:;"'<>,.?/
|
||||
// const specialChar = '~!@#$%^&*_-+=`|\(){}[]:;"\'<>,.?/'
|
||||
// const specialCharRegExp = /^[~!@#$%^&*_\-+=`|\\(){}[\]:;"'<>,.?/]$/
|
||||
// for (let i = 0, arr = specialChar.split(''); i < arr.length; i++) {
|
||||
// const char = arr[i]
|
||||
// if (!specialCharRegExp.test(char)) {
|
||||
// throw new Error('check special character error: ' + char)
|
||||
// }
|
||||
// }
|
||||
|
||||
// 密码强度表达式
|
||||
const passwordRules = {
|
||||
// 密码必须包含大小写字母、数字和特殊符号
|
||||
super: /^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=.*[~!@#$%^&*_\-+=`|\\(){}[\]:;"'<>,.?/])[0-9a-zA-Z~!@#$%^&*_\-+=`|\\(){}[\]:;"'<>,.?/]{8,16}$/,
|
||||
// 密码必须包含字母、数字和特殊符号
|
||||
strong: /^(?=.*[0-9])(?=.*[a-zA-Z])(?=.*[~!@#$%^&*_\-+=`|\\(){}[\]:;"'<>,.?/])[0-9a-zA-Z~!@#$%^&*_\-+=`|\\(){}[\]:;"'<>,.?/]{8,16}$/,
|
||||
// 密码必须为字母、数字和特殊符号任意两种的组合
|
||||
medium: /^(?![0-9]+$)(?![a-zA-Z]+$)(?![~!@#$%^&*_\-+=`|\\(){}[\]:;"'<>,.?/]+$)[0-9a-zA-Z~!@#$%^&*_\-+=`|\\(){}[\]:;"'<>,.?/]{8,16}$/,
|
||||
// 密码必须包含字母和数字
|
||||
weak: /^(?=.*[0-9])(?=.*[a-zA-Z])[0-9a-zA-Z~!@#$%^&*_\-+=`|\\(){}[\]:;"'<>,.?/]{6,16}$/,
|
||||
|
||||
}
|
||||
|
||||
function createPasswordVerifier({
|
||||
passwordStrength = ''
|
||||
} = {}) {
|
||||
return function (password) {
|
||||
const passwordRegExp = passwordRules[passwordStrength]
|
||||
if (!passwordRegExp) {
|
||||
throw new Error('Invalid password strength config: ' + passwordStrength)
|
||||
}
|
||||
const errCode = ERROR.INVALID_PASSWORD
|
||||
if (!isValidString(password)) {
|
||||
return {
|
||||
errCode
|
||||
}
|
||||
}
|
||||
if (!passwordRegExp.test(password)) {
|
||||
return {
|
||||
errCode: errCode + '-' + passwordStrength
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function isEmpty(value) {
|
||||
return value === undefined ||
|
||||
value === null ||
|
||||
(typeof value === 'string' && value.trim() === '')
|
||||
}
|
||||
|
||||
class Validator {
|
||||
constructor({
|
||||
passwordStrength = ''
|
||||
} = {}) {
|
||||
this.baseValidator = baseValidator
|
||||
this.customValidator = Object.create(null)
|
||||
if (passwordStrength) {
|
||||
this.mixin(
|
||||
'password',
|
||||
createPasswordVerifier({
|
||||
passwordStrength
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
mixin(type, handler) {
|
||||
this.customValidator[type] = handler
|
||||
}
|
||||
|
||||
getRealBaseValidator(type) {
|
||||
return this.customValidator[type] || this.baseValidator[type]
|
||||
}
|
||||
|
||||
|
||||
_isMatchUnionType(val, rule) {
|
||||
if (!rule.children || rule.children.length === 0) {
|
||||
return true
|
||||
}
|
||||
const children = rule.children
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
const child = children[i]
|
||||
const category = getRuleCategory(child)
|
||||
let pass = false
|
||||
switch (category) {
|
||||
case 'base':
|
||||
pass = this._isMatchBaseType(val, child)
|
||||
break
|
||||
case 'array':
|
||||
pass = this._isMatchArrayType(val, child)
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
if (pass) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
_isMatchBaseType(val, rule) {
|
||||
const method = this.getRealBaseValidator(rule.type)
|
||||
if (typeof method !== 'function') {
|
||||
throw new Error(`invalid schema type: ${rule.type}`)
|
||||
}
|
||||
const validateRes = method(val)
|
||||
if (validateRes && validateRes.errCode) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
_isMatchArrayType(arr, rule) {
|
||||
if (getType(arr) !== 'array') {
|
||||
return false
|
||||
}
|
||||
if (rule.children && rule.children.length && arr.some(item => !this._isMatchUnionType(item, rule))) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
get validator() {
|
||||
const _this = this
|
||||
return new Proxy({}, {
|
||||
get: (_, prop) => {
|
||||
if (typeof prop !== 'string') {
|
||||
return
|
||||
}
|
||||
const realBaseValidator = this.getRealBaseValidator(prop)
|
||||
if (realBaseValidator) {
|
||||
return realBaseValidator
|
||||
}
|
||||
const rule = parseValidatorName(prop)
|
||||
return function (val) {
|
||||
if (!_this._isMatchUnionType(val, rule)) {
|
||||
return {
|
||||
errCode: ERROR.INVALID_PARAM
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
validate(value = {}, schema = {}) {
|
||||
for (const schemaKey in schema) {
|
||||
let schemaValue = schema[schemaKey]
|
||||
if (getType(schemaValue) === 'string') {
|
||||
schemaValue = {
|
||||
required: true,
|
||||
type: schemaValue
|
||||
}
|
||||
}
|
||||
const {
|
||||
required,
|
||||
type
|
||||
} = schemaValue
|
||||
// value内未传入了schemaKey或对应值为undefined
|
||||
if (isEmpty(value[schemaKey])) {
|
||||
if (required) {
|
||||
return {
|
||||
errCode: ERROR.PARAM_REQUIRED,
|
||||
errMsgValue: {
|
||||
param: schemaKey
|
||||
},
|
||||
schemaKey
|
||||
}
|
||||
} else {
|
||||
//delete value[schemaKey]
|
||||
continue
|
||||
}
|
||||
}
|
||||
const validateMethod = this.validator[type]
|
||||
if (!validateMethod) {
|
||||
throw new Error(`invalid schema type: ${type}`)
|
||||
}
|
||||
const validateRes = validateMethod(value[schemaKey])
|
||||
if (validateRes) {
|
||||
validateRes.schemaKey = schemaKey
|
||||
return validateRes
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function checkClientInfo(clientInfo) {
|
||||
const stringNotRequired = {
|
||||
required: false,
|
||||
type: 'string'
|
||||
}
|
||||
const numberNotRequired = {
|
||||
required: false,
|
||||
type: 'number'
|
||||
}
|
||||
const numberOrStringNotRequired = {
|
||||
required: false,
|
||||
type: 'number|string'
|
||||
}
|
||||
const schema = {
|
||||
uniPlatform: 'string',
|
||||
appId: 'string',
|
||||
deviceId: stringNotRequired,
|
||||
osName: stringNotRequired,
|
||||
locale: stringNotRequired,
|
||||
clientIP: stringNotRequired,
|
||||
appName: stringNotRequired,
|
||||
appVersion: stringNotRequired,
|
||||
appVersionCode: numberOrStringNotRequired,
|
||||
channel: numberOrStringNotRequired,
|
||||
userAgent: stringNotRequired,
|
||||
uniIdToken: stringNotRequired,
|
||||
deviceBrand: stringNotRequired,
|
||||
deviceModel: stringNotRequired,
|
||||
osVersion: stringNotRequired,
|
||||
osLanguage: stringNotRequired,
|
||||
osTheme: stringNotRequired,
|
||||
romName: stringNotRequired,
|
||||
romVersion: stringNotRequired,
|
||||
devicePixelRatio: numberNotRequired,
|
||||
windowWidth: numberNotRequired,
|
||||
windowHeight: numberNotRequired,
|
||||
screenWidth: numberNotRequired,
|
||||
screenHeight: numberNotRequired
|
||||
}
|
||||
const validateRes = new Validator().validate(clientInfo, schema)
|
||||
if (validateRes) {
|
||||
if (validateRes.errCode === ERROR.PARAM_REQUIRED) {
|
||||
console.warn('- 如果使用HBuilderX运行本地云函数/云对象功能时出现此提示,请改为使用客户端调用本地云函数方式调试,或更新HBuilderX到3.4.12及以上版本。\n- 如果是缺少clientInfo.appId,请检查项目manifest.json内是否配置了DCloud AppId')
|
||||
throw new Error(`"clientInfo.${validateRes.schemaKey}" is required.`)
|
||||
} else {
|
||||
throw new Error(`Invalid client info: clienInfo.${validateRes.schemaKey}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
Validator,
|
||||
checkClientInfo
|
||||
}
|
@ -0,0 +1,90 @@
|
||||
// 各接口权限配置,未配置接口表示允许任何用户访问(包括未登录用户)
|
||||
module.exports = {
|
||||
// 管理接口
|
||||
addUser: {
|
||||
// auth: true // 已登录用户方可操作,配置角色或权限时此项可不写
|
||||
role: ['admin'] // 允许进行此操作的角色,包含任一角色均可操作。
|
||||
// permission: [] // 允许进行此操作的权限,包含任一权限均可操作。
|
||||
// 权限角色均配置时,用户拥有任一权限或任一角色均可操作
|
||||
},
|
||||
updateUser: {
|
||||
role: ['admin']
|
||||
},
|
||||
authorizeAppLogin: {
|
||||
role: ['admin']
|
||||
},
|
||||
removeAuthorizedApp: {
|
||||
role: ['admin']
|
||||
},
|
||||
setAuthorizedApp: {
|
||||
role: ['admin']
|
||||
},
|
||||
|
||||
// 用户接口
|
||||
closeAccount: {
|
||||
auth: true
|
||||
},
|
||||
updatePwd: {
|
||||
auth: true
|
||||
},
|
||||
logout: {
|
||||
auth: true
|
||||
},
|
||||
bindMobileBySms: {
|
||||
auth: true
|
||||
},
|
||||
bindMobileByUniverify: {
|
||||
auth: true
|
||||
},
|
||||
bindMobileByMpWeixin: {
|
||||
auth: true
|
||||
},
|
||||
bindAlipay: {
|
||||
auth: true
|
||||
},
|
||||
bindApple: {
|
||||
auth: true
|
||||
},
|
||||
bindQQ: {
|
||||
auth: true
|
||||
},
|
||||
bindWeixin: {
|
||||
auth: true
|
||||
},
|
||||
acceptInvite: {
|
||||
auth: true
|
||||
},
|
||||
getInvitedUser: {
|
||||
auth: true
|
||||
},
|
||||
setPushCid: {
|
||||
auth: true
|
||||
},
|
||||
getAccountInfo: {
|
||||
auth: true
|
||||
},
|
||||
unbindWeixin: {
|
||||
auth: true
|
||||
},
|
||||
unbindAlipay: {
|
||||
auth: true
|
||||
},
|
||||
unbindQQ: {
|
||||
auth: true
|
||||
},
|
||||
unbindApple: {
|
||||
auth: true
|
||||
},
|
||||
setPwd: {
|
||||
auth: true
|
||||
},
|
||||
getFrvCertifyId: {
|
||||
auth: true
|
||||
},
|
||||
getFrvAuthResult: {
|
||||
auth: true
|
||||
},
|
||||
getRealNameInfo: {
|
||||
auth: true
|
||||
}
|
||||
}
|
@ -0,0 +1,696 @@
|
||||
const uniIdCommon = require('uni-id-common')
|
||||
const uniCaptcha = require('uni-captcha')
|
||||
const {
|
||||
getType,
|
||||
checkIdCard
|
||||
} = require('./common/utils')
|
||||
const {
|
||||
checkClientInfo,
|
||||
Validator
|
||||
} = require('./common/validator')
|
||||
const ConfigUtils = require('./lib/utils/config')
|
||||
const {
|
||||
isUniIdError,
|
||||
ERROR
|
||||
} = require('./common/error')
|
||||
const middleware = require('./middleware/index')
|
||||
const universal = require('./common/universal')
|
||||
|
||||
const {
|
||||
registerAdmin,
|
||||
registerUser,
|
||||
registerUserByEmail
|
||||
} = require('./module/register/index')
|
||||
const {
|
||||
addUser,
|
||||
updateUser
|
||||
} = require('./module/admin/index')
|
||||
const {
|
||||
login,
|
||||
loginBySms,
|
||||
loginByUniverify,
|
||||
loginByWeixin,
|
||||
loginByAlipay,
|
||||
loginByQQ,
|
||||
loginByApple,
|
||||
loginByWeixinMobile
|
||||
} = require('./module/login/index')
|
||||
const {
|
||||
logout
|
||||
} = require('./module/logout/index')
|
||||
const {
|
||||
bindMobileBySms,
|
||||
bindMobileByUniverify,
|
||||
bindMobileByMpWeixin,
|
||||
bindAlipay,
|
||||
bindApple,
|
||||
bindQQ,
|
||||
bindWeixin,
|
||||
unbindWeixin,
|
||||
unbindAlipay,
|
||||
unbindQQ,
|
||||
unbindApple
|
||||
} = require('./module/relate/index')
|
||||
const {
|
||||
setPwd,
|
||||
updatePwd,
|
||||
resetPwdBySms,
|
||||
resetPwdByEmail,
|
||||
closeAccount,
|
||||
getAccountInfo,
|
||||
getRealNameInfo
|
||||
} = require('./module/account/index')
|
||||
const {
|
||||
createCaptcha,
|
||||
refreshCaptcha,
|
||||
sendSmsCode,
|
||||
sendEmailCode
|
||||
} = require('./module/verify/index')
|
||||
const {
|
||||
refreshToken,
|
||||
setPushCid,
|
||||
secureNetworkHandshakeByWeixin
|
||||
} = require('./module/utils/index')
|
||||
const {
|
||||
getInvitedUser,
|
||||
acceptInvite
|
||||
} = require('./module/fission')
|
||||
const {
|
||||
authorizeAppLogin,
|
||||
removeAuthorizedApp,
|
||||
setAuthorizedApp
|
||||
} = require('./module/multi-end')
|
||||
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 () {
|
||||
// 支持 callFunction 与 URL化
|
||||
universal.call(this)
|
||||
|
||||
const clientInfo = this.getUniversalClientInfo()
|
||||
/**
|
||||
* 检查clientInfo,无appId和uniPlatform时本云对象无法正常运行
|
||||
* 此外需要保证用到的clientInfo字段均经过类型检查
|
||||
* clientInfo由客户端上传并非完全可信,clientInfo内除clientIP、userAgent、source外均为客户端上传参数
|
||||
* 否则可能会出现一些意料外的情况
|
||||
*/
|
||||
checkClientInfo(clientInfo)
|
||||
let clientPlatform = clientInfo.uniPlatform
|
||||
// 统一platform名称
|
||||
switch (clientPlatform) {
|
||||
case 'app':
|
||||
case 'app-plus':
|
||||
case 'app-android':
|
||||
case 'app-ios':
|
||||
clientPlatform = 'app'
|
||||
break
|
||||
case 'web':
|
||||
case 'h5':
|
||||
clientPlatform = 'web'
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
this.clientPlatform = clientPlatform
|
||||
|
||||
// 挂载uni-id实例到this上,方便后续调用
|
||||
this.uniIdCommon = uniIdCommon.createInstance({
|
||||
clientInfo
|
||||
})
|
||||
|
||||
// 包含uni-id配置合并等功能的工具集
|
||||
this.configUtils = new ConfigUtils({
|
||||
context: this
|
||||
})
|
||||
this.config = this.configUtils.getPlatformConfig()
|
||||
this.hooks = this.configUtils.getHooks()
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
})
|
||||
/**
|
||||
* 示例:覆盖密码验证规则
|
||||
*/
|
||||
// this.validator.mixin('password', function (password) {
|
||||
// if (typeof password !== 'string' || password.length < 10) {
|
||||
// // 调整为密码长度不能小于10
|
||||
// return {
|
||||
// errCode: ERROR.INVALID_PASSWORD
|
||||
// }
|
||||
// }
|
||||
// })
|
||||
/**
|
||||
* 示例:新增验证规则
|
||||
*/
|
||||
// this.validator.mixin('timestamp', function (timestamp) {
|
||||
// if (typeof timestamp !== 'number' || timestamp > Date.now()) {
|
||||
// return {
|
||||
// errCode: ERROR.INVALID_PARAM
|
||||
// }
|
||||
// }
|
||||
// })
|
||||
// // 新增规则同样可以在数组验证规则中使用
|
||||
// this.validator.validate({
|
||||
// timestamp: 123456789
|
||||
// }, {
|
||||
// timestamp: 'timestamp'
|
||||
// })
|
||||
// this.validator.validate({
|
||||
// timestampList: [123456789, 123123123123]
|
||||
// }, {
|
||||
// timestampList: 'array<timestamp>'
|
||||
// })
|
||||
// // 甚至更复杂的写法
|
||||
// this.validator.validate({
|
||||
// timestamp: [123456789123123123, 123123123123]
|
||||
// }, {
|
||||
// timestamp: 'timestamp|array<timestamp|number>'
|
||||
// })
|
||||
|
||||
// 挂载uni-captcha到this上,方便后续调用
|
||||
this.uniCaptcha = uniCaptcha
|
||||
Object.defineProperty(this, 'uniOpenBridge', {
|
||||
get () {
|
||||
return require('uni-open-bridge-common')
|
||||
}
|
||||
})
|
||||
|
||||
// 挂载中间件
|
||||
this.middleware = {}
|
||||
for (const mwName in middleware) {
|
||||
this.middleware[mwName] = middleware[mwName].bind(this)
|
||||
}
|
||||
|
||||
// 国际化
|
||||
const messages = require('./lang/index')
|
||||
const fallbackLocale = 'zh-Hans'
|
||||
const i18n = uniCloud.initI18n({
|
||||
locale: clientInfo.locale,
|
||||
fallbackLocale,
|
||||
messages: JSON.parse(JSON.stringify(messages))
|
||||
})
|
||||
if (!messages[i18n.locale]) {
|
||||
i18n.setLocale(fallbackLocale)
|
||||
}
|
||||
this.t = i18n.t.bind(i18n)
|
||||
|
||||
this.response = {}
|
||||
|
||||
// 请求鉴权验证
|
||||
await this.middleware.verifyRequestSign()
|
||||
|
||||
// 通用权限校验模块
|
||||
await this.middleware.accessControl()
|
||||
},
|
||||
_after (error, result) {
|
||||
if (error) {
|
||||
// 处理中间件内抛出的标准响应对象
|
||||
if (error.errCode && getType(error) === 'object') {
|
||||
const errCode = error.errCode
|
||||
if (!isUniIdError(errCode)) {
|
||||
return error
|
||||
}
|
||||
return {
|
||||
errCode,
|
||||
errMsg: error.errMsg || this.t(errCode, error.errMsgValue)
|
||||
}
|
||||
}
|
||||
throw error
|
||||
}
|
||||
return Object.assign(this.response, result)
|
||||
},
|
||||
/**
|
||||
* 注册管理员
|
||||
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#register-admin
|
||||
* @param {Object} params
|
||||
* @param {String} params.username 用户名
|
||||
* @param {String} params.password 密码
|
||||
* @param {String} params.nickname 昵称
|
||||
* @returns
|
||||
*/
|
||||
registerAdmin,
|
||||
/**
|
||||
* 新增用户
|
||||
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#add-user
|
||||
* @param {Object} params
|
||||
* @param {String} params.username 用户名
|
||||
* @param {String} params.password 密码
|
||||
* @param {String} params.nickname 昵称
|
||||
* @param {Array} params.authorizedApp 允许登录的AppID列表
|
||||
* @param {Array} params.role 用户角色列表
|
||||
* @param {String} params.mobile 手机号
|
||||
* @param {String} params.email 邮箱
|
||||
* @param {Array} params.tags 用户标签
|
||||
* @param {Number} params.status 用户状态
|
||||
* @returns
|
||||
*/
|
||||
addUser,
|
||||
/**
|
||||
* 修改用户
|
||||
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#update-user
|
||||
* @param {Object} params
|
||||
* @param {String} params.id 要更新的用户id
|
||||
* @param {String} params.username 用户名
|
||||
* @param {String} params.password 密码
|
||||
* @param {String} params.nickname 昵称
|
||||
* @param {Array} params.authorizedApp 允许登录的AppID列表
|
||||
* @param {Array} params.role 用户角色列表
|
||||
* @param {String} params.mobile 手机号
|
||||
* @param {String} params.email 邮箱
|
||||
* @param {Array} params.tags 用户标签
|
||||
* @param {Number} params.status 用户状态
|
||||
* @returns
|
||||
*/
|
||||
updateUser,
|
||||
/**
|
||||
* 授权用户登录应用
|
||||
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#authorize-app-login
|
||||
* @param {Object} params
|
||||
* @param {String} params.uid 用户id
|
||||
* @param {String} params.appId 授权的应用的AppId
|
||||
* @returns
|
||||
*/
|
||||
authorizeAppLogin,
|
||||
/**
|
||||
* 移除用户登录授权
|
||||
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#remove-authorized-app
|
||||
* @param {Object} params
|
||||
* @param {String} params.uid 用户id
|
||||
* @param {String} params.appId 取消授权的应用的AppId
|
||||
* @returns
|
||||
*/
|
||||
removeAuthorizedApp,
|
||||
/**
|
||||
* 设置用户允许登录的应用列表
|
||||
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#set-authorized-app
|
||||
* @param {Object} params
|
||||
* @param {String} params.uid 用户id
|
||||
* @param {Array} params.appIdList 允许登录的应用AppId列表
|
||||
* @returns
|
||||
*/
|
||||
setAuthorizedApp,
|
||||
/**
|
||||
* 注册普通用户
|
||||
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#register-user
|
||||
* @param {Object} params
|
||||
* @param {String} params.username 用户名
|
||||
* @param {String} params.password 密码
|
||||
* @param {String} params.captcha 图形验证码
|
||||
* @param {String} params.nickname 昵称
|
||||
* @param {String} params.inviteCode 邀请码
|
||||
* @returns
|
||||
*/
|
||||
registerUser,
|
||||
/**
|
||||
* 通过邮箱+验证码注册用户
|
||||
* @param {Object} params
|
||||
* @param {String} params.email 邮箱
|
||||
* @param {String} params.password 密码
|
||||
* @param {String} params.nickname 昵称
|
||||
* @param {String} params.code 邮箱验证码
|
||||
* @param {String} params.inviteCode 邀请码
|
||||
* @returns
|
||||
*/
|
||||
registerUserByEmail,
|
||||
/**
|
||||
* 用户名密码登录
|
||||
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#login
|
||||
* @param {Object} params
|
||||
* @param {String} params.username 用户名
|
||||
* @param {String} params.mobile 手机号
|
||||
* @param {String} params.email 邮箱
|
||||
* @param {String} params.password 密码
|
||||
* @param {String} params.captcha 图形验证码
|
||||
* @returns
|
||||
*/
|
||||
login,
|
||||
/**
|
||||
* 短信验证码登录
|
||||
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#login-by-sms
|
||||
* @param {Object} params
|
||||
* @param {String} params.mobile 手机号
|
||||
* @param {String} params.code 短信验证码
|
||||
* @param {String} params.captcha 图形验证码
|
||||
* @param {String} params.inviteCode 邀请码
|
||||
* @returns
|
||||
*/
|
||||
loginBySms,
|
||||
/**
|
||||
* App端一键登录
|
||||
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#login-by-univerify
|
||||
* @param {Object} params
|
||||
* @param {String} params.access_token APP端一键登录返回的access_token
|
||||
* @param {String} params.openid APP端一键登录返回的openid
|
||||
* @param {String} params.inviteCode 邀请码
|
||||
* @returns
|
||||
*/
|
||||
loginByUniverify,
|
||||
/**
|
||||
* 微信登录
|
||||
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#login-by-weixin
|
||||
* @param {Object} params
|
||||
* @param {String} params.code 微信登录返回的code
|
||||
* @param {String} params.inviteCode 邀请码
|
||||
* @returns
|
||||
*/
|
||||
loginByWeixin,
|
||||
/**
|
||||
* 支付宝登录
|
||||
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#login-by-alipay
|
||||
* @param {Object} params
|
||||
* @param {String} params.code 支付宝小程序客户端登录返回的code
|
||||
* @param {String} params.inviteCode 邀请码
|
||||
* @returns
|
||||
*/
|
||||
loginByAlipay,
|
||||
/**
|
||||
* QQ登录
|
||||
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#login-by-qq
|
||||
* @param {Object} params
|
||||
* @param {String} params.code QQ小程序登录返回的code参数
|
||||
* @param {String} params.accessToken App端QQ登录返回的accessToken参数
|
||||
* @param {String} params.accessTokenExpired accessToken过期时间,由App端QQ登录返回的expires_in参数计算而来,单位:毫秒
|
||||
* @param {String} params.inviteCode 邀请码
|
||||
* @returns
|
||||
*/
|
||||
loginByQQ,
|
||||
/**
|
||||
* 苹果登录
|
||||
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#login-by-apple
|
||||
* @param {Object} params
|
||||
* @param {String} params.identityToken 苹果登录返回的identityToken
|
||||
* @param {String} params.nickname 用户昵称
|
||||
* @param {String} params.inviteCode 邀请码
|
||||
* @returns
|
||||
*/
|
||||
loginByApple,
|
||||
loginByWeixinMobile,
|
||||
/**
|
||||
* 用户退出登录
|
||||
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#logout
|
||||
* @returns
|
||||
*/
|
||||
logout,
|
||||
/**
|
||||
* 通过短信验证码绑定手机号
|
||||
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#bind-mobile-by-sms
|
||||
* @param {Object} params
|
||||
* @param {String} params.mobile 手机号
|
||||
* @param {String} params.code 短信验证码
|
||||
* @param {String} params.captcha 图形验证码
|
||||
* @returns
|
||||
*/
|
||||
bindMobileBySms,
|
||||
/**
|
||||
* 通过一键登录绑定手机号
|
||||
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#bind-mobile-by-univerify
|
||||
* @param {Object} params
|
||||
* @param {String} params.openid APP端一键登录返回的openid
|
||||
* @param {String} params.access_token APP端一键登录返回的access_token
|
||||
* @returns
|
||||
*/
|
||||
bindMobileByUniverify,
|
||||
/**
|
||||
* 通过微信绑定手机号
|
||||
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#bind-mobile-by-mp-weixin
|
||||
* @param {Object} params
|
||||
* @param {String} params.encryptedData 微信获取手机号返回的加密信息
|
||||
* @param {String} params.iv 微信获取手机号返回的初始向量
|
||||
* @returns
|
||||
*/
|
||||
bindMobileByMpWeixin,
|
||||
/**
|
||||
* 绑定微信
|
||||
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#bind-weixin
|
||||
* @param {Object} params
|
||||
* @param {String} params.code 微信登录返回的code
|
||||
* @returns
|
||||
*/
|
||||
bindWeixin,
|
||||
/**
|
||||
* 绑定QQ
|
||||
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#bind-qq
|
||||
* @param {Object} params
|
||||
* @param {String} params.code 小程序端QQ登录返回的code
|
||||
* @param {String} params.accessToken APP端QQ登录返回的accessToken
|
||||
* @param {String} params.accessTokenExpired accessToken过期时间,由App端QQ登录返回的expires_in参数计算而来,单位:毫秒
|
||||
* @returns
|
||||
*/
|
||||
bindQQ,
|
||||
/**
|
||||
* 绑定支付宝账号
|
||||
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#bind-alipay
|
||||
* @param {Object} params
|
||||
* @param {String} params.code 支付宝小程序登录返回的code参数
|
||||
* @returns
|
||||
*/
|
||||
bindAlipay,
|
||||
/**
|
||||
* 绑定苹果账号
|
||||
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#bind-apple
|
||||
* @param {Object} params
|
||||
* @param {String} params.identityToken 苹果登录返回identityToken
|
||||
* @returns
|
||||
*/
|
||||
bindApple,
|
||||
/**
|
||||
* 更新密码
|
||||
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#update-pwd
|
||||
* @param {object} params
|
||||
* @param {string} params.oldPassword 旧密码
|
||||
* @param {string} params.newPassword 新密码
|
||||
* @returns {object}
|
||||
*/
|
||||
updatePwd,
|
||||
/**
|
||||
* 通过短信验证码重置密码
|
||||
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#reset-pwd-by-sms
|
||||
* @param {object} params
|
||||
* @param {string} params.mobile 手机号
|
||||
* @param {string} params.mobile 短信验证码
|
||||
* @param {string} params.password 密码
|
||||
* @param {string} params.captcha 图形验证码
|
||||
* @returns {object}
|
||||
*/
|
||||
resetPwdBySms,
|
||||
/**
|
||||
* 通过邮箱验证码重置密码
|
||||
* @param {object} params
|
||||
* @param {string} params.email 邮箱
|
||||
* @param {string} params.code 邮箱验证码
|
||||
* @param {string} params.password 密码
|
||||
* @param {string} params.captcha 图形验证码
|
||||
* @returns {object}
|
||||
*/
|
||||
resetPwdByEmail,
|
||||
/**
|
||||
* 注销账户
|
||||
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#close-account
|
||||
* @returns
|
||||
*/
|
||||
closeAccount,
|
||||
/**
|
||||
* 获取账户账户简略信息
|
||||
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#get-account-info
|
||||
*/
|
||||
getAccountInfo,
|
||||
/**
|
||||
* 创建图形验证码
|
||||
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#create-captcha
|
||||
* @param {Object} params
|
||||
* @param {String} params.scene 图形验证码使用场景
|
||||
* @returns
|
||||
*/
|
||||
createCaptcha,
|
||||
/**
|
||||
* 刷新图形验证码
|
||||
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#refresh-captcha
|
||||
* @param {Object} params
|
||||
* @param {String} params.scene 图形验证码使用场景
|
||||
* @returns
|
||||
*/
|
||||
refreshCaptcha,
|
||||
/**
|
||||
* 发送短信验证码
|
||||
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#send-sms-code
|
||||
* @param {Object} params
|
||||
* @param {String} params.mobile 手机号
|
||||
* @param {String} params.captcha 图形验证码
|
||||
* @param {String} params.scene 短信验证码使用场景
|
||||
* @returns
|
||||
*/
|
||||
sendSmsCode,
|
||||
/**
|
||||
* 发送邮箱验证码
|
||||
* @tutorial 需自行实现功能
|
||||
* @param {Object} params
|
||||
* @param {String} params.email 邮箱
|
||||
* @param {String} params.captcha 图形验证码
|
||||
* @param {String} params.scene 短信验证码使用场景
|
||||
* @returns
|
||||
*/
|
||||
sendEmailCode,
|
||||
/**
|
||||
* 刷新token
|
||||
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#refresh-token
|
||||
*/
|
||||
refreshToken,
|
||||
/**
|
||||
* 接受邀请
|
||||
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#accept-invite
|
||||
* @param {Object} params
|
||||
* @param {String} params.inviteCode 邀请码
|
||||
* @returns
|
||||
*/
|
||||
acceptInvite,
|
||||
/**
|
||||
* 获取受邀用户
|
||||
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#get-invited-user
|
||||
* @param {Object} params
|
||||
* @param {Number} params.level 获取受邀用户的级数,1表示直接邀请的用户
|
||||
* @param {Number} params.limit 返回数据大小
|
||||
* @param {Number} params.offset 返回数据偏移
|
||||
* @param {Boolean} params.needTotal 是否需要返回总数
|
||||
* @returns
|
||||
*/
|
||||
getInvitedUser,
|
||||
/**
|
||||
* 更新device表的push_clien_id
|
||||
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#set-push-cid
|
||||
* @param {object} params
|
||||
* @param {string} params.pushClientId 客户端pushClientId
|
||||
* @returns
|
||||
*/
|
||||
setPushCid,
|
||||
/**
|
||||
* 获取支持的登录方式
|
||||
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#get-supported-login-type
|
||||
* @returns
|
||||
*/
|
||||
getSupportedLoginType,
|
||||
|
||||
/**
|
||||
* 解绑微信
|
||||
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#unbind-weixin
|
||||
* @returns
|
||||
*/
|
||||
unbindWeixin,
|
||||
/**
|
||||
* 解绑支付宝
|
||||
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#unbind-alipay
|
||||
* @returns
|
||||
*/
|
||||
unbindAlipay,
|
||||
/**
|
||||
* 解绑QQ
|
||||
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#unbind-qq
|
||||
* @returns
|
||||
*/
|
||||
unbindQQ,
|
||||
/**
|
||||
* 解绑Apple
|
||||
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#unbind-apple
|
||||
* @returns
|
||||
*/
|
||||
unbindApple,
|
||||
/**
|
||||
* 安全网络握手,目前仅处理微信小程序安全网络握手
|
||||
*/
|
||||
secureNetworkHandshakeByWeixin,
|
||||
/**
|
||||
* 设置密码
|
||||
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#set-pwd
|
||||
* @returns
|
||||
*/
|
||||
setPwd,
|
||||
/**
|
||||
* 外部注册用户
|
||||
* @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}
|
||||
*/
|
||||
externalRegister,
|
||||
/**
|
||||
* 外部用户登录
|
||||
* @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
|
||||
*/
|
||||
getRealNameInfo
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
const word = {
|
||||
login: 'login',
|
||||
'verify-mobile': 'verify phone number'
|
||||
}
|
||||
|
||||
const sentence = {
|
||||
'uni-id-account-exists': 'Account exists',
|
||||
'uni-id-account-not-exists': 'Account does not exists',
|
||||
'uni-id-account-not-exists-in-current-app': 'Account does not exists in current app',
|
||||
'uni-id-account-conflict': 'User account conflict',
|
||||
'uni-id-account-banned': 'Account has been banned',
|
||||
'uni-id-account-auditing': 'Account audit in progress',
|
||||
'uni-id-account-audit-failed': 'Account audit failed',
|
||||
'uni-id-account-closed': 'Account has been closed',
|
||||
'uni-id-captcha-required': 'Captcha required',
|
||||
'uni-id-password-error': 'Password error',
|
||||
'uni-id-password-error-exceed-limit': 'The number of password errors is excessive',
|
||||
'uni-id-invalid-username': 'Invalid username',
|
||||
'uni-id-invalid-password': 'invalid password',
|
||||
'uni-id-invalid-password-super': 'Passwords must have 8-16 characters and contain uppercase letters, lowercase letters, numbers, and symbols.',
|
||||
'uni-id-invalid-password-strong': 'Passwords must have 8-16 characters and contain letters, numbers and symbols.',
|
||||
'uni-id-invalid-password-medium': 'Passwords must have 8-16 characters and contain at least two of the following: letters, numbers, and symbols.',
|
||||
'uni-id-invalid-password-weak': 'Passwords must have 6-16 characters and contain letters and numbers.',
|
||||
'uni-id-invalid-mobile': 'Invalid mobile phone number',
|
||||
'uni-id-invalid-email': 'Invalid email address',
|
||||
'uni-id-invalid-nickname': 'Invalid nickname',
|
||||
'uni-id-invalid-param': 'Invalid parameter',
|
||||
'uni-id-param-required': 'Parameter required: {param}',
|
||||
'uni-id-get-third-party-account-failed': 'Get third party account failed',
|
||||
'uni-id-get-third-party-user-info-failed': 'Get third party user info failed',
|
||||
'uni-id-mobile-verify-code-error': 'Verify code error or expired',
|
||||
'uni-id-email-verify-code-error': 'Verify code error or expired',
|
||||
'uni-id-admin-exists': 'Administrator exists',
|
||||
'uni-id-permission-error': 'Permission denied',
|
||||
'uni-id-system-error': 'System error',
|
||||
'uni-id-set-invite-code-failed': 'Set invite code failed',
|
||||
'uni-id-invalid-invite-code': 'Invalid invite code',
|
||||
'uni-id-change-inviter-forbidden': 'Change inviter is not allowed',
|
||||
'uni-id-bind-conflict': 'This account has been bound',
|
||||
'uni-id-admin-exist-in-other-apps': 'Administrator is registered in other consoles',
|
||||
'uni-id-unbind-failed': 'Please bind first and then unbind',
|
||||
'uni-id-unbind-not-supported': 'Unbinding is not supported',
|
||||
'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-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 = {
|
||||
...word,
|
||||
...sentence
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
let lang = {
|
||||
'zh-Hans': require('./zh-hans'),
|
||||
en: require('./en')
|
||||
}
|
||||
|
||||
function mergeLanguage (lang1, lang2) {
|
||||
const localeList = Object.keys(lang1)
|
||||
localeList.push(...Object.keys(lang2))
|
||||
const result = {}
|
||||
for (let i = 0; i < localeList.length; i++) {
|
||||
const locale = localeList[i]
|
||||
result[locale] = Object.assign({}, lang1[locale], lang2[locale])
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
try {
|
||||
const langPath = require.resolve('uni-config-center/uni-id/lang/index.js')
|
||||
lang = mergeLanguage(lang, require(langPath))
|
||||
} catch (error) { }
|
||||
|
||||
module.exports = lang
|
@ -0,0 +1,64 @@
|
||||
const word = {
|
||||
login: '登录',
|
||||
'verify-mobile': '验证手机号'
|
||||
}
|
||||
|
||||
const sentence = {
|
||||
'uni-id-token-expired': '登录状态失效,token已过期',
|
||||
'uni-id-check-token-failed': 'token校验未通过',
|
||||
'uni-id-account-exists': '此账号已注册',
|
||||
'uni-id-account-not-exists': '此账号未注册',
|
||||
'uni-id-account-not-exists-in-current-app': '此账号未在该应用注册',
|
||||
'uni-id-account-conflict': '用户账号冲突',
|
||||
'uni-id-account-banned': '此账号已封禁',
|
||||
'uni-id-account-auditing': '此账号正在审核中',
|
||||
'uni-id-account-audit-failed': '此账号审核失败',
|
||||
'uni-id-account-closed': '此账号已注销',
|
||||
'uni-id-captcha-required': '请输入图形验证码',
|
||||
'uni-id-password-error': '密码错误',
|
||||
'uni-id-password-error-exceed-limit': '密码错误次数过多,请稍后再试',
|
||||
'uni-id-invalid-username': '用户名不合法',
|
||||
'uni-id-invalid-password': '密码不合法',
|
||||
'uni-id-invalid-password-super': '密码必须包含大小写字母、数字和特殊符号,长度8-16位',
|
||||
'uni-id-invalid-password-strong': '密码必须包含字母、数字和特殊符号,长度8-16位不合法',
|
||||
'uni-id-invalid-password-medium': '密码必须为字母、数字和特殊符号任意两种的组合,长度8-16位',
|
||||
'uni-id-invalid-password-weak': '密码必须包含字母和数字,长度6-16位',
|
||||
'uni-id-invalid-mobile': '手机号码不合法',
|
||||
'uni-id-invalid-email': '邮箱不合法',
|
||||
'uni-id-invalid-nickname': '昵称不合法',
|
||||
'uni-id-invalid-param': '参数错误',
|
||||
'uni-id-param-required': '缺少参数: {param}',
|
||||
'uni-id-get-third-party-account-failed': '获取第三方账号失败',
|
||||
'uni-id-get-third-party-user-info-failed': '获取用户信息失败',
|
||||
'uni-id-mobile-verify-code-error': '手机验证码错误或已过期',
|
||||
'uni-id-email-verify-code-error': '邮箱验证码错误或已过期',
|
||||
'uni-id-admin-exists': '超级管理员已存在',
|
||||
'uni-id-permission-error': '权限错误',
|
||||
'uni-id-system-error': '系统错误',
|
||||
'uni-id-set-invite-code-failed': '设置邀请码失败',
|
||||
'uni-id-invalid-invite-code': '邀请码不可用',
|
||||
'uni-id-change-inviter-forbidden': '禁止修改邀请人',
|
||||
'uni-id-bind-conflict': '此账号已被绑定',
|
||||
'uni-id-admin-exist-in-other-apps': '超级管理员已在其他控制台注册',
|
||||
'uni-id-unbind-failed': '请先绑定后再解绑',
|
||||
'uni-id-unbind-not-supported': '不支持解绑',
|
||||
'uni-id-unbind-mobile-not-exists': '这是当前唯一登录方式,请绑定手机号后再尝试解绑',
|
||||
'uni-id-unbind-password-not-exists': '请先设置密码在尝试解绑',
|
||||
'uni-id-unsupported-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': '当日实名认证次数已达上限',
|
||||
'uni-id-config-field-required': '缺少配置项: {field}',
|
||||
'uni-id-config-field-invalid': '配置项: {field}无效'
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
...word,
|
||||
...sentence
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
# 说明
|
||||
|
||||
此目录内为uni-id-co基础能力,不建议直接修改。如果你发现有些逻辑加入会更好,或者此部分代码有Bug可以向我们提交PR,仓库地址:[]()。如果有特殊的需求也可以在[论坛](https://ask.dcloud.net.cn/)提出,我们可以讨论下如何实现。
|
File diff suppressed because one or more lines are too long
@ -0,0 +1,16 @@
|
||||
const AlipayBase = require('../alipayBase')
|
||||
const protocols = require('./protocols')
|
||||
module.exports = class Auth extends AlipayBase {
|
||||
constructor (options) {
|
||||
super(options)
|
||||
this._protocols = protocols
|
||||
}
|
||||
|
||||
async code2Session (code) {
|
||||
const result = await this._exec('alipay.system.oauth.token', {
|
||||
grantType: 'authorization_code',
|
||||
code
|
||||
})
|
||||
return result
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
module.exports = {
|
||||
code2Session: {
|
||||
// args (fromArgs) {
|
||||
// return fromArgs
|
||||
// },
|
||||
returnValue: {
|
||||
openid: 'userId'
|
||||
}
|
||||
}
|
||||
}
|
231
uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/third-party/alipay/alipayBase.js
vendored
Normal file
231
uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/third-party/alipay/alipayBase.js
vendored
Normal file
@ -0,0 +1,231 @@
|
||||
const {
|
||||
camel2snakeJson,
|
||||
snake2camelJson,
|
||||
getOffsetDate,
|
||||
getFullTimeStr
|
||||
} = require('../../../common/utils')
|
||||
const crypto = require('crypto')
|
||||
|
||||
const ALIPAY_ALGORITHM_MAPPING = {
|
||||
RSA: 'RSA-SHA1',
|
||||
RSA2: 'RSA-SHA256'
|
||||
}
|
||||
|
||||
module.exports = class AlipayBase {
|
||||
constructor (options = {}) {
|
||||
if (!options.appId) throw new Error('appId required')
|
||||
if (!options.privateKey) throw new Error('privateKey required')
|
||||
const defaultOptions = {
|
||||
gateway: 'https://openapi.alipay.com/gateway.do',
|
||||
timeout: 5000,
|
||||
charset: 'utf-8',
|
||||
version: '1.0',
|
||||
signType: 'RSA2',
|
||||
timeOffset: -new Date().getTimezoneOffset() / 60,
|
||||
keyType: 'PKCS8'
|
||||
}
|
||||
|
||||
if (options.sandbox) {
|
||||
options.gateway = 'https://openapi.alipaydev.com/gateway.do'
|
||||
}
|
||||
|
||||
this.options = Object.assign({}, defaultOptions, options)
|
||||
|
||||
const privateKeyType =
|
||||
this.options.keyType === 'PKCS8' ? 'PRIVATE KEY' : 'RSA PRIVATE KEY'
|
||||
|
||||
this.options.privateKey = this._formatKey(
|
||||
this.options.privateKey,
|
||||
privateKeyType
|
||||
)
|
||||
if (this.options.alipayPublicKey) {
|
||||
this.options.alipayPublicKey = this._formatKey(
|
||||
this.options.alipayPublicKey,
|
||||
'PUBLIC KEY'
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
_formatKey (key, type) {
|
||||
return `-----BEGIN ${type}-----\n${key}\n-----END ${type}-----`
|
||||
}
|
||||
|
||||
_formatUrl (url, params) {
|
||||
let requestUrl = url
|
||||
// 需要放在 url 中的参数列表
|
||||
const urlArgs = [
|
||||
'app_id',
|
||||
'method',
|
||||
'format',
|
||||
'charset',
|
||||
'sign_type',
|
||||
'sign',
|
||||
'timestamp',
|
||||
'version',
|
||||
'notify_url',
|
||||
'return_url',
|
||||
'auth_token',
|
||||
'app_auth_token'
|
||||
]
|
||||
|
||||
for (const key in params) {
|
||||
if (urlArgs.indexOf(key) > -1) {
|
||||
const val = encodeURIComponent(params[key])
|
||||
requestUrl = `${requestUrl}${requestUrl.includes('?') ? '&' : '?'
|
||||
}${key}=${val}`
|
||||
// 删除 postData 中对应的数据
|
||||
delete params[key]
|
||||
}
|
||||
}
|
||||
|
||||
return { execParams: params, url: requestUrl }
|
||||
}
|
||||
|
||||
_getSign (method, params) {
|
||||
const bizContent = params.bizContent || null
|
||||
delete params.bizContent
|
||||
|
||||
const signParams = Object.assign({
|
||||
method,
|
||||
appId: this.options.appId,
|
||||
charset: this.options.charset,
|
||||
version: this.options.version,
|
||||
signType: this.options.signType,
|
||||
timestamp: getFullTimeStr(getOffsetDate(this.options.timeOffset))
|
||||
}, params)
|
||||
|
||||
if (bizContent) {
|
||||
signParams.bizContent = JSON.stringify(camel2snakeJson(bizContent))
|
||||
}
|
||||
|
||||
// params key 驼峰转下划线
|
||||
const decamelizeParams = camel2snakeJson(signParams)
|
||||
|
||||
// 排序
|
||||
const signStr = Object.keys(decamelizeParams)
|
||||
.sort()
|
||||
.map((key) => {
|
||||
let data = decamelizeParams[key]
|
||||
if (Array.prototype.toString.call(data) !== '[object String]') {
|
||||
data = JSON.stringify(data)
|
||||
}
|
||||
return `${key}=${data}`
|
||||
})
|
||||
.join('&')
|
||||
|
||||
// 计算签名
|
||||
const sign = crypto
|
||||
.createSign(ALIPAY_ALGORITHM_MAPPING[this.options.signType])
|
||||
.update(signStr, 'utf8')
|
||||
.sign(this.options.privateKey, 'base64')
|
||||
|
||||
return Object.assign(decamelizeParams, { sign })
|
||||
}
|
||||
|
||||
async _exec (method, params = {}, option = {}) {
|
||||
// 计算签名
|
||||
const signData = this._getSign(method, params)
|
||||
const { url, execParams } = this._formatUrl(this.options.gateway, signData)
|
||||
const { status, data } = await uniCloud.httpclient.request(url, {
|
||||
method: 'POST',
|
||||
data: execParams,
|
||||
// 按 text 返回(为了验签)
|
||||
dataType: 'text',
|
||||
timeout: this.options.timeout
|
||||
})
|
||||
if (status !== 200) throw new Error('request fail')
|
||||
/**
|
||||
* 示例响应格式
|
||||
* {"alipay_trade_precreate_response":
|
||||
* {"code": "10000","msg": "Success","out_trade_no": "111111","qr_code": "https:\/\/"},
|
||||
* "sign": "abcde="
|
||||
* }
|
||||
* 或者
|
||||
* {"error_response":
|
||||
* {"code":"40002","msg":"Invalid Arguments","sub_code":"isv.code-invalid","sub_msg":"授权码code无效"},
|
||||
* }
|
||||
*/
|
||||
const result = JSON.parse(data)
|
||||
const responseKey = `${method.replace(/\./g, '_')}_response`
|
||||
const response = result[responseKey]
|
||||
const errorResponse = result.error_response
|
||||
if (response) {
|
||||
// 按字符串验签
|
||||
const validateSuccess = option.validateSign ? this._checkResponseSign(data, responseKey) : true
|
||||
if (validateSuccess) {
|
||||
if (!response.code || response.code === '10000') {
|
||||
const errCode = 0
|
||||
const errMsg = response.msg || ''
|
||||
return {
|
||||
errCode,
|
||||
errMsg,
|
||||
...snake2camelJson(response)
|
||||
}
|
||||
}
|
||||
const msg = response.sub_code ? `${response.sub_code} ${response.sub_msg}` : `${response.msg || 'unkonwn error'}`
|
||||
throw new Error(msg)
|
||||
} else {
|
||||
throw new Error('check sign error')
|
||||
}
|
||||
} else if (errorResponse) {
|
||||
throw new Error(errorResponse.sub_msg || errorResponse.msg || 'request fail')
|
||||
}
|
||||
|
||||
throw new Error('request fail')
|
||||
}
|
||||
|
||||
_checkResponseSign (signStr, responseKey) {
|
||||
if (!this.options.alipayPublicKey || this.options.alipayPublicKey === '') {
|
||||
console.warn('options.alipayPublicKey is empty')
|
||||
// 支付宝公钥不存在时不做验签
|
||||
return true
|
||||
}
|
||||
|
||||
// 带验签的参数不存在时返回失败
|
||||
if (!signStr) { return false }
|
||||
|
||||
// 根据服务端返回的结果截取需要验签的目标字符串
|
||||
const validateStr = this._getSignStr(signStr, responseKey)
|
||||
// 服务端返回的签名
|
||||
const serverSign = JSON.parse(signStr).sign
|
||||
|
||||
// 参数存在,并且是正常的结果(不包含 sub_code)时才验签
|
||||
const verifier = crypto.createVerify(ALIPAY_ALGORITHM_MAPPING[this.options.signType])
|
||||
verifier.update(validateStr, 'utf8')
|
||||
return verifier.verify(this.options.alipayPublicKey, serverSign, 'base64')
|
||||
}
|
||||
|
||||
_getSignStr (originStr, responseKey) {
|
||||
// 待签名的字符串
|
||||
let validateStr = originStr.trim()
|
||||
// 找到 xxx_response 开始的位置
|
||||
const startIndex = originStr.indexOf(`${responseKey}"`)
|
||||
// 找到最后一个 “"sign"” 字符串的位置(避免)
|
||||
const lastIndex = originStr.lastIndexOf('"sign"')
|
||||
|
||||
/**
|
||||
* 删除 xxx_response 及之前的字符串
|
||||
* 假设原始字符串为
|
||||
* {"xxx_response":{"code":"10000"},"sign":"jumSvxTKwn24G5sAIN"}
|
||||
* 删除后变为
|
||||
* :{"code":"10000"},"sign":"jumSvxTKwn24G5sAIN"}
|
||||
*/
|
||||
validateStr = validateStr.substr(startIndex + responseKey.length + 1)
|
||||
|
||||
/**
|
||||
* 删除最后一个 "sign" 及之后的字符串
|
||||
* 删除后变为
|
||||
* :{"code":"10000"},
|
||||
* {} 之间就是待验签的字符串
|
||||
*/
|
||||
validateStr = validateStr.substr(0, lastIndex)
|
||||
|
||||
// 删除第一个 { 之前的任何字符
|
||||
validateStr = validateStr.replace(/^[^{]*{/g, '{')
|
||||
|
||||
// 删除最后一个 } 之后的任何字符
|
||||
validateStr = validateStr.replace(/\}([^}]*)$/g, '}')
|
||||
|
||||
return validateStr
|
||||
}
|
||||
}
|
@ -0,0 +1,79 @@
|
||||
const rsaPublicKeyPem = require('../rsa-public-key-pem')
|
||||
const {
|
||||
jwtVerify
|
||||
} = require('../../../npm/index')
|
||||
let authKeysCache = null
|
||||
|
||||
module.exports = class Auth {
|
||||
constructor (options) {
|
||||
this.options = Object.assign({
|
||||
baseUrl: 'https://appleid.apple.com',
|
||||
timeout: 10000
|
||||
}, options)
|
||||
}
|
||||
|
||||
async _fetch (url, options) {
|
||||
const { baseUrl } = this.options
|
||||
return uniCloud.httpclient.request(baseUrl + url, options)
|
||||
}
|
||||
|
||||
async verifyIdentityToken (identityToken) {
|
||||
// 解密出kid,拿取key
|
||||
const jwtHeader = identityToken.split('.')[0]
|
||||
const { kid } = JSON.parse(Buffer.from(jwtHeader, 'base64').toString())
|
||||
let authKeys
|
||||
if (authKeysCache) {
|
||||
authKeys = authKeysCache
|
||||
} else {
|
||||
authKeys = await this.getAuthKeys()
|
||||
authKeysCache = authKeys
|
||||
}
|
||||
const usedKey = authKeys.find(item => item.kid === kid)
|
||||
|
||||
/**
|
||||
* identityToken 格式
|
||||
*
|
||||
* {
|
||||
* iss: 'https://appleid.apple.com',
|
||||
* aud: 'io.dcloud.hellouniapp',
|
||||
* exp: 1610626724,
|
||||
* iat: 1610540324,
|
||||
* sub: '000628.30119d332d9b45a3be4a297f9391fd5c.0403',
|
||||
* c_hash: 'oFfgewoG36cJX00KUbj45A',
|
||||
* email: 'x2awmap99s@privaterelay.appleid.com',
|
||||
* email_verified: 'true',
|
||||
* is_private_email: 'true',
|
||||
* auth_time: 1610540324,
|
||||
* nonce_supported: true
|
||||
* }
|
||||
*/
|
||||
const payload = jwtVerify(
|
||||
identityToken,
|
||||
rsaPublicKeyPem(usedKey.n, usedKey.e),
|
||||
{
|
||||
algorithms: usedKey.alg
|
||||
}
|
||||
)
|
||||
|
||||
if (payload.iss !== 'https://appleid.apple.com' || payload.aud !== this.options.bundleId) {
|
||||
throw new Error('Invalid identity token')
|
||||
}
|
||||
|
||||
return {
|
||||
openid: payload.sub,
|
||||
email: payload.email,
|
||||
emailVerified: payload.email_verified === 'true',
|
||||
isPrivateEmail: payload.is_private_email === 'true'
|
||||
}
|
||||
}
|
||||
|
||||
async getAuthKeys () {
|
||||
const { status, data } = await this._fetch('/auth/keys', {
|
||||
method: 'GET',
|
||||
dataType: 'json',
|
||||
timeout: this.options.timeout
|
||||
})
|
||||
if (status !== 200) throw new Error('request https://appleid.apple.com/auth/keys fail')
|
||||
return data.keys
|
||||
}
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
// http://stackoverflow.com/questions/18835132/xml-to-pem-in-node-js
|
||||
/* eslint-disable camelcase */
|
||||
function rsaPublicKeyPem (modulus_b64, exponent_b64) {
|
||||
const modulus = Buffer.from(modulus_b64, 'base64')
|
||||
const exponent = Buffer.from(exponent_b64, 'base64')
|
||||
|
||||
let modulus_hex = modulus.toString('hex')
|
||||
let exponent_hex = exponent.toString('hex')
|
||||
|
||||
modulus_hex = prepadSigned(modulus_hex)
|
||||
exponent_hex = prepadSigned(exponent_hex)
|
||||
|
||||
const modlen = modulus_hex.length / 2
|
||||
const explen = exponent_hex.length / 2
|
||||
|
||||
const encoded_modlen = encodeLengthHex(modlen)
|
||||
const encoded_explen = encodeLengthHex(explen)
|
||||
const encoded_pubkey = '30' +
|
||||
encodeLengthHex(
|
||||
modlen +
|
||||
explen +
|
||||
encoded_modlen.length / 2 +
|
||||
encoded_explen.length / 2 + 2
|
||||
) +
|
||||
'02' + encoded_modlen + modulus_hex +
|
||||
'02' + encoded_explen + exponent_hex
|
||||
|
||||
const der_b64 = Buffer.from(encoded_pubkey, 'hex').toString('base64')
|
||||
|
||||
const pem = '-----BEGIN RSA PUBLIC KEY-----\n' +
|
||||
der_b64.match(/.{1,64}/g).join('\n') +
|
||||
'\n-----END RSA PUBLIC KEY-----\n'
|
||||
|
||||
return pem
|
||||
}
|
||||
|
||||
function prepadSigned (hexStr) {
|
||||
const msb = hexStr[0]
|
||||
if (msb < '0' || msb > '7') {
|
||||
return '00' + hexStr
|
||||
} else {
|
||||
return hexStr
|
||||
}
|
||||
}
|
||||
|
||||
function toHex (number) {
|
||||
const nstr = number.toString(16)
|
||||
if (nstr.length % 2) return '0' + nstr
|
||||
return nstr
|
||||
}
|
||||
|
||||
// encode ASN.1 DER length field
|
||||
// if <=127, short form
|
||||
// if >=128, long form
|
||||
function encodeLengthHex (n) {
|
||||
if (n <= 127) return toHex(n)
|
||||
else {
|
||||
const n_hex = toHex(n)
|
||||
const length_of_length_byte = 128 + n_hex.length / 2 // 0x80+numbytes
|
||||
return toHex(length_of_length_byte) + n_hex
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = rsaPublicKeyPem
|
36
uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/third-party/index.js
vendored
Normal file
36
uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/third-party/index.js
vendored
Normal file
@ -0,0 +1,36 @@
|
||||
const WxAccount = require('./weixin/account/index')
|
||||
const QQAccount = require('./qq/account/index')
|
||||
const AliAccount = require('./alipay/account/index')
|
||||
const AppleAccount = require('./apple/account/index')
|
||||
|
||||
const createApi = require('./share/create-api')
|
||||
|
||||
module.exports = {
|
||||
initWeixin: function () {
|
||||
const oauthConfig = this.configUtils.getOauthConfig({ provider: 'weixin' })
|
||||
return createApi(WxAccount, {
|
||||
appId: oauthConfig.appid,
|
||||
secret: oauthConfig.appsecret
|
||||
})
|
||||
},
|
||||
initQQ: function () {
|
||||
const oauthConfig = this.configUtils.getOauthConfig({ provider: 'qq' })
|
||||
return createApi(QQAccount, {
|
||||
appId: oauthConfig.appid,
|
||||
secret: oauthConfig.appsecret
|
||||
})
|
||||
},
|
||||
initAlipay: function () {
|
||||
const oauthConfig = this.configUtils.getOauthConfig({ provider: 'alipay' })
|
||||
return createApi(AliAccount, {
|
||||
appId: oauthConfig.appid,
|
||||
privateKey: oauthConfig.privateKey
|
||||
})
|
||||
},
|
||||
initApple: function () {
|
||||
const oauthConfig = this.configUtils.getOauthConfig({ provider: 'apple' })
|
||||
return createApi(AppleAccount, {
|
||||
bundleId: oauthConfig.bundleId
|
||||
})
|
||||
}
|
||||
}
|
97
uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/third-party/qq/account/index.js
vendored
Normal file
97
uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/third-party/qq/account/index.js
vendored
Normal file
@ -0,0 +1,97 @@
|
||||
const {
|
||||
UniCloudError
|
||||
} = require('../../../../common/error')
|
||||
const {
|
||||
resolveUrl
|
||||
} = require('../../../../common/utils')
|
||||
const {
|
||||
callQQOpenApi
|
||||
} = require('../normalize')
|
||||
|
||||
module.exports = class Auth {
|
||||
constructor (options) {
|
||||
this.options = Object.assign({
|
||||
baseUrl: 'https://graph.qq.com',
|
||||
timeout: 5000
|
||||
}, options)
|
||||
}
|
||||
|
||||
async _requestQQOpenapi ({ name, url, data, options }) {
|
||||
const defaultOptions = {
|
||||
method: 'GET',
|
||||
dataType: 'json',
|
||||
dataAsQueryString: true,
|
||||
timeout: this.options.timeout
|
||||
}
|
||||
const result = await callQQOpenApi({
|
||||
name: `auth.${name}`,
|
||||
url: resolveUrl(this.options.baseUrl, url),
|
||||
data,
|
||||
options,
|
||||
defaultOptions
|
||||
})
|
||||
return result
|
||||
}
|
||||
|
||||
async getUserInfo ({
|
||||
accessToken,
|
||||
openid
|
||||
} = {}) {
|
||||
const url = '/user/get_user_info'
|
||||
const result = await this._requestQQOpenapi({
|
||||
name: 'getUserInfo',
|
||||
url,
|
||||
data: {
|
||||
oauthConsumerKey: this.options.appId,
|
||||
accessToken,
|
||||
openid
|
||||
}
|
||||
})
|
||||
return {
|
||||
nickname: result.nickname,
|
||||
avatar: result.figureurl_qq_1
|
||||
}
|
||||
}
|
||||
|
||||
async getOpenidByToken ({
|
||||
accessToken
|
||||
} = {}) {
|
||||
const url = '/oauth2.0/me'
|
||||
const result = await this._requestQQOpenapi({
|
||||
name: 'getOpenidByToken',
|
||||
url,
|
||||
data: {
|
||||
accessToken,
|
||||
unionid: 1,
|
||||
fmt: 'json'
|
||||
}
|
||||
})
|
||||
if (result.clientId !== this.options.appId) {
|
||||
throw new UniCloudError({
|
||||
code: 'APPID_NOT_MATCH',
|
||||
message: 'appid not match'
|
||||
})
|
||||
}
|
||||
return {
|
||||
openid: result.openid,
|
||||
unionid: result.unionid
|
||||
}
|
||||
}
|
||||
|
||||
async code2Session ({
|
||||
code
|
||||
} = {}) {
|
||||
const url = 'https://api.q.qq.com/sns/jscode2session'
|
||||
const result = await this._requestQQOpenapi({
|
||||
name: 'getOpenidByToken',
|
||||
url,
|
||||
data: {
|
||||
grant_type: 'authorization_code',
|
||||
appid: this.options.appId,
|
||||
secret: this.options.secret,
|
||||
js_code: code
|
||||
}
|
||||
})
|
||||
return result
|
||||
}
|
||||
}
|
85
uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/third-party/qq/normalize.js
vendored
Normal file
85
uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/third-party/qq/normalize.js
vendored
Normal file
@ -0,0 +1,85 @@
|
||||
const {
|
||||
UniCloudError
|
||||
} = require('../../../common/error')
|
||||
const {
|
||||
camel2snakeJson,
|
||||
snake2camelJson
|
||||
} = require('../../../common/utils')
|
||||
|
||||
function generateApiResult (apiName, data) {
|
||||
if (data.ret || data.error) {
|
||||
// 这三种都是qq的错误码规范
|
||||
const code = data.ret || data.error || data.errcode || -2
|
||||
const message = data.msg || data.error_description || data.errmsg || `${apiName} fail`
|
||||
throw new UniCloudError({
|
||||
code,
|
||||
message
|
||||
})
|
||||
} else {
|
||||
delete data.ret
|
||||
delete data.msg
|
||||
delete data.error
|
||||
delete data.error_description
|
||||
delete data.errcode
|
||||
delete data.errmsg
|
||||
return {
|
||||
...data,
|
||||
errMsg: `${apiName} ok`,
|
||||
errCode: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function nomalizeError (apiName, error) {
|
||||
throw new UniCloudError({
|
||||
code: error.code || -2,
|
||||
message: error.message || `${apiName} fail`
|
||||
})
|
||||
}
|
||||
|
||||
async function callQQOpenApi ({
|
||||
name,
|
||||
url,
|
||||
data,
|
||||
options,
|
||||
defaultOptions
|
||||
}) {
|
||||
options = Object.assign({}, defaultOptions, options, { data: camel2snakeJson(Object.assign({}, data)) })
|
||||
let result
|
||||
try {
|
||||
result = await uniCloud.httpclient.request(url, options)
|
||||
} catch (e) {
|
||||
return nomalizeError(name, e)
|
||||
}
|
||||
let resData = result.data
|
||||
const contentType = result.headers['content-type']
|
||||
if (
|
||||
Buffer.isBuffer(resData) &&
|
||||
(contentType.indexOf('text/plain') === 0 ||
|
||||
contentType.indexOf('application/json') === 0)
|
||||
) {
|
||||
try {
|
||||
resData = JSON.parse(resData.toString())
|
||||
} catch (e) {
|
||||
resData = resData.toString()
|
||||
}
|
||||
} else if (Buffer.isBuffer(resData)) {
|
||||
resData = {
|
||||
buffer: resData,
|
||||
contentType
|
||||
}
|
||||
}
|
||||
return snake2camelJson(
|
||||
generateApiResult(
|
||||
name,
|
||||
resData || {
|
||||
errCode: -2,
|
||||
errMsg: 'Request failed'
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
callQQOpenApi
|
||||
}
|
73
uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/third-party/share/create-api.js
vendored
Normal file
73
uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/third-party/share/create-api.js
vendored
Normal file
@ -0,0 +1,73 @@
|
||||
const {
|
||||
isFn,
|
||||
isPlainObject
|
||||
} = require('../../../common/utils')
|
||||
|
||||
// 注意:不进行递归处理
|
||||
function parseParams (params = {}, rule) {
|
||||
if (!rule || !params) {
|
||||
return params
|
||||
}
|
||||
const internalKeys = ['_pre', '_purify', '_post']
|
||||
// 转换之前的处理
|
||||
if (rule._pre) {
|
||||
params = rule._pre(params)
|
||||
}
|
||||
// 净化参数
|
||||
let purify = { shouldDelete: new Set([]) }
|
||||
if (rule._purify) {
|
||||
const _purify = rule._purify
|
||||
for (const purifyKey in _purify) {
|
||||
_purify[purifyKey] = new Set(_purify[purifyKey])
|
||||
}
|
||||
purify = Object.assign(purify, _purify)
|
||||
}
|
||||
if (isPlainObject(rule)) {
|
||||
for (const key in rule) {
|
||||
const parser = rule[key]
|
||||
if (isFn(parser) && internalKeys.indexOf(key) === -1) {
|
||||
params[key] = parser(params)
|
||||
} else if (typeof parser === 'string' && internalKeys.indexOf(key) === -1) {
|
||||
// 直接转换属性名称的删除旧属性名
|
||||
params[key] = params[parser]
|
||||
purify.shouldDelete.add(parser)
|
||||
}
|
||||
}
|
||||
} else if (isFn(rule)) {
|
||||
params = rule(params)
|
||||
}
|
||||
|
||||
if (purify.shouldDelete) {
|
||||
for (const item of purify.shouldDelete) {
|
||||
delete params[item]
|
||||
}
|
||||
}
|
||||
|
||||
// 转换之后的处理
|
||||
if (rule._post) {
|
||||
params = rule._post(params)
|
||||
}
|
||||
|
||||
return params
|
||||
}
|
||||
|
||||
function createApi (ApiClass, options) {
|
||||
const apiInstance = new ApiClass(options)
|
||||
return new Proxy(apiInstance, {
|
||||
get: function (obj, prop) {
|
||||
if (typeof obj[prop] === 'function' && prop.indexOf('_') !== 0 && obj._protocols && obj._protocols[prop]) {
|
||||
const protocol = obj._protocols[prop]
|
||||
return async function (params) {
|
||||
params = parseParams(params, protocol.args)
|
||||
let result = await obj[prop](params)
|
||||
result = parseParams(result, protocol.returnValue)
|
||||
return result
|
||||
}
|
||||
} else {
|
||||
return obj[prop]
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = createApi
|
111
uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/third-party/weixin/account/index.js
vendored
Normal file
111
uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/third-party/weixin/account/index.js
vendored
Normal file
@ -0,0 +1,111 @@
|
||||
const {
|
||||
callWxOpenApi,
|
||||
buildUrl
|
||||
} = require('../normalize')
|
||||
|
||||
module.exports = class Auth {
|
||||
constructor (options) {
|
||||
this.options = Object.assign({
|
||||
baseUrl: 'https://api.weixin.qq.com',
|
||||
timeout: 5000
|
||||
}, options)
|
||||
}
|
||||
|
||||
async _requestWxOpenapi ({ name, url, data, options }) {
|
||||
const defaultOptions = {
|
||||
method: 'GET',
|
||||
dataType: 'json',
|
||||
dataAsQueryString: true,
|
||||
timeout: this.options.timeout
|
||||
}
|
||||
const result = await callWxOpenApi({
|
||||
name: `auth.${name}`,
|
||||
url: `${this.options.baseUrl}${buildUrl(url, data)}`,
|
||||
data,
|
||||
options,
|
||||
defaultOptions
|
||||
})
|
||||
return result
|
||||
}
|
||||
|
||||
async code2Session (code) {
|
||||
const url = '/sns/jscode2session'
|
||||
const result = await this._requestWxOpenapi({
|
||||
name: 'code2Session',
|
||||
url,
|
||||
data: {
|
||||
grant_type: 'authorization_code',
|
||||
appid: this.options.appId,
|
||||
secret: this.options.secret,
|
||||
js_code: code
|
||||
}
|
||||
})
|
||||
return result
|
||||
}
|
||||
|
||||
async getOauthAccessToken (code) {
|
||||
const url = '/sns/oauth2/access_token'
|
||||
const result = await this._requestWxOpenapi({
|
||||
name: 'getOauthAccessToken',
|
||||
url,
|
||||
data: {
|
||||
grant_type: 'authorization_code',
|
||||
appid: this.options.appId,
|
||||
secret: this.options.secret,
|
||||
code
|
||||
}
|
||||
})
|
||||
if (result.expiresIn) {
|
||||
result.expired = Date.now() + result.expiresIn * 1000
|
||||
// delete result.expiresIn
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
async getUserInfo ({
|
||||
accessToken,
|
||||
openid
|
||||
} = {}) {
|
||||
const url = '/sns/userinfo'
|
||||
const {
|
||||
nickname,
|
||||
headimgurl: avatar
|
||||
} = await this._requestWxOpenapi({
|
||||
name: 'getUserInfo',
|
||||
url,
|
||||
data: {
|
||||
accessToken,
|
||||
openid,
|
||||
appid: this.options.appId,
|
||||
secret: this.options.secret,
|
||||
scope: 'snsapi_userinfo'
|
||||
}
|
||||
})
|
||||
return {
|
||||
nickname,
|
||||
avatar
|
||||
}
|
||||
}
|
||||
|
||||
async getPhoneNumber (accessToken, code) {
|
||||
const url = `/wxa/business/getuserphonenumber?access_token=${accessToken}`
|
||||
const { phoneInfo } = await this._requestWxOpenapi({
|
||||
name: 'getPhoneNumber',
|
||||
url,
|
||||
data: {
|
||||
code
|
||||
},
|
||||
options: {
|
||||
method: 'POST',
|
||||
dataAsQueryString: false,
|
||||
headers: {
|
||||
'content-type': 'application/json'
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
purePhoneNumber: phoneInfo.purePhoneNumber
|
||||
}
|
||||
}
|
||||
}
|
95
uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/third-party/weixin/normalize.js
vendored
Normal file
95
uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/third-party/weixin/normalize.js
vendored
Normal file
@ -0,0 +1,95 @@
|
||||
const {
|
||||
UniCloudError
|
||||
} = require('../../../common/error')
|
||||
const {
|
||||
camel2snakeJson, snake2camelJson
|
||||
} = require('../../../common/utils')
|
||||
|
||||
function generateApiResult (apiName, data) {
|
||||
if (data.errcode) {
|
||||
throw new UniCloudError({
|
||||
code: data.errcode || -2,
|
||||
message: data.errmsg || `${apiName} fail`
|
||||
})
|
||||
} else {
|
||||
delete data.errcode
|
||||
delete data.errmsg
|
||||
return {
|
||||
...data,
|
||||
errMsg: `${apiName} ok`,
|
||||
errCode: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function nomalizeError (apiName, error) {
|
||||
throw new UniCloudError({
|
||||
code: error.code || -2,
|
||||
message: error.message || `${apiName} fail`
|
||||
})
|
||||
}
|
||||
|
||||
// 微信openapi接口接收蛇形(snake case)参数返回蛇形参数,这里进行转化,如果是formdata里面的参数需要在对应api实现时就转为蛇形
|
||||
async function callWxOpenApi ({
|
||||
name,
|
||||
url,
|
||||
data,
|
||||
options,
|
||||
defaultOptions
|
||||
}) {
|
||||
let result = {}
|
||||
// 获取二维码的接口wxacode.get和wxacode.getUnlimited不可以传入access_token(可能有其他接口也不可以),否则会返回data format error
|
||||
const dataCopy = camel2snakeJson(Object.assign({}, data))
|
||||
if (dataCopy && dataCopy.access_token) {
|
||||
delete dataCopy.access_token
|
||||
}
|
||||
try {
|
||||
options = Object.assign({}, defaultOptions, options, { data: dataCopy })
|
||||
result = await uniCloud.httpclient.request(url, options)
|
||||
} catch (e) {
|
||||
return nomalizeError(name, e)
|
||||
}
|
||||
|
||||
// 有几个接口成功返回buffer失败返回json,对这些接口统一成返回buffer,然后分别解析
|
||||
let resData = result.data
|
||||
const contentType = result.headers['content-type']
|
||||
if (
|
||||
Buffer.isBuffer(resData) &&
|
||||
(contentType.indexOf('text/plain') === 0 ||
|
||||
contentType.indexOf('application/json') === 0)
|
||||
) {
|
||||
try {
|
||||
resData = JSON.parse(resData.toString())
|
||||
} catch (e) {
|
||||
resData = resData.toString()
|
||||
}
|
||||
} else if (Buffer.isBuffer(resData)) {
|
||||
resData = {
|
||||
buffer: resData,
|
||||
contentType
|
||||
}
|
||||
}
|
||||
return snake2camelJson(
|
||||
generateApiResult(
|
||||
name,
|
||||
resData || {
|
||||
errCode: -2,
|
||||
errMsg: 'Request failed'
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
function buildUrl (url, data) {
|
||||
let query = ''
|
||||
if (data && data.accessToken) {
|
||||
const divider = url.indexOf('?') > -1 ? '&' : '?'
|
||||
query = `${divider}access_token=${data.accessToken}`
|
||||
}
|
||||
return `${url}${query}`
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
callWxOpenApi,
|
||||
buildUrl
|
||||
}
|
87
uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/third-party/weixin/utils.js
vendored
Normal file
87
uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/third-party/weixin/utils.js
vendored
Normal file
@ -0,0 +1,87 @@
|
||||
const crypto = require('crypto')
|
||||
const {
|
||||
isPlainObject
|
||||
} = require('../../../common/utils')
|
||||
|
||||
// 退款通知解密key=md5(key)
|
||||
function decryptData (encryptedData, key, iv = '') {
|
||||
// 解密
|
||||
const decipher = crypto.createDecipheriv('aes-128-cbc', key, iv)
|
||||
// 设置自动 padding 为 true,删除填充补位
|
||||
decipher.setAutoPadding(true)
|
||||
let decoded = decipher.update(encryptedData, 'base64', 'utf8')
|
||||
decoded += decipher.final('utf8')
|
||||
return decoded
|
||||
}
|
||||
|
||||
function md5 (str, encoding = 'utf8') {
|
||||
return crypto
|
||||
.createHash('md5')
|
||||
.update(str, encoding)
|
||||
.digest('hex')
|
||||
}
|
||||
|
||||
function sha256 (str, key, encoding = 'utf8') {
|
||||
return crypto
|
||||
.createHmac('sha256', key)
|
||||
.update(str, encoding)
|
||||
.digest('hex')
|
||||
}
|
||||
|
||||
function getSignStr (obj) {
|
||||
return Object.keys(obj)
|
||||
.filter(key => key !== 'sign' && obj[key] !== undefined && obj[key] !== '')
|
||||
.sort()
|
||||
.map(key => key + '=' + obj[key])
|
||||
.join('&')
|
||||
}
|
||||
|
||||
function getNonceStr (length = 16) {
|
||||
let str = ''
|
||||
while (str.length < length) {
|
||||
str += Math.random().toString(32).substring(2)
|
||||
}
|
||||
return str.substring(0, length)
|
||||
}
|
||||
|
||||
// 简易版Object转XML,只可在微信支付时使用,不支持嵌套
|
||||
function buildXML (obj, rootName = 'xml') {
|
||||
const content = Object.keys(obj).map(item => {
|
||||
if (isPlainObject(obj[item])) {
|
||||
return `<${item}><![CDATA[${JSON.stringify(obj[item])}]]></${item}>`
|
||||
} else {
|
||||
return `<${item}><![CDATA[${obj[item]}]]></${item}>`
|
||||
}
|
||||
})
|
||||
return `<${rootName}>${content.join('')}</${rootName}>`
|
||||
}
|
||||
|
||||
function isXML (str) {
|
||||
const reg = /^(<\?xml.*\?>)?(\r?\n)*<xml>(.|\r?\n)*<\/xml>$/i
|
||||
return reg.test(str.trim())
|
||||
};
|
||||
|
||||
// 简易版XML转Object,只可在微信支付时使用,不支持嵌套
|
||||
function parseXML (xml) {
|
||||
const xmlReg = /<(?:xml|root).*?>([\s|\S]*)<\/(?:xml|root)>/
|
||||
const str = xmlReg.exec(xml)[1]
|
||||
const obj = {}
|
||||
const nodeReg = /<(.*?)>(?:<!\[CDATA\[){0,1}(.*?)(?:\]\]>){0,1}<\/.*?>/g
|
||||
let matches = null
|
||||
// eslint-disable-next-line no-cond-assign
|
||||
while ((matches = nodeReg.exec(str))) {
|
||||
obj[matches[1]] = matches[2]
|
||||
}
|
||||
return obj
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
decryptData,
|
||||
md5,
|
||||
sha256,
|
||||
getSignStr,
|
||||
getNonceStr,
|
||||
buildXML,
|
||||
parseXML,
|
||||
isXML
|
||||
}
|
@ -0,0 +1,98 @@
|
||||
const {
|
||||
dbCmd,
|
||||
userCollection
|
||||
} = require('../../common/constants')
|
||||
const {
|
||||
USER_IDENTIFIER
|
||||
} = require('../../common/constants')
|
||||
const {
|
||||
batchFindObjctValue,
|
||||
getType,
|
||||
isMatchUserApp
|
||||
} = require('../../common/utils')
|
||||
|
||||
/**
|
||||
* 查询满足条件的用户
|
||||
* @param {Object} params
|
||||
* @param {Object} params.userQuery 用户唯一标识组成的查询条件
|
||||
* @param {Object} params.authorizedApp 用户允许登录的应用
|
||||
* @returns userMatched 满足条件的用户列表
|
||||
*/
|
||||
async function findUser (params = {}) {
|
||||
const {
|
||||
userQuery,
|
||||
authorizedApp = []
|
||||
} = params
|
||||
const condition = getUserQueryCondition(userQuery)
|
||||
if (condition.length === 0) {
|
||||
throw new Error('Invalid user query')
|
||||
}
|
||||
const authorizedAppType = getType(authorizedApp)
|
||||
if (authorizedAppType !== 'string' && authorizedAppType !== 'array') {
|
||||
throw new Error('Invalid authorized app')
|
||||
}
|
||||
|
||||
let finalQuery
|
||||
|
||||
if (condition.length === 1) {
|
||||
finalQuery = condition[0]
|
||||
} else {
|
||||
finalQuery = dbCmd.or(condition)
|
||||
}
|
||||
const userQueryRes = await userCollection.where(finalQuery).get()
|
||||
return {
|
||||
total: userQueryRes.data.length,
|
||||
userMatched: userQueryRes.data.filter(item => {
|
||||
return isMatchUserApp(item.dcloud_appid, authorizedApp)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function getUserIdentifier (userRecord = {}) {
|
||||
const keys = Object.keys(USER_IDENTIFIER)
|
||||
return batchFindObjctValue(userRecord, keys)
|
||||
}
|
||||
|
||||
function getUserQueryCondition (userRecord = {}) {
|
||||
const userIdentifier = getUserIdentifier(userRecord)
|
||||
const condition = []
|
||||
for (const key in userIdentifier) {
|
||||
const value = userIdentifier[key]
|
||||
if (!value) {
|
||||
// 过滤所有value为假值的条件,在查询用户时没有意义
|
||||
continue
|
||||
}
|
||||
const queryItem = {
|
||||
[key]: value
|
||||
}
|
||||
// 为兼容用户老数据用户名及邮箱需要同时查小写及原始大小写数据
|
||||
if (key === 'mobile') {
|
||||
queryItem.mobile_confirmed = 1
|
||||
} else if (key === 'email') {
|
||||
queryItem.email_confirmed = 1
|
||||
const email = userIdentifier.email
|
||||
if (email.toLowerCase() !== email) {
|
||||
condition.push({
|
||||
email: email.toLowerCase(),
|
||||
email_confirmed: 1
|
||||
})
|
||||
}
|
||||
} else if (key === 'username') {
|
||||
const username = userIdentifier.username
|
||||
if (username.toLowerCase() !== username) {
|
||||
condition.push({
|
||||
username: username.toLowerCase()
|
||||
})
|
||||
}
|
||||
} else if (key === 'identities') {
|
||||
queryItem.identities = dbCmd.elemMatch(value)
|
||||
}
|
||||
condition.push(queryItem)
|
||||
}
|
||||
return condition
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
findUser,
|
||||
getUserIdentifier
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
const {
|
||||
ERROR
|
||||
} = require('../../common/error')
|
||||
|
||||
async function getNeedCaptcha ({
|
||||
uid,
|
||||
username,
|
||||
mobile,
|
||||
email,
|
||||
type = 'login',
|
||||
limitDuration = 7200000, // 两小时
|
||||
limitTimes = 3 // 记录次数
|
||||
} = {}) {
|
||||
const db = uniCloud.database()
|
||||
const dbCmd = db.command
|
||||
// 当用户最近“2小时内(limitDuration)”登录失败达到3次(limitTimes)时。要求用户提交验证码
|
||||
const now = Date.now()
|
||||
const uniIdLogCollection = db.collection('uni-id-log')
|
||||
const userIdentifier = {
|
||||
user_id: uid,
|
||||
username,
|
||||
mobile,
|
||||
email
|
||||
}
|
||||
|
||||
let totalKey = 0; let deleteKey = 0
|
||||
for (const key in userIdentifier) {
|
||||
totalKey++
|
||||
if (!userIdentifier[key] || typeof userIdentifier[key] !== 'string') {
|
||||
deleteKey++
|
||||
delete userIdentifier[key]
|
||||
}
|
||||
}
|
||||
|
||||
if (deleteKey === totalKey) {
|
||||
throw new Error('System error') // 正常情况下不会进入此条件,但是考虑到后续会有其他开发者修改此云对象,在此处做一个判断
|
||||
}
|
||||
|
||||
const {
|
||||
data: recentRecord
|
||||
} = await uniIdLogCollection.where({
|
||||
ip: this.getUniversalClientInfo().clientIP,
|
||||
...userIdentifier,
|
||||
type,
|
||||
create_date: dbCmd.gt(now - limitDuration)
|
||||
})
|
||||
.orderBy('create_date', 'desc')
|
||||
.limit(limitTimes)
|
||||
.get()
|
||||
return recentRecord.length === limitTimes && recentRecord.every(item => item.state === 0)
|
||||
}
|
||||
|
||||
async function verifyCaptcha (params = {}) {
|
||||
const {
|
||||
captcha,
|
||||
scene
|
||||
} = params
|
||||
if (!captcha) {
|
||||
throw {
|
||||
errCode: ERROR.CAPTCHA_REQUIRED
|
||||
}
|
||||
}
|
||||
const payload = await this.uniCaptcha.verify({
|
||||
deviceId: this.getUniversalClientInfo().deviceId,
|
||||
captcha,
|
||||
scene
|
||||
})
|
||||
if (payload.errCode) {
|
||||
throw payload
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getNeedCaptcha,
|
||||
verifyCaptcha
|
||||
}
|
@ -0,0 +1,137 @@
|
||||
const {
|
||||
getWeixinPlatform
|
||||
} = require('./weixin')
|
||||
const createConfig = require('uni-config-center')
|
||||
|
||||
const requiredConfig = {
|
||||
'web.weixin-h5': ['appid', 'appsecret'],
|
||||
'web.weixin-web': ['appid', 'appsecret'],
|
||||
'app.weixin': ['appid', 'appsecret'],
|
||||
'mp-weixin.weixin': ['appid', 'appsecret'],
|
||||
'app.qq': ['appid', 'appsecret'],
|
||||
'mp-alipay.alipay': ['appid', 'privateKey'],
|
||||
'app.apple': ['bundleId']
|
||||
}
|
||||
|
||||
const uniIdConfig = createConfig({
|
||||
pluginId: 'uni-id'
|
||||
})
|
||||
|
||||
class ConfigUtils {
|
||||
constructor({
|
||||
context
|
||||
} = {}) {
|
||||
this.context = context
|
||||
this.clientInfo = context.getUniversalClientInfo()
|
||||
const {
|
||||
appId,
|
||||
uniPlatform
|
||||
} = this.clientInfo
|
||||
this.appId = appId
|
||||
switch (uniPlatform) {
|
||||
case 'app':
|
||||
case 'app-plus':
|
||||
case 'app-android':
|
||||
case 'app-ios':
|
||||
this.platform = 'app'
|
||||
break
|
||||
case 'web':
|
||||
case 'h5':
|
||||
this.platform = 'web'
|
||||
break
|
||||
default:
|
||||
this.platform = uniPlatform
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
getConfigArray() {
|
||||
let configContent
|
||||
try {
|
||||
configContent = require('uni-config-center/uni-id/config.json')
|
||||
} catch (error) {
|
||||
throw new Error('Invalid config file\n' + error.message)
|
||||
}
|
||||
if (configContent[0]) {
|
||||
return Object.values(configContent)
|
||||
}
|
||||
configContent.isDefaultConfig = true
|
||||
return [configContent]
|
||||
}
|
||||
|
||||
getAppConfig() {
|
||||
const configArray = this.getConfigArray()
|
||||
return configArray.find(item => item.dcloudAppid === this.appId) || configArray.find(item => item.isDefaultConfig)
|
||||
}
|
||||
|
||||
getPlatformConfig() {
|
||||
const appConfig = this.getAppConfig()
|
||||
if (!appConfig) {
|
||||
throw new Error(
|
||||
`Config for current app (${this.appId}) was not found, please check your config file or client appId`)
|
||||
}
|
||||
const platform = this.platform
|
||||
if (
|
||||
(this.platform === 'app' && appConfig['app-plus']) ||
|
||||
(this.platform === 'web' && appConfig.h5)
|
||||
) {
|
||||
throw new Error(
|
||||
`Client platform is ${this.platform}, but ${this.platform === 'web' ? 'h5' : 'app-plus'} was found in config. Please refer to: https://uniapp.dcloud.net.cn/uniCloud/uni-id-summary?id=m-to-co`
|
||||
)
|
||||
}
|
||||
|
||||
const defaultConfig = {
|
||||
tokenExpiresIn: 7200,
|
||||
tokenExpiresThreshold: 1200,
|
||||
passwordErrorLimit: 6,
|
||||
passwordErrorRetryTime: 3600
|
||||
}
|
||||
return Object.assign(defaultConfig, appConfig, appConfig[platform])
|
||||
}
|
||||
|
||||
getOauthProvider({
|
||||
provider
|
||||
} = {}) {
|
||||
const clientPlatform = this.platform
|
||||
let oatuhProivder = provider
|
||||
if (provider === 'weixin' && clientPlatform === 'web') {
|
||||
const weixinPlatform = getWeixinPlatform.call(this.context)
|
||||
if (weixinPlatform === 'h5' || weixinPlatform === 'web') {
|
||||
oatuhProivder = 'weixin-' + weixinPlatform // weixin-h5 公众号,weixin-web pc端
|
||||
}
|
||||
}
|
||||
return oatuhProivder
|
||||
}
|
||||
|
||||
getOauthConfig({
|
||||
provider
|
||||
} = {}) {
|
||||
const config = this.getPlatformConfig()
|
||||
const clientPlatform = this.platform
|
||||
const oatuhProivder = this.getOauthProvider({
|
||||
provider
|
||||
})
|
||||
const requireConfigKey = requiredConfig[`${clientPlatform}.${oatuhProivder}`] || []
|
||||
if (!config.oauth || !config.oauth[oatuhProivder]) {
|
||||
throw new Error(`Config param required: ${clientPlatform}.oauth.${oatuhProivder}`)
|
||||
}
|
||||
const oauthConfig = config.oauth[oatuhProivder]
|
||||
requireConfigKey.forEach((item) => {
|
||||
if (!oauthConfig[item]) {
|
||||
throw new Error(`Config param required: ${clientPlatform}.oauth.${oatuhProivder}.${item}`)
|
||||
}
|
||||
})
|
||||
return oauthConfig
|
||||
}
|
||||
|
||||
getHooks() {
|
||||
if (uniIdConfig.hasFile('hooks/index.js')) {
|
||||
return require(
|
||||
uniIdConfig.resolve('hooks/index.js')
|
||||
)
|
||||
}
|
||||
return {}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ConfigUtils
|
@ -0,0 +1,192 @@
|
||||
const {
|
||||
dbCmd,
|
||||
userCollection
|
||||
} = require('../../common/constants')
|
||||
const {
|
||||
ERROR
|
||||
} = require('../../common/error')
|
||||
/**
|
||||
* 获取随机邀请码,邀请码由大写字母加数字组成,由于存在手动输入邀请码的场景,从可选字符中去除 0、1、I、O
|
||||
* @param {number} len 邀请码长度,默认6位
|
||||
* @returns {string} 随机邀请码
|
||||
*/
|
||||
function getRandomInviteCode (len = 6) {
|
||||
const charArr = ['2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'L', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']
|
||||
let code = ''
|
||||
for (let i = 0; i < len; i++) {
|
||||
code += charArr[Math.floor(Math.random() * charArr.length)]
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取可用的邀请码,至多尝试十次以获取可用邀请码。从10亿可选值中随机,碰撞概率较低
|
||||
* 也有其他方案可以尝试,比如在数据库内设置一个从0开始计数的数字,每次调用此方法时使用updateAndReturn使数字加1并返回加1后的值,根据这个值生成对应的邀请码,比如(22222A + 1 == 22222B),此方式性能理论更好,但是不适用于旧项目
|
||||
* @param {object} param
|
||||
* @param {string} param.inviteCode 初始随机邀请码
|
||||
*/
|
||||
async function getValidInviteCode () {
|
||||
let retry = 10
|
||||
let code
|
||||
let codeValid = false
|
||||
while (retry > 0 && !codeValid) {
|
||||
retry--
|
||||
code = getRandomInviteCode()
|
||||
const getUserRes = await userCollection.where({
|
||||
my_invite_code: code
|
||||
}).limit(1).get()
|
||||
if (getUserRes.data.length === 0) {
|
||||
codeValid = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if (!codeValid) {
|
||||
throw {
|
||||
errCode: ERROR.SET_INVITE_CODE_FAILED
|
||||
}
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据邀请码查询邀请人
|
||||
* @param {object} param
|
||||
* @param {string} param.inviteCode 邀请码
|
||||
* @param {string} param.queryUid 受邀人id,非空时校验不可被下家或自己邀请
|
||||
* @returns
|
||||
*/
|
||||
async function findUserByInviteCode ({
|
||||
inviteCode,
|
||||
queryUid
|
||||
} = {}) {
|
||||
if (typeof inviteCode !== 'string') {
|
||||
throw {
|
||||
errCode: ERROR.SYSTEM_ERROR
|
||||
}
|
||||
}
|
||||
// 根据邀请码查询邀请人
|
||||
let getInviterRes
|
||||
if (queryUid) {
|
||||
getInviterRes = await userCollection.where({
|
||||
_id: dbCmd.neq(queryUid),
|
||||
inviter_uid: dbCmd.not(dbCmd.all([queryUid])),
|
||||
my_invite_code: inviteCode
|
||||
}).get()
|
||||
} else {
|
||||
getInviterRes = await userCollection.where({
|
||||
my_invite_code: inviteCode
|
||||
}).get()
|
||||
}
|
||||
if (getInviterRes.data.length > 1) {
|
||||
// 正常情况下不可能进入此条件,以防用户自行修改数据库出错,在此做出判断
|
||||
throw {
|
||||
errCode: ERROR.SYSTEM_ERROR
|
||||
}
|
||||
}
|
||||
const inviterRecord = getInviterRes.data[0]
|
||||
if (!inviterRecord) {
|
||||
throw {
|
||||
errCode: ERROR.INVALID_INVITE_CODE
|
||||
}
|
||||
}
|
||||
return inviterRecord
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据邀请码生成邀请信息
|
||||
* @param {object} param
|
||||
* @param {string} param.inviteCode 邀请码
|
||||
* @param {string} param.queryUid 受邀人id,非空时校验不可被下家或自己邀请
|
||||
* @returns
|
||||
*/
|
||||
async function generateInviteInfo ({
|
||||
inviteCode,
|
||||
queryUid
|
||||
} = {}) {
|
||||
const inviterRecord = await findUserByInviteCode({
|
||||
inviteCode,
|
||||
queryUid
|
||||
})
|
||||
// 倒叙拼接当前用户邀请链
|
||||
const inviterUid = inviterRecord.inviter_uid || []
|
||||
inviterUid.unshift(inviterRecord._id)
|
||||
return {
|
||||
inviterUid,
|
||||
inviteTime: Date.now()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查当前用户是否可以接受邀请,如果可以返回用户记录
|
||||
* @param {string} uid
|
||||
*/
|
||||
async function checkInviteInfo (uid) {
|
||||
// 检查当前用户是否已有邀请人
|
||||
const getUserRes = await userCollection.doc(uid).field({
|
||||
my_invite_code: true,
|
||||
inviter_uid: true
|
||||
}).get()
|
||||
const userRecord = getUserRes.data[0]
|
||||
if (!userRecord) {
|
||||
throw {
|
||||
errCode: ERROR.ACCOUNT_NOT_EXISTS
|
||||
}
|
||||
}
|
||||
if (userRecord.inviter_uid && userRecord.inviter_uid.length > 0) {
|
||||
throw {
|
||||
errCode: ERROR.CHANGE_INVITER_FORBIDDEN
|
||||
}
|
||||
}
|
||||
return userRecord
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定用户接受邀请码邀请
|
||||
* @param {object} param
|
||||
* @param {string} param.uid 用户uid
|
||||
* @param {string} param.inviteCode 邀请人的邀请码
|
||||
* @returns
|
||||
*/
|
||||
async function acceptInvite ({
|
||||
uid,
|
||||
inviteCode
|
||||
} = {}) {
|
||||
await checkInviteInfo(uid)
|
||||
const {
|
||||
inviterUid,
|
||||
inviteTime
|
||||
} = await generateInviteInfo({
|
||||
inviteCode,
|
||||
queryUid: uid
|
||||
})
|
||||
|
||||
if (inviterUid === uid) {
|
||||
throw {
|
||||
errCode: ERROR.INVALID_INVITE_CODE
|
||||
}
|
||||
}
|
||||
|
||||
// 更新当前用户的邀请人信息
|
||||
await userCollection.doc(uid).update({
|
||||
inviter_uid: inviterUid,
|
||||
invite_time: inviteTime
|
||||
})
|
||||
|
||||
// 更新当前用户邀请的用户的邀请人信息,这步可能较为耗时
|
||||
await userCollection.where({
|
||||
inviter_uid: uid
|
||||
}).update({
|
||||
inviter_uid: dbCmd.push(inviterUid)
|
||||
})
|
||||
|
||||
return {
|
||||
errCode: 0,
|
||||
errMsg: ''
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
acceptInvite,
|
||||
generateInviteInfo,
|
||||
getValidInviteCode
|
||||
}
|
@ -0,0 +1,246 @@
|
||||
const {
|
||||
findUser
|
||||
} = require('./account')
|
||||
const {
|
||||
userCollection,
|
||||
LOG_TYPE
|
||||
} = require('../../common/constants')
|
||||
const {
|
||||
ERROR
|
||||
} = require('../../common/error')
|
||||
const {
|
||||
logout
|
||||
} = require('./logout')
|
||||
const PasswordUtils = require('./password')
|
||||
|
||||
async function realPreLogin (params = {}) {
|
||||
const {
|
||||
user
|
||||
} = params
|
||||
const appId = this.getUniversalClientInfo().appId
|
||||
const {
|
||||
total,
|
||||
userMatched
|
||||
} = await findUser({
|
||||
userQuery: user,
|
||||
authorizedApp: appId
|
||||
})
|
||||
if (userMatched.length === 0) {
|
||||
if (total > 0) {
|
||||
throw {
|
||||
errCode: ERROR.ACCOUNT_NOT_EXISTS_IN_CURRENT_APP
|
||||
}
|
||||
}
|
||||
throw {
|
||||
errCode: ERROR.ACCOUNT_NOT_EXISTS
|
||||
}
|
||||
} else if (userMatched.length > 1) {
|
||||
throw {
|
||||
errCode: ERROR.ACCOUNT_CONFLICT
|
||||
}
|
||||
}
|
||||
const userRecord = userMatched[0]
|
||||
checkLoginUserRecord(userRecord)
|
||||
return userRecord
|
||||
}
|
||||
|
||||
async function preLogin (params = {}) {
|
||||
const {
|
||||
user
|
||||
} = params
|
||||
try {
|
||||
const user = await realPreLogin.call(this, params)
|
||||
return user
|
||||
} catch (error) {
|
||||
await this.middleware.uniIdLog({
|
||||
success: false,
|
||||
data: user,
|
||||
type: LOG_TYPE.LOGIN
|
||||
})
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
async function preLoginWithPassword (params = {}) {
|
||||
const {
|
||||
user,
|
||||
password
|
||||
} = params
|
||||
try {
|
||||
const userRecord = await realPreLogin.call(this, params)
|
||||
const {
|
||||
passwordErrorLimit,
|
||||
passwordErrorRetryTime
|
||||
} = this.config
|
||||
const {
|
||||
clientIP
|
||||
} = this.getUniversalClientInfo()
|
||||
// 根据ip地址,密码错误次数过多,锁定登录
|
||||
let loginIPLimit = userRecord.login_ip_limit || []
|
||||
// 清理无用记录
|
||||
loginIPLimit = loginIPLimit.filter(item => item.last_error_time > Date.now() - passwordErrorRetryTime * 1000)
|
||||
let currentIPLimit = loginIPLimit.find(item => item.ip === clientIP)
|
||||
if (currentIPLimit && currentIPLimit.error_times >= passwordErrorLimit) {
|
||||
throw {
|
||||
errCode: ERROR.PASSWORD_ERROR_EXCEED_LIMIT
|
||||
}
|
||||
}
|
||||
const passwordUtils = new PasswordUtils({
|
||||
userRecord,
|
||||
clientInfo: this.getUniversalClientInfo(),
|
||||
passwordSecret: this.config.passwordSecret
|
||||
})
|
||||
|
||||
const {
|
||||
success: checkPasswordSuccess,
|
||||
refreshPasswordInfo
|
||||
} = passwordUtils.checkUserPassword({
|
||||
password
|
||||
})
|
||||
if (!checkPasswordSuccess) {
|
||||
// 更新用户ip对应的密码错误记录
|
||||
if (!currentIPLimit) {
|
||||
currentIPLimit = {
|
||||
ip: clientIP,
|
||||
error_times: 1,
|
||||
last_error_time: Date.now()
|
||||
}
|
||||
loginIPLimit.push(currentIPLimit)
|
||||
} else {
|
||||
currentIPLimit.error_times++
|
||||
currentIPLimit.last_error_time = Date.now()
|
||||
}
|
||||
await userCollection.doc(userRecord._id).update({
|
||||
login_ip_limit: loginIPLimit
|
||||
})
|
||||
throw {
|
||||
errCode: ERROR.PASSWORD_ERROR
|
||||
}
|
||||
}
|
||||
const extraData = {}
|
||||
if (refreshPasswordInfo) {
|
||||
extraData.password = refreshPasswordInfo.passwordHash
|
||||
extraData.password_secret_version = refreshPasswordInfo.version
|
||||
}
|
||||
|
||||
const currentIPLimitIndex = loginIPLimit.indexOf(currentIPLimit)
|
||||
if (currentIPLimitIndex > -1) {
|
||||
loginIPLimit.splice(currentIPLimitIndex, 1)
|
||||
}
|
||||
extraData.login_ip_limit = loginIPLimit
|
||||
return {
|
||||
user: userRecord,
|
||||
extraData
|
||||
}
|
||||
} catch (error) {
|
||||
await this.middleware.uniIdLog({
|
||||
success: false,
|
||||
data: user,
|
||||
type: LOG_TYPE.LOGIN
|
||||
})
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
function checkLoginUserRecord (user) {
|
||||
switch (user.status) {
|
||||
case undefined:
|
||||
case 0:
|
||||
break
|
||||
case 1:
|
||||
throw {
|
||||
errCode: ERROR.ACCOUNT_BANNED
|
||||
}
|
||||
case 2:
|
||||
throw {
|
||||
errCode: ERROR.ACCOUNT_AUDITING
|
||||
}
|
||||
case 3:
|
||||
throw {
|
||||
errCode: ERROR.ACCOUNT_AUDIT_FAILED
|
||||
}
|
||||
case 4:
|
||||
throw {
|
||||
errCode: ERROR.ACCOUNT_CLOSED
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
async function thirdPartyLogin (params = {}) {
|
||||
const {
|
||||
user
|
||||
} = params
|
||||
return {
|
||||
mobileConfirmed: !!user.mobile_confirmed,
|
||||
emailConfirmed: !!user.email_confirmed
|
||||
}
|
||||
}
|
||||
|
||||
async function postLogin (params = {}) {
|
||||
const {
|
||||
user,
|
||||
extraData,
|
||||
isThirdParty = false
|
||||
} = params
|
||||
const {
|
||||
clientIP
|
||||
} = this.getUniversalClientInfo()
|
||||
const uniIdToken = this.getUniversalUniIdToken()
|
||||
const uid = user._id
|
||||
const updateData = {
|
||||
last_login_date: Date.now(),
|
||||
last_login_ip: clientIP,
|
||||
...extraData
|
||||
}
|
||||
const createTokenRes = await this.uniIdCommon.createToken({
|
||||
uid
|
||||
})
|
||||
|
||||
const {
|
||||
errCode,
|
||||
token,
|
||||
tokenExpired
|
||||
} = createTokenRes
|
||||
if (errCode) {
|
||||
throw createTokenRes
|
||||
}
|
||||
|
||||
if (uniIdToken) {
|
||||
try {
|
||||
await logout.call(this)
|
||||
} catch (error) {}
|
||||
}
|
||||
|
||||
await userCollection.doc(uid).update(updateData)
|
||||
await this.middleware.uniIdLog({
|
||||
data: {
|
||||
user_id: uid
|
||||
},
|
||||
type: LOG_TYPE.LOGIN
|
||||
})
|
||||
return {
|
||||
errCode: 0,
|
||||
newToken: {
|
||||
token,
|
||||
tokenExpired
|
||||
},
|
||||
uid,
|
||||
...(
|
||||
isThirdParty
|
||||
? thirdPartyLogin({
|
||||
user
|
||||
})
|
||||
: {}
|
||||
),
|
||||
passwordConfirmed: !!user.password
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
preLogin,
|
||||
postLogin,
|
||||
checkLoginUserRecord,
|
||||
preLoginWithPassword
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
const {
|
||||
dbCmd,
|
||||
LOG_TYPE,
|
||||
deviceCollection,
|
||||
userCollection
|
||||
} = require('../../common/constants')
|
||||
|
||||
async function logout () {
|
||||
const {
|
||||
deviceId
|
||||
} = this.getUniversalClientInfo()
|
||||
const uniIdToken = this.getUniversalUniIdToken()
|
||||
const payload = await this.uniIdCommon.checkToken(
|
||||
uniIdToken,
|
||||
{
|
||||
autoRefresh: false
|
||||
}
|
||||
)
|
||||
if (payload.errCode) {
|
||||
throw payload
|
||||
}
|
||||
const uid = payload.uid
|
||||
|
||||
// 删除token
|
||||
await userCollection.doc(uid).update({
|
||||
token: dbCmd.pull(uniIdToken)
|
||||
})
|
||||
|
||||
// 仅当device表的device_id和user_id均对应时才进行更新
|
||||
await deviceCollection.where({
|
||||
device_id: deviceId,
|
||||
user_id: uid
|
||||
}).update({
|
||||
token_expired: 0
|
||||
})
|
||||
await this.middleware.uniIdLog({
|
||||
data: {
|
||||
user_id: uid
|
||||
},
|
||||
type: LOG_TYPE.LOGOUT
|
||||
})
|
||||
return {
|
||||
errCode: 0
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
logout
|
||||
}
|
@ -0,0 +1,261 @@
|
||||
const {
|
||||
getType
|
||||
} = require('../../common/utils')
|
||||
const crypto = require('crypto')
|
||||
const createConfig = require('uni-config-center')
|
||||
const shareConfig = createConfig({
|
||||
pluginId: 'uni-id'
|
||||
})
|
||||
let customPassword = {}
|
||||
if (shareConfig.hasFile('custom-password.js')) {
|
||||
customPassword = shareConfig.requireFile('custom-password.js') || {}
|
||||
}
|
||||
|
||||
const passwordAlgorithmMap = {
|
||||
UNI_ID_HMAC_SHA1: 'hmac-sha1',
|
||||
UNI_ID_HMAC_SHA256: 'hmac-sha256',
|
||||
UNI_ID_CUSTOM: 'custom'
|
||||
}
|
||||
|
||||
const passwordAlgorithmKeyMap = Object.keys(passwordAlgorithmMap).reduce((res, item) => {
|
||||
res[passwordAlgorithmMap[item]] = item
|
||||
return res
|
||||
}, {})
|
||||
|
||||
const passwordExtMethod = {
|
||||
[passwordAlgorithmMap.UNI_ID_HMAC_SHA1]: {
|
||||
verify ({ password }) {
|
||||
const { password_secret_version: passwordSecretVersion } = this.userRecord
|
||||
|
||||
const passwordSecret = this._getSecretByVersion({
|
||||
version: passwordSecretVersion
|
||||
})
|
||||
|
||||
const { passwordHash } = this.encrypt({
|
||||
password,
|
||||
passwordSecret
|
||||
})
|
||||
|
||||
return passwordHash === this.userRecord.password
|
||||
},
|
||||
encrypt ({ password, passwordSecret }) {
|
||||
const { value: secret, version } = passwordSecret
|
||||
const hmac = crypto.createHmac('sha1', secret.toString('ascii'))
|
||||
|
||||
hmac.update(password)
|
||||
|
||||
return {
|
||||
passwordHash: hmac.digest('hex'),
|
||||
version
|
||||
}
|
||||
}
|
||||
},
|
||||
[passwordAlgorithmMap.UNI_ID_HMAC_SHA256]: {
|
||||
verify ({ password }) {
|
||||
const parse = this._parsePassword()
|
||||
const passwordHash = crypto.createHmac(parse.algorithm, parse.salt).update(password).digest('hex')
|
||||
|
||||
return passwordHash === parse.hash
|
||||
},
|
||||
encrypt ({ password, passwordSecret }) {
|
||||
const { version } = passwordSecret
|
||||
|
||||
// 默认使用 sha256 加密算法
|
||||
const salt = crypto.randomBytes(10).toString('hex')
|
||||
const sha256Hash = crypto.createHmac(passwordAlgorithmMap.UNI_ID_HMAC_SHA256.substring(5), salt).update(password).digest('hex')
|
||||
const algorithm = passwordAlgorithmKeyMap[passwordAlgorithmMap.UNI_ID_HMAC_SHA256]
|
||||
// B 为固定值,对应 PasswordMethodMaps 中的 sha256算法
|
||||
// hash 格式 $[PasswordMethodFlagMapsKey]$[salt size]$[salt][Hash]
|
||||
const passwordHash = `$${algorithm}$${salt.length}$${salt}${sha256Hash}`
|
||||
|
||||
return {
|
||||
passwordHash,
|
||||
version
|
||||
}
|
||||
}
|
||||
},
|
||||
[passwordAlgorithmMap.UNI_ID_CUSTOM]: {
|
||||
verify ({ password, passwordSecret }) {
|
||||
if (!customPassword.verifyPassword) throw new Error('verifyPassword method not found in custom password file')
|
||||
|
||||
// return true or false
|
||||
return customPassword.verifyPassword({
|
||||
password,
|
||||
passwordSecret,
|
||||
userRecord: this.userRecord,
|
||||
clientInfo: this.clientInfo
|
||||
})
|
||||
},
|
||||
encrypt ({ password, passwordSecret }) {
|
||||
if (!customPassword.encryptPassword) throw new Error('encryptPassword method not found in custom password file')
|
||||
|
||||
// return object<{passwordHash: string, version: number}>
|
||||
return customPassword.encryptPassword({
|
||||
password,
|
||||
passwordSecret,
|
||||
clientInfo: this.clientInfo
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class PasswordUtils {
|
||||
constructor ({
|
||||
userRecord = {},
|
||||
clientInfo,
|
||||
passwordSecret
|
||||
} = {}) {
|
||||
if (!clientInfo) throw new Error('Invalid clientInfo')
|
||||
if (!passwordSecret) throw new Error('Invalid password secret')
|
||||
|
||||
this.clientInfo = clientInfo
|
||||
this.userRecord = userRecord
|
||||
this.passwordSecret = this.prePasswordSecret(passwordSecret)
|
||||
}
|
||||
|
||||
/**
|
||||
* passwordSecret 预处理
|
||||
* @param passwordSecret
|
||||
* @return {*[]}
|
||||
*/
|
||||
prePasswordSecret (passwordSecret) {
|
||||
const newPasswordSecret = []
|
||||
if (getType(passwordSecret) === 'string') {
|
||||
newPasswordSecret.push({
|
||||
value: passwordSecret,
|
||||
type: passwordAlgorithmMap.UNI_ID_HMAC_SHA1
|
||||
})
|
||||
} else if (getType(passwordSecret) === 'array') {
|
||||
for (const secret of passwordSecret.sort((a, b) => a.version - b.version)) {
|
||||
newPasswordSecret.push({
|
||||
...secret,
|
||||
// 没有 type 设置默认 type hmac-sha1
|
||||
type: secret.type || passwordAlgorithmMap.UNI_ID_HMAC_SHA1
|
||||
})
|
||||
}
|
||||
} else {
|
||||
throw new Error('Invalid password secret')
|
||||
}
|
||||
|
||||
return newPasswordSecret
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取最新加密密钥
|
||||
* @return {*}
|
||||
* @private
|
||||
*/
|
||||
_getLastestSecret () {
|
||||
return this.passwordSecret[this.passwordSecret.length - 1]
|
||||
}
|
||||
|
||||
_getOldestSecret () {
|
||||
return this.passwordSecret[0]
|
||||
}
|
||||
|
||||
_getSecretByVersion ({ version } = {}) {
|
||||
if (!version && version !== 0) {
|
||||
return this._getOldestSecret()
|
||||
}
|
||||
if (this.passwordSecret.length === 1) {
|
||||
return this.passwordSecret[0]
|
||||
}
|
||||
return this.passwordSecret.find(item => item.version === version)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取密码的验证/加密方法
|
||||
* @param passwordSecret
|
||||
* @return {*[]}
|
||||
* @private
|
||||
*/
|
||||
_getPasswordExt (passwordSecret) {
|
||||
const ext = passwordExtMethod[passwordSecret.type]
|
||||
if (!ext) {
|
||||
throw new Error(`暂不支持 ${passwordSecret.type} 类型的加密算法`)
|
||||
}
|
||||
|
||||
const passwordExt = Object.create(null)
|
||||
|
||||
for (const key in ext) {
|
||||
passwordExt[key] = ext[key].bind(Object.assign(this, Object.keys(ext).reduce((res, item) => {
|
||||
if (item !== key) {
|
||||
res[item] = ext[item].bind(this)
|
||||
}
|
||||
return res
|
||||
}, {})))
|
||||
}
|
||||
|
||||
return passwordExt
|
||||
}
|
||||
|
||||
_parsePassword () {
|
||||
const [algorithmKey = '', cost = 0, hashStr = ''] = this.userRecord.password.split('$').filter(key => key)
|
||||
const algorithm = passwordAlgorithmMap[algorithmKey] ? passwordAlgorithmMap[algorithmKey].substring(5) : null
|
||||
const salt = hashStr.substring(0, Number(cost))
|
||||
const hash = hashStr.substring(Number(cost))
|
||||
|
||||
return {
|
||||
algorithm,
|
||||
salt,
|
||||
hash
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成加密后的密码
|
||||
* @param {String} password 密码
|
||||
*/
|
||||
generatePasswordHash ({ password }) {
|
||||
if (!password) throw new Error('Invalid password')
|
||||
|
||||
const passwordSecret = this._getLastestSecret()
|
||||
const ext = this._getPasswordExt(passwordSecret)
|
||||
|
||||
const { passwordHash, version } = ext.encrypt({
|
||||
password,
|
||||
passwordSecret
|
||||
})
|
||||
|
||||
return {
|
||||
passwordHash,
|
||||
version
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 密码校验
|
||||
* @param {String} password
|
||||
* @param {Boolean} autoRefresh
|
||||
* @return {{refreshPasswordInfo: {version: *, passwordHash: *}, success: boolean}|{success: boolean}}
|
||||
*/
|
||||
checkUserPassword ({ password, autoRefresh = true }) {
|
||||
if (!password) throw new Error('Invalid password')
|
||||
|
||||
const { password_secret_version: passwordSecretVersion } = this.userRecord
|
||||
const passwordSecret = this._getSecretByVersion({
|
||||
version: passwordSecretVersion
|
||||
})
|
||||
const ext = this._getPasswordExt(passwordSecret)
|
||||
|
||||
const success = ext.verify({ password, passwordSecret })
|
||||
|
||||
if (!success) {
|
||||
return {
|
||||
success: false
|
||||
}
|
||||
}
|
||||
|
||||
let refreshPasswordInfo
|
||||
if (autoRefresh && passwordSecretVersion !== this._getLastestSecret().version) {
|
||||
refreshPasswordInfo = this.generatePasswordHash({ password })
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
refreshPasswordInfo
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = PasswordUtils
|
@ -0,0 +1,154 @@
|
||||
const {
|
||||
userCollection
|
||||
} = require('../../common/constants')
|
||||
const {
|
||||
ERROR
|
||||
} = require('../../common/error')
|
||||
|
||||
function getQQPlatform () {
|
||||
const platform = this.clientPlatform
|
||||
switch (platform) {
|
||||
case 'app':
|
||||
case 'app-plus':
|
||||
case 'app-android':
|
||||
case 'app-ios':
|
||||
return 'app'
|
||||
case 'mp-qq':
|
||||
return 'mp'
|
||||
default:
|
||||
throw new Error('Unsupported qq platform')
|
||||
}
|
||||
}
|
||||
|
||||
async function saveQQUserKey ({
|
||||
openid,
|
||||
sessionKey, // QQ小程序用户sessionKey
|
||||
accessToken, // App端QQ用户accessToken
|
||||
accessTokenExpired // App端QQ用户accessToken过期时间
|
||||
} = {}) {
|
||||
// 微信公众平台、开放平台refreshToken有效期均为30天(微信没有在网络请求里面返回30天这个值,务必注意未来可能出现调整,需及时更新此处逻辑)。
|
||||
// 此前QQ开放平台有调整过accessToken的过期时间:[access_token有效期由90天缩短至30天](https://wiki.connect.qq.com/%E3%80%90qq%E4%BA%92%E8%81%94%E3%80%91access_token%E6%9C%89%E6%95%88%E6%9C%9F%E8%B0%83%E6%95%B4)
|
||||
const appId = this.getUniversalClientInfo().appId
|
||||
const qqPlatform = getQQPlatform.call(this)
|
||||
const keyObj = {
|
||||
dcloudAppid: appId,
|
||||
openid,
|
||||
platform: 'qq-' + qqPlatform
|
||||
}
|
||||
switch (qqPlatform) {
|
||||
case 'mp':
|
||||
await this.uniOpenBridge.setSessionKey(keyObj, {
|
||||
session_key: sessionKey
|
||||
}, 30 * 24 * 60 * 60)
|
||||
break
|
||||
case 'app':
|
||||
case 'h5':
|
||||
case 'web':
|
||||
await this.uniOpenBridge.setUserAccessToken(keyObj, {
|
||||
access_token: accessToken,
|
||||
access_token_expired: accessTokenExpired
|
||||
}, accessTokenExpired
|
||||
? Math.floor((accessTokenExpired - Date.now()) / 1000)
|
||||
: 30 * 24 * 60 * 60
|
||||
)
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
function generateQQCache ({
|
||||
sessionKey, // QQ小程序用户sessionKey
|
||||
accessToken, // App端QQ用户accessToken
|
||||
accessTokenExpired // App端QQ用户accessToken过期时间
|
||||
} = {}) {
|
||||
const platform = getQQPlatform.call(this)
|
||||
let cache
|
||||
switch (platform) {
|
||||
case 'app':
|
||||
cache = {
|
||||
access_token: accessToken,
|
||||
access_token_expired: accessTokenExpired
|
||||
}
|
||||
break
|
||||
case 'mp':
|
||||
cache = {
|
||||
session_key: sessionKey
|
||||
}
|
||||
break
|
||||
default:
|
||||
throw new Error('Unsupported qq platform')
|
||||
}
|
||||
return {
|
||||
third_party: {
|
||||
[`${platform}_qq`]: cache
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getQQOpenid ({
|
||||
userRecord
|
||||
} = {}) {
|
||||
const qqPlatform = getQQPlatform.call(this)
|
||||
const appId = this.getUniversalClientInfo().appId
|
||||
const qqOpenidObj = userRecord.qq_openid
|
||||
if (!qqOpenidObj) {
|
||||
return
|
||||
}
|
||||
return qqOpenidObj[`${qqPlatform}_${appId}`] || qqOpenidObj[qqPlatform]
|
||||
}
|
||||
|
||||
async function getQQCacheFallback ({
|
||||
userRecord,
|
||||
key
|
||||
} = {}) {
|
||||
const platform = getQQPlatform.call(this)
|
||||
const thirdParty = userRecord && userRecord.third_party
|
||||
if (!thirdParty) {
|
||||
return
|
||||
}
|
||||
const qqCache = thirdParty[`${platform}_qq`]
|
||||
return qqCache && qqCache[key]
|
||||
}
|
||||
|
||||
async function getQQCache ({
|
||||
uid,
|
||||
userRecord,
|
||||
key
|
||||
} = {}) {
|
||||
const qqPlatform = getQQPlatform.call(this)
|
||||
const appId = this.getUniversalClientInfo().appId
|
||||
|
||||
if (!userRecord) {
|
||||
const getUserRes = await userCollection.doc(uid).get()
|
||||
userRecord = getUserRes.data[0]
|
||||
}
|
||||
if (!userRecord) {
|
||||
throw {
|
||||
errCode: ERROR.ACCOUNT_NOT_EXISTS
|
||||
}
|
||||
}
|
||||
const openid = getQQOpenid.call(this, {
|
||||
userRecord
|
||||
})
|
||||
const getCacheMethod = qqPlatform === 'mp' ? 'getSessionKey' : 'getUserAccessToken'
|
||||
const userKey = await this.uniOpenBridge[getCacheMethod]({
|
||||
dcloudAppid: appId,
|
||||
platform: 'qq-' + qqPlatform,
|
||||
openid
|
||||
})
|
||||
if (userKey) {
|
||||
return userKey[key]
|
||||
}
|
||||
return getQQCacheFallback({
|
||||
userRecord,
|
||||
key
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getQQPlatform,
|
||||
generateQQCache,
|
||||
getQQCache,
|
||||
saveQQUserKey
|
||||
}
|
@ -0,0 +1,231 @@
|
||||
const {
|
||||
userCollection,
|
||||
LOG_TYPE
|
||||
} = require('../../common/constants')
|
||||
const {
|
||||
ERROR
|
||||
} = require('../../common/error')
|
||||
const {
|
||||
findUser
|
||||
} = require('./account')
|
||||
const {
|
||||
getValidInviteCode,
|
||||
generateInviteInfo
|
||||
} = require('./fission')
|
||||
const {
|
||||
logout
|
||||
} = require('./logout')
|
||||
const PasswordUtils = require('./password')
|
||||
const {
|
||||
merge
|
||||
} = require('../npm/index')
|
||||
|
||||
async function realPreRegister (params = {}) {
|
||||
const {
|
||||
user
|
||||
} = params
|
||||
const {
|
||||
userMatched
|
||||
} = await findUser({
|
||||
userQuery: user,
|
||||
authorizedApp: this.getUniversalClientInfo().appId
|
||||
})
|
||||
if (userMatched.length > 0) {
|
||||
throw {
|
||||
errCode: ERROR.ACCOUNT_EXISTS
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function preRegister (params = {}) {
|
||||
try {
|
||||
await realPreRegister.call(this, params)
|
||||
} catch (error) {
|
||||
await this.middleware.uniIdLog({
|
||||
success: false,
|
||||
type: LOG_TYPE.REGISTER
|
||||
})
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
async function preRegisterWithPassword (params = {}) {
|
||||
const {
|
||||
user,
|
||||
password
|
||||
} = params
|
||||
await preRegister.call(this, {
|
||||
user
|
||||
})
|
||||
const passwordUtils = new PasswordUtils({
|
||||
clientInfo: this.getUniversalClientInfo(),
|
||||
passwordSecret: this.config.passwordSecret
|
||||
})
|
||||
const {
|
||||
passwordHash,
|
||||
version
|
||||
} = passwordUtils.generatePasswordHash({
|
||||
password
|
||||
})
|
||||
const extraData = {
|
||||
password: passwordHash,
|
||||
password_secret_version: version
|
||||
}
|
||||
return {
|
||||
user,
|
||||
extraData
|
||||
}
|
||||
}
|
||||
|
||||
async function thirdPartyRegister ({
|
||||
user = {}
|
||||
} = {}) {
|
||||
return {
|
||||
mobileConfirmed: !!(user.mobile && user.mobile_confirmed) || false,
|
||||
emailConfirmed: !!(user.email && user.email_confirmed) || false
|
||||
}
|
||||
}
|
||||
|
||||
async function postRegister (params = {}) {
|
||||
const {
|
||||
user,
|
||||
extraData = {},
|
||||
isThirdParty = false,
|
||||
inviteCode
|
||||
} = params
|
||||
const {
|
||||
appId,
|
||||
appName,
|
||||
appVersion,
|
||||
appVersionCode,
|
||||
channel,
|
||||
scene,
|
||||
clientIP,
|
||||
osName
|
||||
} = this.getUniversalClientInfo()
|
||||
const uniIdToken = this.getUniversalUniIdToken()
|
||||
|
||||
merge(user, extraData)
|
||||
|
||||
const registerChannel = channel || scene
|
||||
user.register_env = {
|
||||
appid: appId || '',
|
||||
uni_platform: this.clientPlatform || '',
|
||||
os_name: osName || '',
|
||||
app_name: appName || '',
|
||||
app_version: appVersion || '',
|
||||
app_version_code: appVersionCode || '',
|
||||
channel: registerChannel ? registerChannel + '' : '', // channel可能为数字,统一存为字符串
|
||||
client_ip: clientIP || ''
|
||||
}
|
||||
|
||||
user.register_date = Date.now()
|
||||
user.dcloud_appid = [appId]
|
||||
|
||||
if (user.username) {
|
||||
user.username = user.username.toLowerCase()
|
||||
}
|
||||
if (user.email) {
|
||||
user.email = user.email.toLowerCase()
|
||||
}
|
||||
|
||||
const {
|
||||
autoSetInviteCode, // 注册时自动设置邀请码
|
||||
forceInviteCode, // 必须有邀请码才允许注册,注意此逻辑不可对admin生效
|
||||
userRegisterDefaultRole // 用户注册时配置的默认角色
|
||||
} = this.config
|
||||
if (autoSetInviteCode) {
|
||||
user.my_invite_code = await getValidInviteCode()
|
||||
}
|
||||
|
||||
// 如果用户注册默认角色配置存在且不为空数组
|
||||
if (userRegisterDefaultRole && userRegisterDefaultRole.length) {
|
||||
// 将用户已有的角色和配置的默认角色合并成一个数组,并去重
|
||||
user.role = Array.from(new Set([...(user.role || []), ...userRegisterDefaultRole]))
|
||||
}
|
||||
|
||||
const isAdmin = user.role && user.role.includes('admin')
|
||||
|
||||
if (forceInviteCode && !isAdmin && !inviteCode) {
|
||||
throw {
|
||||
errCode: ERROR.INVALID_INVITE_CODE
|
||||
}
|
||||
}
|
||||
|
||||
if (inviteCode) {
|
||||
const {
|
||||
inviterUid,
|
||||
inviteTime
|
||||
} = await generateInviteInfo({
|
||||
inviteCode
|
||||
})
|
||||
user.inviter_uid = inviterUid
|
||||
user.invite_time = inviteTime
|
||||
}
|
||||
|
||||
if (uniIdToken) {
|
||||
try {
|
||||
await logout.call(this)
|
||||
} catch (error) { }
|
||||
}
|
||||
|
||||
const beforeRegister = this.hooks.beforeRegister
|
||||
let userRecord = user
|
||||
if (beforeRegister) {
|
||||
userRecord = await beforeRegister({
|
||||
userRecord,
|
||||
clientInfo: this.getUniversalClientInfo()
|
||||
})
|
||||
}
|
||||
|
||||
const {
|
||||
id: uid
|
||||
} = await userCollection.add(userRecord)
|
||||
|
||||
const createTokenRes = await this.uniIdCommon.createToken({
|
||||
uid
|
||||
})
|
||||
|
||||
const {
|
||||
errCode,
|
||||
token,
|
||||
tokenExpired
|
||||
} = createTokenRes
|
||||
|
||||
if (errCode) {
|
||||
throw createTokenRes
|
||||
}
|
||||
|
||||
await this.middleware.uniIdLog({
|
||||
data: {
|
||||
user_id: uid
|
||||
},
|
||||
type: LOG_TYPE.REGISTER
|
||||
})
|
||||
|
||||
return {
|
||||
errCode: 0,
|
||||
uid,
|
||||
newToken: {
|
||||
token,
|
||||
tokenExpired
|
||||
},
|
||||
...(
|
||||
isThirdParty
|
||||
? thirdPartyRegister({
|
||||
user: {
|
||||
...userRecord,
|
||||
_id: uid
|
||||
}
|
||||
})
|
||||
: {}
|
||||
),
|
||||
passwordConfirmed: !!userRecord.password
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
preRegister,
|
||||
preRegisterWithPassword,
|
||||
postRegister
|
||||
}
|
@ -0,0 +1,166 @@
|
||||
const {
|
||||
findUser
|
||||
} = require('./account')
|
||||
const {
|
||||
ERROR
|
||||
} = require('../../common/error')
|
||||
const {
|
||||
userCollection, dbCmd, USER_IDENTIFIER
|
||||
} = require('../../common/constants')
|
||||
const {
|
||||
getUserIdentifier
|
||||
} = require('../../lib/utils/account')
|
||||
|
||||
const {
|
||||
batchFindObjctValue
|
||||
} = require('../../common/utils')
|
||||
const {
|
||||
merge
|
||||
} = require('../npm/index')
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {object} param
|
||||
* @param {string} param.uid 用户id
|
||||
* @param {string} param.bindAccount 要绑定的三方账户、手机号或邮箱
|
||||
*/
|
||||
async function preBind ({
|
||||
uid,
|
||||
bindAccount,
|
||||
logType
|
||||
} = {}) {
|
||||
const {
|
||||
userMatched
|
||||
} = await findUser({
|
||||
userQuery: bindAccount,
|
||||
authorizedApp: this.getUniversalClientInfo().appId
|
||||
})
|
||||
if (userMatched.length > 0) {
|
||||
await this.middleware.uniIdLog({
|
||||
data: {
|
||||
user_id: uid
|
||||
},
|
||||
type: logType,
|
||||
success: false
|
||||
})
|
||||
throw {
|
||||
errCode: ERROR.BIND_CONFLICT
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function postBind ({
|
||||
uid,
|
||||
extraData = {},
|
||||
bindAccount,
|
||||
logType
|
||||
} = {}) {
|
||||
await userCollection.doc(uid).update(merge(bindAccount, extraData))
|
||||
await this.middleware.uniIdLog({
|
||||
data: {
|
||||
user_id: uid
|
||||
},
|
||||
type: logType
|
||||
})
|
||||
return {
|
||||
errCode: 0
|
||||
}
|
||||
}
|
||||
|
||||
async function preUnBind ({
|
||||
uid,
|
||||
unBindAccount,
|
||||
logType
|
||||
}) {
|
||||
const notUnBind = ['username', 'mobile', 'email']
|
||||
const userIdentifier = getUserIdentifier(unBindAccount)
|
||||
const condition = Object.keys(userIdentifier).reduce((res, key) => {
|
||||
if (userIdentifier[key]) {
|
||||
if (notUnBind.includes(key)) {
|
||||
throw {
|
||||
errCode: ERROR.UNBIND_NOT_SUPPORTED
|
||||
}
|
||||
}
|
||||
|
||||
res.push({
|
||||
[key]: userIdentifier[key]
|
||||
})
|
||||
}
|
||||
|
||||
return res
|
||||
}, [])
|
||||
const currentUnBindAccount = Object.keys(userIdentifier).reduce((res, key) => {
|
||||
if (userIdentifier[key]) {
|
||||
res.push(key)
|
||||
}
|
||||
return res
|
||||
}, [])
|
||||
const { data: users } = await userCollection.where(dbCmd.and(
|
||||
{ _id: uid },
|
||||
dbCmd.or(condition)
|
||||
)).get()
|
||||
|
||||
if (users.length <= 0) {
|
||||
await this.middleware.uniIdLog({
|
||||
data: {
|
||||
user_id: uid
|
||||
},
|
||||
type: logType,
|
||||
success: false
|
||||
})
|
||||
throw {
|
||||
errCode: ERROR.UNBIND_FAIL
|
||||
}
|
||||
}
|
||||
|
||||
const [user] = users
|
||||
const otherAccounts = batchFindObjctValue(user, Object.keys(USER_IDENTIFIER).filter(key => !notUnBind.includes(key) && !currentUnBindAccount.includes(key)))
|
||||
let hasOtherAccountBind = false
|
||||
|
||||
for (const key in otherAccounts) {
|
||||
if (otherAccounts[key]) {
|
||||
hasOtherAccountBind = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有其他第三方登录方式
|
||||
if (!hasOtherAccountBind) {
|
||||
// 存在用户名或者邮箱但是没有设置过没密码就提示设置密码
|
||||
if ((user.username || user.email) && !user.password) {
|
||||
throw {
|
||||
errCode: ERROR.UNBIND_PASSWORD_NOT_EXISTS
|
||||
}
|
||||
}
|
||||
// 账号任何登录方式都没有就优先绑定手机号
|
||||
if (!user.mobile) {
|
||||
throw {
|
||||
errCode: ERROR.UNBIND_MOBILE_NOT_EXISTS
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function postUnBind ({
|
||||
uid,
|
||||
unBindAccount,
|
||||
logType
|
||||
}) {
|
||||
await userCollection.doc(uid).update(unBindAccount)
|
||||
await this.middleware.uniIdLog({
|
||||
data: {
|
||||
user_id: uid
|
||||
},
|
||||
type: logType
|
||||
})
|
||||
return {
|
||||
errCode: 0
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
preBind,
|
||||
postBind,
|
||||
preUnBind,
|
||||
postUnBind
|
||||
}
|
@ -0,0 +1,79 @@
|
||||
const {
|
||||
setMobileVerifyCode
|
||||
} = require('./verify-code')
|
||||
const {
|
||||
getVerifyCode
|
||||
} = require('../../common/utils')
|
||||
|
||||
/**
|
||||
* 发送短信
|
||||
* @param {object} param
|
||||
* @param {string} param.mobile 手机号
|
||||
* @param {object} param.code 可选,验证码
|
||||
* @param {object} param.scene 短信场景
|
||||
* @param {object} param.templateId 可选,短信模板id
|
||||
* @returns
|
||||
*/
|
||||
async function sendSmsCode ({
|
||||
mobile,
|
||||
code,
|
||||
scene,
|
||||
templateId
|
||||
} = {}) {
|
||||
const requiredParams = [
|
||||
'name',
|
||||
'codeExpiresIn'
|
||||
]
|
||||
const smsConfig = (this.config.service && this.config.service.sms) || {}
|
||||
for (let i = 0; i < requiredParams.length; i++) {
|
||||
const key = requiredParams[i]
|
||||
if (!smsConfig[key]) {
|
||||
throw new Error(`Missing config param: service.sms.${key}`)
|
||||
}
|
||||
}
|
||||
if (!code) {
|
||||
code = getVerifyCode()
|
||||
}
|
||||
let action
|
||||
switch (scene) {
|
||||
case 'login-by-sms':
|
||||
action = this.t('login')
|
||||
break
|
||||
default:
|
||||
action = this.t('verify-mobile')
|
||||
break
|
||||
}
|
||||
const sceneConfig = (smsConfig.scene || {})[scene] || {}
|
||||
if (!templateId) {
|
||||
templateId = sceneConfig.templateId
|
||||
}
|
||||
if (!templateId) {
|
||||
throw new Error('"templateId" is required')
|
||||
}
|
||||
const codeExpiresIn = sceneConfig.codeExpiresIn || smsConfig.codeExpiresIn
|
||||
await setMobileVerifyCode.call(this, {
|
||||
mobile,
|
||||
code,
|
||||
expiresIn: codeExpiresIn,
|
||||
scene
|
||||
})
|
||||
await uniCloud.sendSms({
|
||||
smsKey: smsConfig.smsKey,
|
||||
smsSecret: smsConfig.smsSecret,
|
||||
phone: mobile,
|
||||
templateId,
|
||||
data: {
|
||||
name: smsConfig.name,
|
||||
code,
|
||||
action,
|
||||
expMinute: '' + Math.round(codeExpiresIn / 60)
|
||||
}
|
||||
})
|
||||
return {
|
||||
errCode: 0
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
sendSmsCode
|
||||
}
|
@ -0,0 +1,106 @@
|
||||
const {
|
||||
checkLoginUserRecord,
|
||||
postLogin
|
||||
} = require('./login')
|
||||
const {
|
||||
postRegister
|
||||
} = require('./register')
|
||||
const {
|
||||
findUser
|
||||
} = require('./account')
|
||||
const {
|
||||
ERROR
|
||||
} = require('../../common/error')
|
||||
|
||||
async function realPreUnifiedLogin (params = {}) {
|
||||
const {
|
||||
user,
|
||||
type
|
||||
} = params
|
||||
const appId = this.getUniversalClientInfo().appId
|
||||
const {
|
||||
total,
|
||||
userMatched
|
||||
} = await findUser({
|
||||
userQuery: user,
|
||||
authorizedApp: appId
|
||||
})
|
||||
if (userMatched.length === 0) {
|
||||
if (type === 'login') {
|
||||
if (total > 0) {
|
||||
throw {
|
||||
errCode: ERROR.ACCOUNT_NOT_EXISTS_IN_CURRENT_APP
|
||||
}
|
||||
}
|
||||
throw {
|
||||
errCode: ERROR.ACCOUNT_NOT_EXISTS
|
||||
}
|
||||
}
|
||||
return {
|
||||
type: 'register',
|
||||
user
|
||||
}
|
||||
} if (userMatched.length === 1) {
|
||||
if (type === 'register') {
|
||||
throw {
|
||||
errCode: ERROR.ACCOUNT_EXISTS
|
||||
}
|
||||
}
|
||||
const userRecord = userMatched[0]
|
||||
checkLoginUserRecord(userRecord)
|
||||
return {
|
||||
type: 'login',
|
||||
user: userRecord
|
||||
}
|
||||
} else if (userMatched.length > 1) {
|
||||
throw {
|
||||
errCode: ERROR.ACCOUNT_CONFLICT
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function preUnifiedLogin (params = {}) {
|
||||
try {
|
||||
const result = await realPreUnifiedLogin.call(this, params)
|
||||
return result
|
||||
} catch (error) {
|
||||
await this.middleware.uniIdLog({
|
||||
success: false
|
||||
})
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
async function postUnifiedLogin (params = {}) {
|
||||
const {
|
||||
user,
|
||||
extraData = {},
|
||||
isThirdParty = false,
|
||||
type,
|
||||
inviteCode
|
||||
} = params
|
||||
let result
|
||||
if (type === 'login') {
|
||||
result = await postLogin.call(this, {
|
||||
user,
|
||||
extraData,
|
||||
isThirdParty
|
||||
})
|
||||
} else if (type === 'register') {
|
||||
result = await postRegister.call(this, {
|
||||
user,
|
||||
extraData,
|
||||
isThirdParty,
|
||||
inviteCode
|
||||
})
|
||||
}
|
||||
return {
|
||||
...result,
|
||||
type
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
preUnifiedLogin,
|
||||
postUnifiedLogin
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
async function getPhoneNumber ({
|
||||
// eslint-disable-next-line camelcase
|
||||
access_token,
|
||||
openid
|
||||
} = {}) {
|
||||
const requiredParams = []
|
||||
const univerifyConfig = (this.config.service && this.config.service.univerify) || {}
|
||||
for (let i = 0; i < requiredParams.length; i++) {
|
||||
const key = requiredParams[i]
|
||||
if (!univerifyConfig[key]) {
|
||||
throw new Error(`Missing config param: service.univerify.${key}`)
|
||||
}
|
||||
}
|
||||
return uniCloud.getPhoneNumber({
|
||||
provider: 'univerify',
|
||||
appid: this.getUniversalClientInfo().appId,
|
||||
apiKey: univerifyConfig.apiKey,
|
||||
apiSecret: univerifyConfig.apiSecret,
|
||||
// eslint-disable-next-line camelcase
|
||||
access_token,
|
||||
openid
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getPhoneNumber
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
const {
|
||||
userCollection
|
||||
} = require('../../common/constants')
|
||||
const {
|
||||
USER_STATUS
|
||||
} = require('../../common/constants')
|
||||
async function setUserStatus (uid, status) {
|
||||
const updateData = {
|
||||
status
|
||||
}
|
||||
if (status !== USER_STATUS.NORMAL) {
|
||||
updateData.valid_token_date = Date.now()
|
||||
}
|
||||
await userCollection.doc(uid).update({
|
||||
status
|
||||
})
|
||||
// TODO 此接口尚不完善,例如注销后其他客户端可能存在有效token,支持Redis后此处会补充额外逻辑
|
||||
return {
|
||||
errCode: 0
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
setUserStatus
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
let redisEnable = null
|
||||
function getRedisEnable() {
|
||||
// 未用到的时候不调用redis接口,节省一些连接数
|
||||
if (redisEnable !== null) {
|
||||
return redisEnable
|
||||
}
|
||||
try {
|
||||
uniCloud.redis()
|
||||
redisEnable = true
|
||||
} catch (error) {
|
||||
redisEnable = false
|
||||
}
|
||||
return redisEnable
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getRedisEnable
|
||||
}
|
@ -0,0 +1,152 @@
|
||||
const {
|
||||
dbCmd,
|
||||
verifyCollection
|
||||
} = require('../../common/constants')
|
||||
const {
|
||||
ERROR
|
||||
} = require('../../common/error')
|
||||
const {
|
||||
getVerifyCode
|
||||
} = require('../../common/utils')
|
||||
|
||||
async function setVerifyCode ({
|
||||
mobile,
|
||||
email,
|
||||
code,
|
||||
expiresIn,
|
||||
scene
|
||||
} = {}) {
|
||||
const now = Date.now()
|
||||
const record = {
|
||||
mobile,
|
||||
email,
|
||||
scene,
|
||||
code: code || getVerifyCode(),
|
||||
state: 0,
|
||||
ip: this.getUniversalClientInfo().clientIP,
|
||||
created_date: now,
|
||||
expired_date: now + expiresIn * 1000
|
||||
}
|
||||
await verifyCollection.add(record)
|
||||
return {
|
||||
errCode: 0
|
||||
}
|
||||
}
|
||||
|
||||
async function setEmailVerifyCode ({
|
||||
email,
|
||||
code,
|
||||
expiresIn,
|
||||
scene
|
||||
} = {}) {
|
||||
email = email && email.trim()
|
||||
if (!email) {
|
||||
throw {
|
||||
errCode: ERROR.INVALID_EMAIL
|
||||
}
|
||||
}
|
||||
email = email.toLowerCase()
|
||||
return setVerifyCode.call(this, {
|
||||
email,
|
||||
code,
|
||||
expiresIn,
|
||||
scene
|
||||
})
|
||||
}
|
||||
|
||||
async function setMobileVerifyCode ({
|
||||
mobile,
|
||||
code,
|
||||
expiresIn,
|
||||
scene
|
||||
} = {}) {
|
||||
mobile = mobile && mobile.trim()
|
||||
if (!mobile) {
|
||||
throw {
|
||||
errCode: ERROR.INVALID_MOBILE
|
||||
}
|
||||
}
|
||||
return setVerifyCode.call(this, {
|
||||
mobile,
|
||||
code,
|
||||
expiresIn,
|
||||
scene
|
||||
})
|
||||
}
|
||||
|
||||
async function verifyEmailCode ({
|
||||
email,
|
||||
code,
|
||||
scene
|
||||
} = {}) {
|
||||
email = email && email.trim()
|
||||
if (!email) {
|
||||
throw {
|
||||
errCode: ERROR.INVALID_EMAIL
|
||||
}
|
||||
}
|
||||
email = email.toLowerCase()
|
||||
const {
|
||||
data: codeRecord
|
||||
} = await verifyCollection.where({
|
||||
email,
|
||||
scene,
|
||||
code,
|
||||
state: 0,
|
||||
expired_date: dbCmd.gt(Date.now())
|
||||
}).limit(1).get()
|
||||
|
||||
if (codeRecord.length === 0) {
|
||||
throw {
|
||||
errCode: ERROR.EMAIL_VERIFY_CODE_ERROR
|
||||
}
|
||||
}
|
||||
await verifyCollection.doc(codeRecord[0]._id).update({
|
||||
state: 1
|
||||
})
|
||||
return {
|
||||
errCode: 0
|
||||
}
|
||||
}
|
||||
|
||||
async function verifyMobileCode ({
|
||||
mobile,
|
||||
code,
|
||||
scene
|
||||
} = {}) {
|
||||
mobile = mobile && mobile.trim()
|
||||
if (!mobile) {
|
||||
throw {
|
||||
errCode: ERROR.INVALID_MOBILE
|
||||
}
|
||||
}
|
||||
const {
|
||||
data: codeRecord
|
||||
} = await verifyCollection.where({
|
||||
mobile,
|
||||
scene,
|
||||
code,
|
||||
state: 0,
|
||||
expired_date: dbCmd.gt(Date.now())
|
||||
}).limit(1).get()
|
||||
|
||||
if (codeRecord.length === 0) {
|
||||
throw {
|
||||
errCode: ERROR.MOBILE_VERIFY_CODE_ERROR
|
||||
}
|
||||
}
|
||||
|
||||
await verifyCollection.doc(codeRecord[0]._id).update({
|
||||
state: 1
|
||||
})
|
||||
return {
|
||||
errCode: 0
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
verifyEmailCode,
|
||||
verifyMobileCode,
|
||||
setEmailVerifyCode,
|
||||
setMobileVerifyCode
|
||||
}
|
@ -0,0 +1,236 @@
|
||||
const crypto = require('crypto')
|
||||
const {
|
||||
userCollection
|
||||
} = require('../../common/constants')
|
||||
const {
|
||||
ERROR
|
||||
} = require('../../common/error')
|
||||
const {
|
||||
getRedisEnable
|
||||
} = require('./utils')
|
||||
const {
|
||||
openDataCollection
|
||||
} = require('../../common/constants')
|
||||
|
||||
function decryptWeixinData ({
|
||||
encryptedData,
|
||||
sessionKey,
|
||||
iv
|
||||
} = {}) {
|
||||
const oauthConfig = this.configUtils.getOauthConfig({
|
||||
provider: 'weixin'
|
||||
})
|
||||
const decipher = crypto.createDecipheriv(
|
||||
'aes-128-cbc',
|
||||
Buffer.from(sessionKey, 'base64'),
|
||||
Buffer.from(iv, 'base64')
|
||||
)
|
||||
// 设置自动 padding 为 true,删除填充补位
|
||||
decipher.setAutoPadding(true)
|
||||
let decoded
|
||||
decoded = decipher.update(encryptedData, 'base64', 'utf8')
|
||||
decoded += decipher.final('utf8')
|
||||
decoded = JSON.parse(decoded)
|
||||
if (decoded.watermark.appid !== oauthConfig.appid) {
|
||||
throw new Error('Invalid wechat appid in decode content')
|
||||
}
|
||||
return decoded
|
||||
}
|
||||
|
||||
function getWeixinPlatform () {
|
||||
const platform = this.clientPlatform
|
||||
const userAgent = this.getUniversalClientInfo().userAgent
|
||||
switch (platform) {
|
||||
case 'app':
|
||||
case 'app-plus':
|
||||
case 'app-android':
|
||||
case 'app-ios':
|
||||
return 'app'
|
||||
case 'mp-weixin':
|
||||
return 'mp'
|
||||
case 'h5':
|
||||
case 'web':
|
||||
return userAgent.indexOf('MicroMessenger') > -1 ? 'h5' : 'web'
|
||||
default:
|
||||
throw new Error('Unsupported weixin platform')
|
||||
}
|
||||
}
|
||||
|
||||
async function saveWeixinUserKey ({
|
||||
openid,
|
||||
sessionKey, // 微信小程序用户sessionKey
|
||||
accessToken, // App端微信用户accessToken
|
||||
refreshToken, // App端微信用户refreshToken
|
||||
accessTokenExpired // App端微信用户accessToken过期时间
|
||||
} = {}) {
|
||||
// 微信公众平台、开放平台refreshToken有效期均为30天(微信没有在网络请求里面返回30天这个值,务必注意未来可能出现调整,需及时更新此处逻辑)。
|
||||
// 此前QQ开放平台有调整过accessToken的过期时间:[access_token有效期由90天缩短至30天](https://wiki.connect.qq.com/%E3%80%90qq%E4%BA%92%E8%81%94%E3%80%91access_token%E6%9C%89%E6%95%88%E6%9C%9F%E8%B0%83%E6%95%B4)
|
||||
|
||||
const appId = this.getUniversalClientInfo().appId
|
||||
const weixinPlatform = getWeixinPlatform.call(this)
|
||||
const keyObj = {
|
||||
dcloudAppid: appId,
|
||||
openid,
|
||||
platform: 'weixin-' + weixinPlatform
|
||||
}
|
||||
switch (weixinPlatform) {
|
||||
case 'mp':
|
||||
await this.uniOpenBridge.setSessionKey(keyObj, {
|
||||
session_key: sessionKey
|
||||
}, 30 * 24 * 60 * 60)
|
||||
break
|
||||
case 'app':
|
||||
case 'h5':
|
||||
case 'web':
|
||||
await this.uniOpenBridge.setUserAccessToken(keyObj, {
|
||||
access_token: accessToken,
|
||||
refresh_token: refreshToken,
|
||||
access_token_expired: accessTokenExpired
|
||||
}, 30 * 24 * 60 * 60)
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
async function saveSecureNetworkCache ({
|
||||
code,
|
||||
openid,
|
||||
unionid,
|
||||
sessionKey
|
||||
}) {
|
||||
const {
|
||||
appId
|
||||
} = this.getUniversalClientInfo()
|
||||
const key = `uni-id:${appId}:weixin-mp:code:${code}:secure-network-cache`
|
||||
const value = JSON.stringify({
|
||||
openid,
|
||||
unionid,
|
||||
session_key: sessionKey
|
||||
})
|
||||
// 此处存储的是code的缓存,设置有效期和token一致
|
||||
const expiredSeconds = this.config.tokenExpiresIn || 3 * 24 * 60 * 60
|
||||
|
||||
await openDataCollection.doc(key).set({
|
||||
value,
|
||||
expired: Date.now() + expiredSeconds * 1000
|
||||
})
|
||||
const isRedisEnable = getRedisEnable()
|
||||
if (isRedisEnable) {
|
||||
const redis = uniCloud.redis()
|
||||
await redis.set(key, value, 'EX', expiredSeconds)
|
||||
}
|
||||
}
|
||||
|
||||
function generateWeixinCache ({
|
||||
sessionKey, // 微信小程序用户sessionKey
|
||||
accessToken, // App端微信用户accessToken
|
||||
refreshToken, // App端微信用户refreshToken
|
||||
accessTokenExpired // App端微信用户accessToken过期时间
|
||||
} = {}) {
|
||||
const platform = getWeixinPlatform.call(this)
|
||||
let cache
|
||||
switch (platform) {
|
||||
case 'app':
|
||||
case 'h5':
|
||||
case 'web':
|
||||
cache = {
|
||||
access_token: accessToken,
|
||||
refresh_token: refreshToken,
|
||||
access_token_expired: accessTokenExpired
|
||||
}
|
||||
break
|
||||
case 'mp':
|
||||
cache = {
|
||||
session_key: sessionKey
|
||||
}
|
||||
break
|
||||
default:
|
||||
throw new Error('Unsupported weixin platform')
|
||||
}
|
||||
return {
|
||||
third_party: {
|
||||
[`${platform}_weixin`]: cache
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getWeixinOpenid ({
|
||||
userRecord
|
||||
} = {}) {
|
||||
const weixinPlatform = getWeixinPlatform.call(this)
|
||||
const appId = this.getUniversalClientInfo().appId
|
||||
const wxOpenidObj = userRecord.wx_openid
|
||||
if (!wxOpenidObj) {
|
||||
return
|
||||
}
|
||||
return wxOpenidObj[`${weixinPlatform}_${appId}`] || wxOpenidObj[weixinPlatform]
|
||||
}
|
||||
|
||||
async function getWeixinCacheFallback ({
|
||||
userRecord,
|
||||
key
|
||||
} = {}) {
|
||||
const platform = getWeixinPlatform.call(this)
|
||||
const thirdParty = userRecord && userRecord.third_party
|
||||
if (!thirdParty) {
|
||||
return
|
||||
}
|
||||
const weixinCache = thirdParty[`${platform}_weixin`]
|
||||
return weixinCache && weixinCache[key]
|
||||
}
|
||||
|
||||
async function getWeixinCache ({
|
||||
uid,
|
||||
userRecord,
|
||||
key
|
||||
} = {}) {
|
||||
const weixinPlatform = getWeixinPlatform.call(this)
|
||||
const appId = this.getUniversalClientInfo().appId
|
||||
if (!userRecord) {
|
||||
const getUserRes = await userCollection.doc(uid).get()
|
||||
userRecord = getUserRes.data[0]
|
||||
}
|
||||
if (!userRecord) {
|
||||
throw {
|
||||
errCode: ERROR.ACCOUNT_NOT_EXISTS
|
||||
}
|
||||
}
|
||||
const openid = getWeixinOpenid.call(this, {
|
||||
userRecord
|
||||
})
|
||||
const getCacheMethod = weixinPlatform === 'mp' ? 'getSessionKey' : 'getUserAccessToken'
|
||||
const userKey = await this.uniOpenBridge[getCacheMethod]({
|
||||
dcloudAppid: appId,
|
||||
platform: 'weixin-' + weixinPlatform,
|
||||
openid
|
||||
})
|
||||
if (userKey) {
|
||||
return userKey[key]
|
||||
}
|
||||
return getWeixinCacheFallback({
|
||||
userRecord,
|
||||
key
|
||||
})
|
||||
}
|
||||
|
||||
async function getWeixinAccessToken () {
|
||||
const weixinPlatform = getWeixinPlatform.call(this)
|
||||
const appId = this.getUniversalClientInfo().appId
|
||||
|
||||
const cache = await this.uniOpenBridge.getAccessToken({
|
||||
dcloudAppid: appId,
|
||||
platform: 'weixin-' + weixinPlatform
|
||||
})
|
||||
|
||||
return cache.access_token
|
||||
}
|
||||
module.exports = {
|
||||
decryptWeixinData,
|
||||
getWeixinPlatform,
|
||||
generateWeixinCache,
|
||||
getWeixinCache,
|
||||
saveWeixinUserKey,
|
||||
getWeixinAccessToken,
|
||||
saveSecureNetworkCache
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
const methodPermission = require('../config/permission')
|
||||
const {
|
||||
ERROR
|
||||
} = require('../common/error')
|
||||
|
||||
function isAccessAllowed (user, setting) {
|
||||
const {
|
||||
role: userRole = [],
|
||||
permission: userPermission = []
|
||||
} = user
|
||||
const {
|
||||
role: settingRole = [],
|
||||
permission: settingPermission = []
|
||||
} = setting
|
||||
if (userRole.includes('admin')) {
|
||||
return
|
||||
}
|
||||
if (
|
||||
settingRole.length > 0 &&
|
||||
settingRole.every(item => !userRole.includes(item))
|
||||
) {
|
||||
throw {
|
||||
errCode: ERROR.PERMISSION_ERROR
|
||||
}
|
||||
}
|
||||
if (
|
||||
settingPermission.length > 0 &&
|
||||
settingPermission.every(item => !userPermission.includes(item))
|
||||
) {
|
||||
throw {
|
||||
errCode: ERROR.PERMISSION_ERROR
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = async function () {
|
||||
const methodName = this.getMethodName()
|
||||
if (!(methodName in methodPermission)) {
|
||||
return
|
||||
}
|
||||
const {
|
||||
auth,
|
||||
role,
|
||||
permission
|
||||
} = methodPermission[methodName]
|
||||
if (auth || role || permission) {
|
||||
await this.middleware.auth()
|
||||
}
|
||||
if (role && role.length === 0) {
|
||||
throw new Error('[AccessControl]Empty role array is not supported')
|
||||
}
|
||||
if (permission && permission.length === 0) {
|
||||
throw new Error('[AccessControl]Empty permission array is not supported')
|
||||
}
|
||||
return isAccessAllowed(this.authInfo, {
|
||||
role,
|
||||
permission
|
||||
})
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
module.exports = async function () {
|
||||
if (this.authInfo) { // 多次执行auth时如果第一次成功后续不再执行
|
||||
return
|
||||
}
|
||||
const token = this.getUniversalUniIdToken()
|
||||
const payload = await this.uniIdCommon.checkToken(token)
|
||||
if (payload.errCode) {
|
||||
throw payload
|
||||
}
|
||||
this.authInfo = payload
|
||||
if (payload.token) {
|
||||
this.response.newToken = {
|
||||
token: payload.token,
|
||||
tokenExpired: payload.tokenExpired
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
module.exports = {
|
||||
auth: require('./auth'),
|
||||
uniIdLog: require('./uni-id-log'),
|
||||
validate: require('./validate'),
|
||||
accessControl: require('./access-control'),
|
||||
verifyRequestSign: require('./verify-request-sign'),
|
||||
...require('./rbac')
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
const {
|
||||
ERROR
|
||||
} = require('../common/error')
|
||||
|
||||
function hasRole (...roleList) {
|
||||
const userRole = this.authInfo.role || []
|
||||
if (userRole.includes('admin')) {
|
||||
return
|
||||
}
|
||||
const isMatch = roleList.every(roleItem => {
|
||||
return userRole.includes(roleItem)
|
||||
})
|
||||
if (!isMatch) {
|
||||
throw {
|
||||
errCode: ERROR.PERMISSION_ERROR
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function hasPermission (...permissionList) {
|
||||
const userRole = this.authInfo.role || []
|
||||
const userPermission = this.authInfo.permission || []
|
||||
if (userRole.includes('admin')) {
|
||||
return
|
||||
}
|
||||
const isMatch = permissionList.every(permissionItem => {
|
||||
return userPermission.includes(permissionItem)
|
||||
})
|
||||
if (!isMatch) {
|
||||
throw {
|
||||
errCode: ERROR.PERMISSION_ERROR
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
hasRole,
|
||||
hasPermission
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
const db = uniCloud.database()
|
||||
module.exports = async function ({
|
||||
data = {},
|
||||
success = true,
|
||||
type = 'login'
|
||||
} = {}) {
|
||||
const now = Date.now()
|
||||
const uniIdLogCollection = db.collection('uni-id-log')
|
||||
const requiredDataKeyList = ['user_id', 'username', 'email', 'mobile']
|
||||
const dataCopy = {}
|
||||
for (let i = 0; i < requiredDataKeyList.length; i++) {
|
||||
const key = requiredDataKeyList[i]
|
||||
if (key in data && typeof data[key] === 'string') {
|
||||
dataCopy[key] = data[key]
|
||||
}
|
||||
}
|
||||
const {
|
||||
appId,
|
||||
clientIP,
|
||||
deviceId,
|
||||
userAgent
|
||||
} = this.getUniversalClientInfo()
|
||||
const logData = {
|
||||
appid: appId,
|
||||
device_id: deviceId,
|
||||
ip: clientIP,
|
||||
type,
|
||||
ua: userAgent,
|
||||
create_date: now,
|
||||
...dataCopy
|
||||
}
|
||||
|
||||
if (success) {
|
||||
logData.state = 1
|
||||
} else {
|
||||
logData.state = 0
|
||||
}
|
||||
return uniIdLogCollection.add(logData)
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
module.exports = function (value = {}, schema = {}) {
|
||||
const validateRes = this.validator.validate(value, schema)
|
||||
if (validateRes) {
|
||||
delete validateRes.schemaKey
|
||||
throw validateRes
|
||||
}
|
||||
}
|
@ -0,0 +1,85 @@
|
||||
const crypto = require('crypto')
|
||||
const createConfig = require('uni-config-center')
|
||||
const { verifyHttpInfo } = require('uni-cloud-s2s')
|
||||
|
||||
const { ERROR } = require('../common/error')
|
||||
const s2sConfig = createConfig({
|
||||
pluginId: 'uni-cloud-s2s'
|
||||
})
|
||||
const needSignFunctions = new Set([
|
||||
'externalRegister',
|
||||
'externalLogin',
|
||||
'updateUserInfoByExternal'
|
||||
])
|
||||
|
||||
module.exports = function () {
|
||||
const methodName = this.getMethodName()
|
||||
const { source } = this.getUniversalClientInfo()
|
||||
|
||||
// 指定接口需要鉴权
|
||||
if (!needSignFunctions.has(methodName)) return
|
||||
|
||||
// 非 HTTP 方式请求拒绝访问
|
||||
if (source !== 'http') {
|
||||
throw {
|
||||
errCode: ERROR.ILLEGAL_REQUEST
|
||||
}
|
||||
}
|
||||
|
||||
// 支持 uni-cloud-s2s 验证请求
|
||||
if (s2sConfig.hasFile('config.json')) {
|
||||
try {
|
||||
if (!verifyHttpInfo(this.getHttpInfo())) {
|
||||
throw {
|
||||
errCode: ERROR.ILLEGAL_REQUEST
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
if (e.errSubject === 'uni-cloud-s2s') {
|
||||
throw {
|
||||
errCode: ERROR.ILLEGAL_REQUEST,
|
||||
errMsg: e.errMsg
|
||||
}
|
||||
}
|
||||
throw e
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (!this.config.requestAuthSecret || typeof this.config.requestAuthSecret !== 'string') {
|
||||
throw {
|
||||
errCode: ERROR.CONFIG_FIELD_REQUIRED,
|
||||
errMsgValue: {
|
||||
field: 'requestAuthSecret'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const timeout = 20 * 1000 // 请求超过20秒不能再请求,防止重放攻击
|
||||
const { headers, body: _body } = this.getHttpInfo()
|
||||
const { 'uni-id-nonce': nonce, 'uni-id-timestamp': timestamp, 'uni-id-signature': signature } = headers
|
||||
const body = JSON.parse(_body).params || {}
|
||||
const bodyStr = Object.keys(body)
|
||||
.sort()
|
||||
.filter(item => typeof body[item] !== 'object')
|
||||
.map(item => `${item}=${body[item]}`)
|
||||
.join('&')
|
||||
|
||||
if (isNaN(Number(timestamp)) || (Number(timestamp) + timeout) < Date.now()) {
|
||||
console.error('[timestamp error], timestamp:', timestamp, 'timeout:', timeout)
|
||||
|
||||
throw {
|
||||
errCode: ERROR.ILLEGAL_REQUEST
|
||||
}
|
||||
}
|
||||
|
||||
const reSignature = crypto.createHmac('sha256', `${this.config.requestAuthSecret + nonce}`).update(`${timestamp}${bodyStr}`).digest('hex')
|
||||
|
||||
if (signature !== reSignature.toUpperCase()) {
|
||||
console.error('[signature error], signature:', signature, 'reSignature:', reSignature.toUpperCase(), 'requestAuthSecret:', this.config.requestAuthSecret)
|
||||
throw {
|
||||
errCode: ERROR.ILLEGAL_REQUEST
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
const {
|
||||
setUserStatus
|
||||
} = require('../../lib/utils/update-user-info')
|
||||
const {
|
||||
USER_STATUS
|
||||
} = require('../../common/constants')
|
||||
|
||||
/**
|
||||
* 注销账户
|
||||
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#close-account
|
||||
* @returns
|
||||
*/
|
||||
module.exports = async function () {
|
||||
const { uid } = this.authInfo
|
||||
return setUserStatus(uid, USER_STATUS.CLOSED)
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
const {
|
||||
userCollection
|
||||
} = require('../../common/constants')
|
||||
const {
|
||||
ERROR
|
||||
} = require('../../common/error')
|
||||
|
||||
function isUsernameSet (userRecord) {
|
||||
return !!userRecord.username
|
||||
}
|
||||
function isNicknameSet (userRecord) {
|
||||
return !!userRecord.nickname
|
||||
}
|
||||
function isPasswordSet (userRecord) {
|
||||
return !!userRecord.password
|
||||
}
|
||||
function isMobileBound (userRecord) {
|
||||
return !!(userRecord.mobile && userRecord.mobile_confirmed)
|
||||
}
|
||||
function isEmailBound (userRecord) {
|
||||
return !!(userRecord.email && userRecord.email_confirmed)
|
||||
}
|
||||
function isWeixinBound (userRecord) {
|
||||
return !!(
|
||||
userRecord.wx_unionid ||
|
||||
Object.keys(userRecord.wx_openid || {}).length
|
||||
)
|
||||
}
|
||||
function isQQBound (userRecord) {
|
||||
return !!(
|
||||
userRecord.qq_unionid ||
|
||||
Object.keys(userRecord.qq_openid || {}).length
|
||||
)
|
||||
}
|
||||
function isAlipayBound (userRecord) {
|
||||
return !!userRecord.ali_openid
|
||||
}
|
||||
function isAppleBound (userRecord) {
|
||||
return !!userRecord.apple_openid
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取账户账户简略信息
|
||||
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#get-account-info
|
||||
*/
|
||||
module.exports = async function () {
|
||||
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
|
||||
}
|
||||
}
|
||||
return {
|
||||
errCode: 0,
|
||||
isUsernameSet: isUsernameSet(userRecord),
|
||||
isNicknameSet: isNicknameSet(userRecord),
|
||||
isPasswordSet: isPasswordSet(userRecord),
|
||||
isMobileBound: isMobileBound(userRecord),
|
||||
isEmailBound: isEmailBound(userRecord),
|
||||
isWeixinBound: isWeixinBound(userRecord),
|
||||
isQQBound: isQQBound(userRecord),
|
||||
isAlipayBound: isAlipayBound(userRecord),
|
||||
isAppleBound: isAppleBound(userRecord)
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
module.exports = {
|
||||
setPwd: require('./set-pwd'),
|
||||
updatePwd: require('./update-pwd'),
|
||||
resetPwdBySms: require('./reset-pwd-by-sms'),
|
||||
resetPwdByEmail: require('./reset-pwd-by-email'),
|
||||
closeAccount: require('./close-account'),
|
||||
getAccountInfo: require('./get-account-info'),
|
||||
getRealNameInfo: require('./get-realname-info')
|
||||
}
|
@ -0,0 +1,128 @@
|
||||
const {
|
||||
ERROR
|
||||
} = require('../../common/error')
|
||||
const {
|
||||
getNeedCaptcha,
|
||||
verifyCaptcha
|
||||
} = require('../../lib/utils/captcha')
|
||||
const {
|
||||
verifyEmailCode
|
||||
} = require('../../lib/utils/verify-code')
|
||||
const {
|
||||
userCollection,
|
||||
EMAIL_SCENE,
|
||||
CAPTCHA_SCENE,
|
||||
LOG_TYPE
|
||||
} = require('../../common/constants')
|
||||
const {
|
||||
findUser
|
||||
} = require('../../lib/utils/account')
|
||||
const PasswordUtils = require('../../lib/utils/password')
|
||||
|
||||
/**
|
||||
* 通过邮箱验证码重置密码
|
||||
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#reset-pwd-by-email
|
||||
* @param {object} params
|
||||
* @param {string} params.email 邮箱
|
||||
* @param {string} params.code 邮箱验证码
|
||||
* @param {string} params.password 密码
|
||||
* @param {string} params.captcha 图形验证码
|
||||
* @returns {object}
|
||||
*/
|
||||
module.exports = async function (params = {}) {
|
||||
const schema = {
|
||||
email: 'email',
|
||||
code: 'string',
|
||||
password: 'password',
|
||||
captcha: {
|
||||
required: false,
|
||||
type: 'string'
|
||||
}
|
||||
}
|
||||
this.middleware.validate(params, schema)
|
||||
const {
|
||||
email,
|
||||
code,
|
||||
password,
|
||||
captcha
|
||||
} = params
|
||||
|
||||
const needCaptcha = await getNeedCaptcha.call(this, {
|
||||
email,
|
||||
type: LOG_TYPE.RESET_PWD_BY_EMAIL
|
||||
})
|
||||
if (needCaptcha) {
|
||||
await verifyCaptcha.call(this, {
|
||||
captcha,
|
||||
scene: CAPTCHA_SCENE.RESET_PWD_BY_EMAIL
|
||||
})
|
||||
}
|
||||
try {
|
||||
// 验证手机号验证码,验证不通过时写入失败日志
|
||||
await verifyEmailCode({
|
||||
email,
|
||||
code,
|
||||
scene: EMAIL_SCENE.RESET_PWD_BY_EMAIL
|
||||
})
|
||||
} catch (error) {
|
||||
await this.middleware.uniIdLog({
|
||||
data: {
|
||||
email
|
||||
},
|
||||
type: LOG_TYPE.RESET_PWD_BY_EMAIL,
|
||||
success: false
|
||||
})
|
||||
throw error
|
||||
}
|
||||
// 根据手机号查找匹配的用户
|
||||
const {
|
||||
total,
|
||||
userMatched
|
||||
} = await findUser.call(this, {
|
||||
userQuery: {
|
||||
email
|
||||
},
|
||||
authorizedApp: [this.getUniversalClientInfo().appId]
|
||||
})
|
||||
if (userMatched.length === 0) {
|
||||
if (total > 0) {
|
||||
throw {
|
||||
errCode: ERROR.ACCOUNT_NOT_EXISTS_IN_CURRENT_APP
|
||||
}
|
||||
}
|
||||
throw {
|
||||
errCode: ERROR.ACCOUNT_NOT_EXISTS
|
||||
}
|
||||
} else if (userMatched.length > 1) {
|
||||
throw {
|
||||
errCode: ERROR.ACCOUNT_CONFLICT
|
||||
}
|
||||
}
|
||||
const { _id: uid } = userMatched[0]
|
||||
const {
|
||||
passwordHash,
|
||||
version
|
||||
} = new PasswordUtils({
|
||||
clientInfo: this.getUniversalClientInfo(),
|
||||
passwordSecret: this.config.passwordSecret
|
||||
}).generatePasswordHash({
|
||||
password
|
||||
})
|
||||
// 更新用户密码
|
||||
await userCollection.doc(uid).update({
|
||||
password: passwordHash,
|
||||
password_secret_version: version,
|
||||
valid_token_date: Date.now()
|
||||
})
|
||||
|
||||
// 写入成功日志
|
||||
await this.middleware.uniIdLog({
|
||||
data: {
|
||||
email
|
||||
},
|
||||
type: LOG_TYPE.RESET_PWD_BY_SMS
|
||||
})
|
||||
return {
|
||||
errCode: 0
|
||||
}
|
||||
}
|
@ -0,0 +1,128 @@
|
||||
const {
|
||||
ERROR
|
||||
} = require('../../common/error')
|
||||
const {
|
||||
getNeedCaptcha,
|
||||
verifyCaptcha
|
||||
} = require('../../lib/utils/captcha')
|
||||
const {
|
||||
verifyMobileCode
|
||||
} = require('../../lib/utils/verify-code')
|
||||
const {
|
||||
userCollection,
|
||||
SMS_SCENE,
|
||||
CAPTCHA_SCENE,
|
||||
LOG_TYPE
|
||||
} = require('../../common/constants')
|
||||
const {
|
||||
findUser
|
||||
} = require('../../lib/utils/account')
|
||||
const PasswordUtils = require('../../lib/utils/password')
|
||||
|
||||
/**
|
||||
* 通过短信验证码重置密码
|
||||
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#reset-pwd-by-sms
|
||||
* @param {object} params
|
||||
* @param {string} params.mobile 手机号
|
||||
* @param {string} params.mobile 短信验证码
|
||||
* @param {string} params.password 密码
|
||||
* @param {string} params.captcha 图形验证码
|
||||
* @returns {object}
|
||||
*/
|
||||
module.exports = async function (params = {}) {
|
||||
const schema = {
|
||||
mobile: 'mobile',
|
||||
code: 'string',
|
||||
password: 'password',
|
||||
captcha: {
|
||||
required: false,
|
||||
type: 'string'
|
||||
}
|
||||
}
|
||||
this.middleware.validate(params, schema)
|
||||
const {
|
||||
mobile,
|
||||
code,
|
||||
password,
|
||||
captcha
|
||||
} = params
|
||||
|
||||
const needCaptcha = await getNeedCaptcha.call(this, {
|
||||
mobile,
|
||||
type: LOG_TYPE.RESET_PWD_BY_SMS
|
||||
})
|
||||
if (needCaptcha) {
|
||||
await verifyCaptcha.call(this, {
|
||||
captcha,
|
||||
scene: CAPTCHA_SCENE.RESET_PWD_BY_SMS
|
||||
})
|
||||
}
|
||||
try {
|
||||
// 验证手机号验证码,验证不通过时写入失败日志
|
||||
await verifyMobileCode({
|
||||
mobile,
|
||||
code,
|
||||
scene: SMS_SCENE.RESET_PWD_BY_SMS
|
||||
})
|
||||
} catch (error) {
|
||||
await this.middleware.uniIdLog({
|
||||
data: {
|
||||
mobile
|
||||
},
|
||||
type: LOG_TYPE.RESET_PWD_BY_SMS,
|
||||
success: false
|
||||
})
|
||||
throw error
|
||||
}
|
||||
// 根据手机号查找匹配的用户
|
||||
const {
|
||||
total,
|
||||
userMatched
|
||||
} = await findUser.call(this, {
|
||||
userQuery: {
|
||||
mobile
|
||||
},
|
||||
authorizedApp: [this.getUniversalClientInfo().appId]
|
||||
})
|
||||
if (userMatched.length === 0) {
|
||||
if (total > 0) {
|
||||
throw {
|
||||
errCode: ERROR.ACCOUNT_NOT_EXISTS_IN_CURRENT_APP
|
||||
}
|
||||
}
|
||||
throw {
|
||||
errCode: ERROR.ACCOUNT_NOT_EXISTS
|
||||
}
|
||||
} else if (userMatched.length > 1) {
|
||||
throw {
|
||||
errCode: ERROR.ACCOUNT_CONFLICT
|
||||
}
|
||||
}
|
||||
const { _id: uid } = userMatched[0]
|
||||
const {
|
||||
passwordHash,
|
||||
version
|
||||
} = new PasswordUtils({
|
||||
clientInfo: this.getUniversalClientInfo(),
|
||||
passwordSecret: this.config.passwordSecret
|
||||
}).generatePasswordHash({
|
||||
password
|
||||
})
|
||||
// 更新用户密码
|
||||
await userCollection.doc(uid).update({
|
||||
password: passwordHash,
|
||||
password_secret_version: version,
|
||||
valid_token_date: Date.now()
|
||||
})
|
||||
|
||||
// 写入成功日志
|
||||
await this.middleware.uniIdLog({
|
||||
data: {
|
||||
mobile
|
||||
},
|
||||
type: LOG_TYPE.RESET_PWD_BY_SMS
|
||||
})
|
||||
return {
|
||||
errCode: 0
|
||||
}
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
const { userCollection, SMS_SCENE, LOG_TYPE, CAPTCHA_SCENE } = require('../../common/constants')
|
||||
const { ERROR } = require('../../common/error')
|
||||
const { verifyMobileCode } = require('../../lib/utils/verify-code')
|
||||
const PasswordUtils = require('../../lib/utils/password')
|
||||
const { getNeedCaptcha, verifyCaptcha } = require('../../lib/utils/captcha')
|
||||
|
||||
module.exports = async function (params = {}) {
|
||||
const schema = {
|
||||
password: 'password',
|
||||
code: 'string',
|
||||
captcha: {
|
||||
required: false,
|
||||
type: 'string'
|
||||
}
|
||||
}
|
||||
this.middleware.validate(params, schema)
|
||||
|
||||
const { password, code, captcha } = params
|
||||
const uid = this.authInfo.uid
|
||||
const getUserRes = await userCollection.doc(uid).get()
|
||||
const userRecord = getUserRes.data[0]
|
||||
if (!userRecord) {
|
||||
throw {
|
||||
errCode: ERROR.ACCOUNT_NOT_EXISTS
|
||||
}
|
||||
}
|
||||
|
||||
const needCaptcha = await getNeedCaptcha.call(this, {
|
||||
mobile: userRecord.mobile
|
||||
})
|
||||
|
||||
if (needCaptcha) {
|
||||
await verifyCaptcha.call(this, {
|
||||
captcha,
|
||||
scene: CAPTCHA_SCENE.SET_PWD_BY_SMS
|
||||
})
|
||||
}
|
||||
|
||||
try {
|
||||
// 验证手机号验证码,验证不通过时写入失败日志
|
||||
await verifyMobileCode({
|
||||
mobile: userRecord.mobile,
|
||||
code,
|
||||
scene: SMS_SCENE.SET_PWD_BY_SMS
|
||||
})
|
||||
} catch (error) {
|
||||
await this.middleware.uniIdLog({
|
||||
data: {
|
||||
mobile: userRecord.mobile
|
||||
},
|
||||
type: LOG_TYPE.SET_PWD_BY_SMS,
|
||||
success: false
|
||||
})
|
||||
throw error
|
||||
}
|
||||
|
||||
const {
|
||||
passwordHash,
|
||||
version
|
||||
} = new PasswordUtils({
|
||||
clientInfo: this.getUniversalClientInfo(),
|
||||
passwordSecret: this.config.passwordSecret
|
||||
}).generatePasswordHash({
|
||||
password
|
||||
})
|
||||
|
||||
// 更新用户密码
|
||||
await userCollection.doc(uid).update({
|
||||
password: passwordHash,
|
||||
password_secret_version: version
|
||||
})
|
||||
|
||||
await this.middleware.uniIdLog({
|
||||
data: {
|
||||
mobile: userRecord.mobile
|
||||
},
|
||||
type: LOG_TYPE.SET_PWD_BY_SMS
|
||||
})
|
||||
|
||||
return {
|
||||
errCode: 0
|
||||
}
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
const {
|
||||
userCollection
|
||||
} = require('../../common/constants')
|
||||
const {
|
||||
ERROR
|
||||
} = require('../../common/error')
|
||||
const PasswordUtils = require('../../lib/utils/password')
|
||||
/**
|
||||
* 更新密码
|
||||
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#update-pwd
|
||||
* @param {object} params
|
||||
* @param {string} params.oldPassword 旧密码
|
||||
* @param {string} params.newPassword 新密码
|
||||
* @returns {object}
|
||||
*/
|
||||
module.exports = async function (params = {}) {
|
||||
const schema = {
|
||||
oldPassword: 'string', // 防止密码规则调整导致旧密码无法更新
|
||||
newPassword: 'password'
|
||||
}
|
||||
this.middleware.validate(params, schema)
|
||||
const uid = this.authInfo.uid
|
||||
const getUserRes = await userCollection.doc(uid).get()
|
||||
const userRecord = getUserRes.data[0]
|
||||
if (!userRecord) {
|
||||
throw {
|
||||
errCode: ERROR.ACCOUNT_NOT_EXISTS
|
||||
}
|
||||
}
|
||||
const {
|
||||
oldPassword,
|
||||
newPassword
|
||||
} = params
|
||||
const passwordUtils = new PasswordUtils({
|
||||
userRecord,
|
||||
clientInfo: this.getUniversalClientInfo(),
|
||||
passwordSecret: this.config.passwordSecret
|
||||
})
|
||||
|
||||
const {
|
||||
success: checkPasswordSuccess
|
||||
} = passwordUtils.checkUserPassword({
|
||||
password: oldPassword,
|
||||
autoRefresh: false
|
||||
})
|
||||
|
||||
if (!checkPasswordSuccess) {
|
||||
throw {
|
||||
errCode: ERROR.PASSWORD_ERROR
|
||||
}
|
||||
}
|
||||
|
||||
const {
|
||||
passwordHash,
|
||||
version
|
||||
} = passwordUtils.generatePasswordHash({
|
||||
password: newPassword
|
||||
})
|
||||
|
||||
await userCollection.doc(uid).update({
|
||||
password: passwordHash,
|
||||
password_secret_version: version,
|
||||
valid_token_date: Date.now() // refreshToken时会校验,如果创建token时间在此时间点之前,则拒绝下发新token,返回token失效错误码
|
||||
})
|
||||
// 执行更新密码操作后客户端应将用户退出重新登录
|
||||
return {
|
||||
errCode: 0
|
||||
}
|
||||
}
|
@ -0,0 +1,131 @@
|
||||
const {
|
||||
findUser
|
||||
} = require('../../lib/utils/account')
|
||||
const {
|
||||
ERROR
|
||||
} = require('../../common/error')
|
||||
const {
|
||||
userCollection
|
||||
} = require('../../common/constants')
|
||||
const PasswordUtils = require('../../lib/utils/password')
|
||||
|
||||
/**
|
||||
* 新增用户
|
||||
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#add-user
|
||||
* @param {Object} params
|
||||
* @param {String} params.username 用户名
|
||||
* @param {String} params.password 密码
|
||||
* @param {String} params.nickname 昵称
|
||||
* @param {Array} params.authorizedApp 允许登录的AppID列表
|
||||
* @param {Array} params.role 用户角色列表
|
||||
* @param {String} params.mobile 手机号
|
||||
* @param {String} params.email 邮箱
|
||||
* @param {Array} params.tags 用户标签
|
||||
* @param {Number} params.status 用户状态
|
||||
* @returns
|
||||
*/
|
||||
module.exports = async function (params = {}) {
|
||||
const schema = {
|
||||
username: 'username',
|
||||
password: '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 {
|
||||
username,
|
||||
password,
|
||||
authorizedApp,
|
||||
nickname,
|
||||
role,
|
||||
mobile,
|
||||
email,
|
||||
tags,
|
||||
status
|
||||
} = params
|
||||
const {
|
||||
userMatched
|
||||
} = await findUser({
|
||||
userQuery: {
|
||||
username,
|
||||
mobile,
|
||||
email
|
||||
},
|
||||
authorizedApp
|
||||
})
|
||||
if (userMatched.length) {
|
||||
throw {
|
||||
errCode: ERROR.ACCOUNT_EXISTS
|
||||
}
|
||||
}
|
||||
const passwordUtils = new PasswordUtils({
|
||||
clientInfo: this.getUniversalClientInfo(),
|
||||
passwordSecret: this.config.passwordSecret
|
||||
})
|
||||
const {
|
||||
passwordHash,
|
||||
version
|
||||
} = passwordUtils.generatePasswordHash({
|
||||
password
|
||||
})
|
||||
const data = {
|
||||
username,
|
||||
password: passwordHash,
|
||||
password_secret_version: version,
|
||||
dcloud_appid: authorizedApp || [],
|
||||
nickname,
|
||||
role: role || [],
|
||||
mobile,
|
||||
email,
|
||||
tags: tags || [],
|
||||
status
|
||||
}
|
||||
if (email) {
|
||||
data.email_confirmed = 1
|
||||
}
|
||||
if (mobile) {
|
||||
data.mobile_confirmed = 1
|
||||
}
|
||||
|
||||
// 触发 beforeRegister 钩子
|
||||
const beforeRegister = this.hooks.beforeRegister
|
||||
let userRecord = data
|
||||
if (beforeRegister) {
|
||||
userRecord = await beforeRegister({
|
||||
userRecord,
|
||||
clientInfo: this.getUniversalClientInfo()
|
||||
})
|
||||
}
|
||||
|
||||
await userCollection.add(userRecord)
|
||||
return {
|
||||
errCode: 0,
|
||||
errMsg: ''
|
||||
}
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
module.exports = {
|
||||
addUser: require('./add-user'),
|
||||
updateUser: require('./update-user')
|
||||
}
|
@ -0,0 +1,138 @@
|
||||
const {
|
||||
findUser
|
||||
} = require('../../lib/utils/account')
|
||||
const {
|
||||
ERROR
|
||||
} = require('../../common/error')
|
||||
const {
|
||||
userCollection
|
||||
} = require('../../common/constants')
|
||||
const PasswordUtils = require('../../lib/utils/password')
|
||||
|
||||
/**
|
||||
* 修改用户
|
||||
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#update-user
|
||||
* @param {Object} params
|
||||
* @param {String} params.uid 要更新的用户id
|
||||
* @param {String} params.username 用户名
|
||||
* @param {String} params.password 密码
|
||||
* @param {String} params.nickname 昵称
|
||||
* @param {Array} params.authorizedApp 允许登录的AppID列表
|
||||
* @param {Array} params.role 用户角色列表
|
||||
* @param {String} params.mobile 手机号
|
||||
* @param {String} params.email 邮箱
|
||||
* @param {Array} params.tags 用户标签
|
||||
* @param {Number} params.status 用户状态
|
||||
* @returns
|
||||
*/
|
||||
module.exports = async function (params = {}) {
|
||||
const schema = {
|
||||
uid: 'string',
|
||||
username: 'username',
|
||||
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,
|
||||
username,
|
||||
password,
|
||||
authorizedApp,
|
||||
nickname,
|
||||
role,
|
||||
mobile,
|
||||
email,
|
||||
tags,
|
||||
status
|
||||
} = params
|
||||
|
||||
// 更新的用户数据字段
|
||||
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.doc(uid).update(realData)
|
||||
|
||||
return {
|
||||
errCode: 0
|
||||
}
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
function isMobileCodeSupported () {
|
||||
const config = this.config
|
||||
return !!(config.service && config.service.sms && config.service.sms.smsKey)
|
||||
}
|
||||
|
||||
function isUniverifySupport () {
|
||||
return true
|
||||
}
|
||||
|
||||
function isWeixinSupported () {
|
||||
this.configUtils.getOauthConfig({
|
||||
provider: 'weixin'
|
||||
})
|
||||
return true
|
||||
}
|
||||
|
||||
function isQQSupported () {
|
||||
this.configUtils.getOauthConfig({
|
||||
provider: 'qq'
|
||||
})
|
||||
return true
|
||||
}
|
||||
|
||||
function isAppleSupported () {
|
||||
this.configUtils.getOauthConfig({
|
||||
provider: 'apple'
|
||||
})
|
||||
return true
|
||||
}
|
||||
|
||||
function isAlipaySupported () {
|
||||
this.configUtils.getOauthConfig({
|
||||
provider: 'alipay'
|
||||
})
|
||||
return true
|
||||
}
|
||||
|
||||
const loginTypeTester = {
|
||||
'mobile-code': isMobileCodeSupported,
|
||||
univerify: isUniverifySupport,
|
||||
weixin: isWeixinSupported,
|
||||
qq: isQQSupported,
|
||||
apple: isAppleSupported,
|
||||
alipay: isAlipaySupported
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取支持的登录方式
|
||||
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#get-supported-login-type
|
||||
* @returns
|
||||
*/
|
||||
module.exports = async function () {
|
||||
const supportedLoginType = [
|
||||
'username-password',
|
||||
'mobile-password',
|
||||
'email-password'
|
||||
]
|
||||
for (const type in loginTypeTester) {
|
||||
try {
|
||||
if (loginTypeTester[type].call(this)) {
|
||||
supportedLoginType.push(type)
|
||||
}
|
||||
} catch (error) { }
|
||||
}
|
||||
return {
|
||||
errCode: 0,
|
||||
errMsg: '',
|
||||
supportedLoginType
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
getSupportedLoginType: require('./get-supported-login-type')
|
||||
}
|
5
uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/external/index.js
vendored
Normal file
5
uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/external/index.js
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
module.exports = {
|
||||
externalRegister: require('./register'),
|
||||
externalLogin: require('./login'),
|
||||
updateUserInfoByExternal: require('./update-user-info')
|
||||
}
|
68
uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/external/login.js
vendored
Normal file
68
uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/external/login.js
vendored
Normal file
@ -0,0 +1,68 @@
|
||||
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 = {
|
||||
uid: {
|
||||
required: false,
|
||||
type: 'string'
|
||||
},
|
||||
externalUid: {
|
||||
required: false,
|
||||
type: 'string'
|
||||
}
|
||||
}
|
||||
|
||||
this.middleware.validate(params, schema)
|
||||
|
||||
const {
|
||||
uid,
|
||||
externalUid
|
||||
} = params
|
||||
|
||||
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, {
|
||||
user
|
||||
})
|
||||
|
||||
return {
|
||||
errCode: result.errCode,
|
||||
newToken: result.newToken,
|
||||
uid: result.uid
|
||||
}
|
||||
}
|
93
uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/external/register.js
vendored
Normal file
93
uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/external/register.js
vendored
Normal file
@ -0,0 +1,93 @@
|
||||
const url = require('url')
|
||||
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 {number} params.gender 性别
|
||||
* @param {string} params.avatar 头像
|
||||
* @returns {object}
|
||||
*/
|
||||
module.exports = async function (params = {}) {
|
||||
const schema = {
|
||||
externalUid: 'string',
|
||||
nickname: {
|
||||
required: false,
|
||||
type: 'nickname'
|
||||
},
|
||||
gender: {
|
||||
required: false,
|
||||
type: 'number'
|
||||
},
|
||||
avatar: {
|
||||
required: false,
|
||||
type: 'string'
|
||||
}
|
||||
}
|
||||
|
||||
this.middleware.validate(params, schema)
|
||||
|
||||
const {
|
||||
externalUid,
|
||||
avatar,
|
||||
gender,
|
||||
nickname
|
||||
} = params
|
||||
|
||||
await preRegister.call(this, {
|
||||
user: {
|
||||
identities: {
|
||||
provider: EXTERNAL_DIRECT_CONNECT_PROVIDER,
|
||||
uid: externalUid
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const extraData = {}
|
||||
|
||||
if (avatar) {
|
||||
// eslint-disable-next-line n/no-deprecated-api
|
||||
const avatarPath = url.parse(avatar).pathname
|
||||
const extName = avatarPath.indexOf('.') > -1 ? avatarPath.split('.').pop() : ''
|
||||
|
||||
extraData.avatar_file = {
|
||||
name: avatarPath,
|
||||
extname: extName,
|
||||
url: avatar
|
||||
}
|
||||
}
|
||||
|
||||
const result = await postRegister.call(this, {
|
||||
user: {
|
||||
avatar,
|
||||
gender,
|
||||
nickname,
|
||||
identities: [
|
||||
{
|
||||
provider: EXTERNAL_DIRECT_CONNECT_PROVIDER,
|
||||
userInfo: {
|
||||
avatar,
|
||||
gender,
|
||||
nickname
|
||||
},
|
||||
uid: externalUid
|
||||
}
|
||||
]
|
||||
},
|
||||
extraData
|
||||
})
|
||||
|
||||
return {
|
||||
errCode: result.errCode,
|
||||
newToken: result.newToken,
|
||||
externalUid,
|
||||
avatar,
|
||||
gender,
|
||||
nickname,
|
||||
uid: result.uid
|
||||
}
|
||||
}
|
208
uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/external/update-user-info.js
vendored
Normal file
208
uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/external/update-user-info.js
vendored
Normal file
@ -0,0 +1,208 @@
|
||||
const url = require('url')
|
||||
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'
|
||||
},
|
||||
gender: {
|
||||
required: false,
|
||||
type: 'number'
|
||||
},
|
||||
avatar: {
|
||||
required: false,
|
||||
type: 'string'
|
||||
}
|
||||
}
|
||||
|
||||
this.middleware.validate(params, schema)
|
||||
|
||||
const {
|
||||
uid,
|
||||
externalUid,
|
||||
username,
|
||||
password,
|
||||
authorizedApp,
|
||||
nickname,
|
||||
role,
|
||||
mobile,
|
||||
email,
|
||||
tags,
|
||||
status,
|
||||
avatar,
|
||||
gender
|
||||
} = params
|
||||
|
||||
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 users = await userCollection.where(query).get()
|
||||
const user = users.data && users.data[0]
|
||||
if (!user) {
|
||||
throw {
|
||||
errCode: ERROR.ACCOUNT_NOT_EXISTS
|
||||
}
|
||||
}
|
||||
|
||||
// 更新的用户数据字段
|
||||
const data = {
|
||||
username,
|
||||
dcloud_appid: authorizedApp,
|
||||
nickname,
|
||||
role,
|
||||
mobile,
|
||||
email,
|
||||
tags,
|
||||
status,
|
||||
avatar,
|
||||
gender
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
if (avatar) {
|
||||
// eslint-disable-next-line n/no-deprecated-api
|
||||
const avatarPath = url.parse(avatar).pathname
|
||||
const extName = avatarPath.indexOf('.') > -1 ? avatarPath.split('.').pop() : ''
|
||||
|
||||
realData.avatar_file = {
|
||||
name: avatarPath,
|
||||
extname: extName,
|
||||
url: avatar
|
||||
}
|
||||
}
|
||||
|
||||
if (user.identities.length) {
|
||||
const identity = user.identities.find(item => item.provider === EXTERNAL_DIRECT_CONNECT_PROVIDER)
|
||||
|
||||
if (identity) {
|
||||
identity.userInfo = {
|
||||
avatar,
|
||||
gender,
|
||||
nickname
|
||||
}
|
||||
}
|
||||
|
||||
realData.identities = user.identities
|
||||
}
|
||||
|
||||
await userCollection.where(query).update(realData)
|
||||
|
||||
return {
|
||||
errCode: 0
|
||||
}
|
||||
}
|
@ -0,0 +1,136 @@
|
||||
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
|
||||
})
|
||||
|
||||
console.log(ERROR.FRV_FAIL, 'error: ', res)
|
||||
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`,
|
||||
cloudPathAsRealPath: true,
|
||||
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)) // 对身份证号进行脱敏处理
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
module.exports = {
|
||||
getFrvCertifyId: require('./get-certify-id'),
|
||||
getFrvAuthResult: require('./get-auth-result')
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
const {
|
||||
acceptInvite
|
||||
} = require('../../lib/utils/fission')
|
||||
|
||||
/**
|
||||
* 接受邀请
|
||||
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#accept-invite
|
||||
* @param {Object} params
|
||||
* @param {String} params.inviteCode 邀请码
|
||||
* @returns
|
||||
*/
|
||||
module.exports = async function (params = {}) {
|
||||
const schema = {
|
||||
inviteCode: 'string'
|
||||
}
|
||||
this.middleware.validate(params, schema)
|
||||
const {
|
||||
inviteCode
|
||||
} = params
|
||||
const uid = this.authInfo.uid
|
||||
return acceptInvite({
|
||||
uid,
|
||||
inviteCode
|
||||
})
|
||||
}
|
@ -0,0 +1,80 @@
|
||||
const {
|
||||
userCollection
|
||||
} = require('../../common/constants')
|
||||
const {
|
||||
coverMobile
|
||||
} = require('../../common/utils')
|
||||
|
||||
/**
|
||||
* 获取受邀用户
|
||||
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#get-invited-user
|
||||
* @param {Object} params
|
||||
* @param {Number} params.level 获取受邀用户的级数,1表示直接邀请的用户
|
||||
* @param {Number} params.limit 返回数据大小
|
||||
* @param {Number} params.offset 返回数据偏移
|
||||
* @param {Boolean} params.needTotal 是否需要返回总数
|
||||
* @returns
|
||||
*/
|
||||
module.exports = async function (params = {}) {
|
||||
const schema = {
|
||||
level: 'number',
|
||||
limit: {
|
||||
required: false,
|
||||
type: 'number'
|
||||
},
|
||||
offset: {
|
||||
required: false,
|
||||
type: 'number'
|
||||
},
|
||||
needTotal: {
|
||||
required: false,
|
||||
type: 'boolean'
|
||||
}
|
||||
}
|
||||
this.middleware.validate(params, schema)
|
||||
const {
|
||||
level,
|
||||
limit = 20,
|
||||
offset = 0,
|
||||
needTotal = false
|
||||
} = params
|
||||
const uid = this.authInfo.uid
|
||||
const query = {
|
||||
[`inviter_uid.${level - 1}`]: uid
|
||||
}
|
||||
const getUserRes = await userCollection.where(query)
|
||||
.field({
|
||||
_id: true,
|
||||
avatar: true,
|
||||
avatar_file: true,
|
||||
username: true,
|
||||
nickname: true,
|
||||
mobile: true,
|
||||
invite_time: true
|
||||
})
|
||||
.orderBy('invite_time', 'desc')
|
||||
.skip(offset)
|
||||
.limit(limit)
|
||||
.get()
|
||||
|
||||
const invitedUser = getUserRes.data.map(item => {
|
||||
return {
|
||||
uid: item._id,
|
||||
username: item.username,
|
||||
nickname: item.nickname,
|
||||
mobile: coverMobile(item.mobile),
|
||||
inviteTime: item.invite_time,
|
||||
avatar: item.avatar,
|
||||
avatarFile: item.avatar_file
|
||||
}
|
||||
})
|
||||
const result = {
|
||||
errCode: 0,
|
||||
invitedUser
|
||||
}
|
||||
if (needTotal) {
|
||||
const getTotalRes = await userCollection.where(query).count()
|
||||
result.total = getTotalRes.total
|
||||
}
|
||||
return result
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
module.exports = {
|
||||
acceptInvite: require('./accept-invite'),
|
||||
getInvitedUser: require('./get-invited-user')
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
module.exports = {
|
||||
login: require('./login'),
|
||||
loginBySms: require('./login-by-sms'),
|
||||
loginByUniverify: require('./login-by-univerify'),
|
||||
loginByWeixin: require('./login-by-weixin'),
|
||||
loginByAlipay: require('./login-by-alipay'),
|
||||
loginByQQ: require('./login-by-qq'),
|
||||
loginByApple: require('./login-by-apple'),
|
||||
loginByBaidu: require('./login-by-baidu'),
|
||||
loginByDingtalk: require('./login-by-dingtalk'),
|
||||
loginByToutiao: require('./login-by-toutiao'),
|
||||
loginByDouyin: require('./login-by-douyin'),
|
||||
loginByWeibo: require('./login-by-weibo'),
|
||||
loginByTaobao: require('./login-by-taobao'),
|
||||
loginByEmailLink: require('./login-by-email-link'),
|
||||
loginByEmailCode: require('./login-by-email-code'),
|
||||
loginByFacebook: require('./login-by-facebook'),
|
||||
loginByGoogle: require('./login-by-google'),
|
||||
loginByWeixinMobile: require('./login-by-weixin-mobile')
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
const {
|
||||
initAlipay
|
||||
} = require('../../lib/third-party/index')
|
||||
const {
|
||||
ERROR
|
||||
} = require('../../common/error')
|
||||
const {
|
||||
preUnifiedLogin,
|
||||
postUnifiedLogin
|
||||
} = require('../../lib/utils/unified-login')
|
||||
const {
|
||||
LOG_TYPE
|
||||
} = require('../../common/constants')
|
||||
|
||||
/**
|
||||
* 支付宝登录
|
||||
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#login-by-alipay
|
||||
* @param {Object} params
|
||||
* @param {String} params.code 支付宝小程序客户端登录返回的code
|
||||
* @param {String} params.inviteCode 邀请码
|
||||
* @returns
|
||||
*/
|
||||
module.exports = async function (params = {}) {
|
||||
const schema = {
|
||||
code: 'string',
|
||||
inviteCode: {
|
||||
type: 'string',
|
||||
required: false
|
||||
}
|
||||
}
|
||||
this.middleware.validate(params, schema)
|
||||
const {
|
||||
code,
|
||||
inviteCode
|
||||
} = params
|
||||
const alipayApi = initAlipay.call(this)
|
||||
let getAlipayAccountResult
|
||||
try {
|
||||
getAlipayAccountResult = await alipayApi.code2Session(code)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
await this.middleware.uniIdLog({
|
||||
success: false,
|
||||
type: LOG_TYPE.LOGIN
|
||||
})
|
||||
throw {
|
||||
errCode: ERROR.GET_THIRD_PARTY_ACCOUNT_FAILED
|
||||
}
|
||||
}
|
||||
|
||||
const {
|
||||
openid
|
||||
} = getAlipayAccountResult
|
||||
|
||||
const {
|
||||
type,
|
||||
user
|
||||
} = await preUnifiedLogin.call(this, {
|
||||
user: {
|
||||
ali_openid: openid
|
||||
}
|
||||
})
|
||||
return postUnifiedLogin.call(this, {
|
||||
user,
|
||||
extraData: {},
|
||||
isThirdParty: true,
|
||||
type,
|
||||
inviteCode
|
||||
})
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
const {
|
||||
initApple
|
||||
} = require('../../lib/third-party/index')
|
||||
const {
|
||||
ERROR
|
||||
} = require('../../common/error')
|
||||
const {
|
||||
preUnifiedLogin,
|
||||
postUnifiedLogin
|
||||
} = require('../../lib/utils/unified-login')
|
||||
const {
|
||||
LOG_TYPE
|
||||
} = require('../../common/constants')
|
||||
|
||||
/**
|
||||
* 苹果登录
|
||||
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#login-by-apple
|
||||
* @param {Object} params
|
||||
* @param {String} params.identityToken 苹果登录返回的identityToken
|
||||
* @param {String} params.nickname 用户昵称
|
||||
* @param {String} params.inviteCode 邀请码
|
||||
* @returns
|
||||
*/
|
||||
module.exports = async function (params = {}) {
|
||||
const schema = {
|
||||
identityToken: 'string',
|
||||
nickname: {
|
||||
required: false,
|
||||
type: 'nickname'
|
||||
},
|
||||
inviteCode: {
|
||||
required: false,
|
||||
type: 'string'
|
||||
}
|
||||
}
|
||||
this.middleware.validate(params, schema)
|
||||
const {
|
||||
identityToken,
|
||||
nickname,
|
||||
inviteCode
|
||||
} = params
|
||||
const appleApi = initApple.call(this)
|
||||
let verifyResult
|
||||
try {
|
||||
verifyResult = await appleApi.verifyIdentityToken(identityToken)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
await this.middleware.uniIdLog({
|
||||
success: false,
|
||||
type: LOG_TYPE.LOGIN
|
||||
})
|
||||
throw {
|
||||
errCode: ERROR.GET_THIRD_PARTY_ACCOUNT_FAILED
|
||||
}
|
||||
}
|
||||
const {
|
||||
openid
|
||||
} = verifyResult
|
||||
|
||||
const {
|
||||
type,
|
||||
user
|
||||
} = await preUnifiedLogin.call(this, {
|
||||
user: {
|
||||
apple_openid: openid
|
||||
}
|
||||
})
|
||||
return postUnifiedLogin.call(this, {
|
||||
user,
|
||||
extraData: {
|
||||
nickname
|
||||
},
|
||||
isThirdParty: true,
|
||||
type,
|
||||
inviteCode
|
||||
})
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
/**
|
||||
* 百度登录
|
||||
* @param {Object} params
|
||||
* @returns
|
||||
*/
|
||||
module.exports = async function (params = {}) {
|
||||
// 此接口暂未实现,欢迎向我们提交pr
|
||||
throw new Error('api[loginByBaidu] is not yet implemented')
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
/**
|
||||
* 钉钉登录
|
||||
* @param {Object} params
|
||||
* @returns
|
||||
*/
|
||||
module.exports = async function (params = {}) {
|
||||
// 此接口暂未实现,欢迎向我们提交pr
|
||||
throw new Error('api[loginByDingtalk] is not yet implemented')
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
/**
|
||||
* 抖音登录
|
||||
* @param {Object} params
|
||||
* @returns
|
||||
*/
|
||||
module.exports = async function (params = {}) {
|
||||
// 此接口暂未实现,欢迎向我们提交pr
|
||||
throw new Error('api[loginByDouyin] is not yet implemented')
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
/**
|
||||
* 邮箱验证码登录
|
||||
* @param {Object} params
|
||||
* @returns
|
||||
*/
|
||||
module.exports = async function (params = {}) {
|
||||
// 此接口暂未实现,欢迎向我们提交pr
|
||||
throw new Error('api[loginByEmailCode] is not yet implemented')
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
/**
|
||||
* 邮箱点击链接登录
|
||||
* @param {Object} params
|
||||
* @returns
|
||||
*/
|
||||
module.exports = async function (params = {}) {
|
||||
// 此接口暂未实现,欢迎向我们提交pr
|
||||
throw new Error('api[loginByEmailLink] is not yet implemented')
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Facebook登录
|
||||
* @param {Object} params
|
||||
* @returns
|
||||
*/
|
||||
module.exports = async function (params = {}) {
|
||||
// 此接口暂未实现,欢迎向我们提交pr
|
||||
throw new Error('api[loginByFacebook] is not yet implemented')
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Google登录
|
||||
* @param {Object} params
|
||||
* @returns
|
||||
*/
|
||||
module.exports = async function (params = {}) {
|
||||
// 此接口暂未实现,欢迎向我们提交pr
|
||||
throw new Error('api[loginByGoogle] is not yet implemented')
|
||||
}
|
@ -0,0 +1,167 @@
|
||||
const {
|
||||
initQQ
|
||||
} = require('../../lib/third-party/index')
|
||||
const {
|
||||
ERROR
|
||||
} = require('../../common/error')
|
||||
const {
|
||||
preUnifiedLogin,
|
||||
postUnifiedLogin
|
||||
} = require('../../lib/utils/unified-login')
|
||||
const {
|
||||
LOG_TYPE
|
||||
} = require('../../common/constants')
|
||||
const {
|
||||
getQQPlatform,
|
||||
generateQQCache,
|
||||
saveQQUserKey
|
||||
} = require('../../lib/utils/qq')
|
||||
const url = require('url')
|
||||
|
||||
/**
|
||||
* QQ登录
|
||||
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#login-by-qq
|
||||
* @param {Object} params
|
||||
* @param {String} params.code QQ小程序登录返回的code参数
|
||||
* @param {String} params.accessToken App端QQ登录返回的accessToken参数
|
||||
* @param {String} params.accessTokenExpired accessToken过期时间,由App端QQ登录返回的expires_in参数计算而来,单位:毫秒
|
||||
* @param {String} params.inviteCode 邀请码
|
||||
* @returns
|
||||
*/
|
||||
module.exports = async function (params = {}) {
|
||||
const schema = {
|
||||
code: {
|
||||
type: 'string',
|
||||
required: false
|
||||
},
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: false
|
||||
},
|
||||
accessTokenExpired: {
|
||||
type: 'number',
|
||||
required: false
|
||||
},
|
||||
inviteCode: {
|
||||
type: 'string',
|
||||
required: false
|
||||
}
|
||||
}
|
||||
this.middleware.validate(params, schema)
|
||||
const {
|
||||
code,
|
||||
accessToken,
|
||||
accessTokenExpired,
|
||||
inviteCode
|
||||
} = params
|
||||
const {
|
||||
appId
|
||||
} = this.getUniversalClientInfo()
|
||||
const qqApi = initQQ.call(this)
|
||||
const qqPlatform = getQQPlatform.call(this)
|
||||
let apiName
|
||||
switch (qqPlatform) {
|
||||
case 'mp':
|
||||
apiName = 'code2Session'
|
||||
break
|
||||
case 'app':
|
||||
apiName = 'getOpenidByToken'
|
||||
break
|
||||
default:
|
||||
throw new Error('Unsupported qq platform')
|
||||
}
|
||||
let getQQAccountResult
|
||||
try {
|
||||
getQQAccountResult = await qqApi[apiName]({
|
||||
code,
|
||||
accessToken
|
||||
})
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
await this.middleware.uniIdLog({
|
||||
success: false,
|
||||
type: LOG_TYPE.LOGIN
|
||||
})
|
||||
throw {
|
||||
errCode: ERROR.GET_THIRD_PARTY_ACCOUNT_FAILED
|
||||
}
|
||||
}
|
||||
|
||||
const {
|
||||
openid,
|
||||
unionid,
|
||||
// 保存下面的字段
|
||||
sessionKey // QQ小程序用户sessionKey
|
||||
} = getQQAccountResult
|
||||
|
||||
const {
|
||||
type,
|
||||
user
|
||||
} = await preUnifiedLogin.call(this, {
|
||||
user: {
|
||||
qq_openid: {
|
||||
[qqPlatform]: openid
|
||||
},
|
||||
qq_unionid: unionid
|
||||
}
|
||||
})
|
||||
const extraData = {
|
||||
qq_openid: {
|
||||
[`${qqPlatform}_${appId}`]: openid
|
||||
},
|
||||
qq_unionid: unionid
|
||||
}
|
||||
if (type === 'register' && qqPlatform !== 'mp') {
|
||||
const {
|
||||
nickname,
|
||||
avatar
|
||||
} = await qqApi.getUserInfo({
|
||||
accessToken,
|
||||
openid
|
||||
})
|
||||
if (avatar) {
|
||||
// eslint-disable-next-line n/no-deprecated-api
|
||||
const extName = url.parse(avatar).pathname.split('.').pop()
|
||||
const cloudPath = `user/avatar/${openid.slice(-8) + Date.now()}-avatar.${extName}`
|
||||
const getAvatarRes = await uniCloud.httpclient.request(avatar)
|
||||
if (getAvatarRes.status >= 400) {
|
||||
throw {
|
||||
errCode: ERROR.GET_THIRD_PARTY_USER_INFO_FAILED
|
||||
}
|
||||
}
|
||||
const {
|
||||
fileID
|
||||
} = await uniCloud.uploadFile({
|
||||
cloudPath,
|
||||
fileContent: getAvatarRes.data
|
||||
})
|
||||
extraData.avatar_file = {
|
||||
name: cloudPath,
|
||||
extname: extName,
|
||||
url: fileID
|
||||
}
|
||||
}
|
||||
extraData.nickname = nickname
|
||||
}
|
||||
await saveQQUserKey.call(this, {
|
||||
openid,
|
||||
sessionKey,
|
||||
accessToken,
|
||||
accessTokenExpired
|
||||
})
|
||||
return postUnifiedLogin.call(this, {
|
||||
user,
|
||||
extraData: {
|
||||
...extraData,
|
||||
...generateQQCache.call(this, {
|
||||
openid,
|
||||
sessionKey, // QQ小程序用户sessionKey
|
||||
accessToken, // App端QQ用户accessToken
|
||||
accessTokenExpired // App端QQ用户accessToken过期时间
|
||||
})
|
||||
},
|
||||
isThirdParty: true,
|
||||
type,
|
||||
inviteCode
|
||||
})
|
||||
}
|
@ -0,0 +1,99 @@
|
||||
const {
|
||||
getNeedCaptcha,
|
||||
verifyCaptcha
|
||||
} = require('../../lib/utils/captcha')
|
||||
const {
|
||||
verifyMobileCode
|
||||
} = require('../../lib/utils/verify-code')
|
||||
const {
|
||||
preUnifiedLogin,
|
||||
postUnifiedLogin
|
||||
} = require('../../lib/utils/unified-login')
|
||||
const {
|
||||
CAPTCHA_SCENE,
|
||||
SMS_SCENE,
|
||||
LOG_TYPE
|
||||
} = require('../../common/constants')
|
||||
|
||||
/**
|
||||
* 短信验证码登录
|
||||
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#login-by-sms
|
||||
* @param {Object} params
|
||||
* @param {String} params.mobile 手机号
|
||||
* @param {String} params.code 短信验证码
|
||||
* @param {String} params.captcha 图形验证码
|
||||
* @param {String} params.inviteCode 邀请码
|
||||
* @returns
|
||||
*/
|
||||
module.exports = async function (params = {}) {
|
||||
const schema = {
|
||||
mobile: 'mobile',
|
||||
code: 'string',
|
||||
captcha: {
|
||||
required: false,
|
||||
type: 'string'
|
||||
},
|
||||
inviteCode: {
|
||||
required: false,
|
||||
type: 'string'
|
||||
}
|
||||
}
|
||||
this.middleware.validate(params, schema)
|
||||
const {
|
||||
mobile,
|
||||
code,
|
||||
captcha,
|
||||
inviteCode
|
||||
} = params
|
||||
|
||||
const needCaptcha = await getNeedCaptcha.call(this, {
|
||||
mobile
|
||||
})
|
||||
|
||||
if (needCaptcha) {
|
||||
await verifyCaptcha.call(this, {
|
||||
captcha,
|
||||
scene: CAPTCHA_SCENE.LOGIN_BY_SMS
|
||||
})
|
||||
}
|
||||
|
||||
try {
|
||||
await verifyMobileCode({
|
||||
mobile,
|
||||
code,
|
||||
scene: SMS_SCENE.LOGIN_BY_SMS
|
||||
})
|
||||
} catch (error) {
|
||||
console.log(error, {
|
||||
mobile,
|
||||
code,
|
||||
type: SMS_SCENE.LOGIN_BY_SMS
|
||||
})
|
||||
await this.middleware.uniIdLog({
|
||||
success: false,
|
||||
data: {
|
||||
mobile
|
||||
},
|
||||
type: LOG_TYPE.LOGIN
|
||||
})
|
||||
throw error
|
||||
}
|
||||
|
||||
const {
|
||||
type,
|
||||
user
|
||||
} = await preUnifiedLogin.call(this, {
|
||||
user: {
|
||||
mobile
|
||||
}
|
||||
})
|
||||
return postUnifiedLogin.call(this, {
|
||||
user,
|
||||
extraData: {
|
||||
mobile_confirmed: 1
|
||||
},
|
||||
isThirdParty: false,
|
||||
type,
|
||||
inviteCode
|
||||
})
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
/**
|
||||
* 淘宝登录
|
||||
* @param {Object} params
|
||||
* @returns
|
||||
*/
|
||||
module.exports = async function (params = {}) {
|
||||
// 此接口暂未实现,欢迎向我们提交pr
|
||||
throw new Error('api[loginByTaobao] is not yet implemented')
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
/**
|
||||
* 头条登录
|
||||
* @param {Object} params
|
||||
* @returns
|
||||
*/
|
||||
module.exports = async function (params = {}) {
|
||||
// 此接口暂未实现,欢迎向我们提交pr
|
||||
throw new Error('api[loginByToutiao] is not yet implemented')
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
const {
|
||||
getPhoneNumber
|
||||
} = require('../../lib/utils/univerify')
|
||||
const {
|
||||
preUnifiedLogin,
|
||||
postUnifiedLogin
|
||||
} = require('../../lib/utils/unified-login')
|
||||
const {
|
||||
LOG_TYPE
|
||||
} = require('../../common/constants')
|
||||
|
||||
/**
|
||||
* App端一键登录
|
||||
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#login-by-univerify
|
||||
* @param {Object} params
|
||||
* @param {String} params.access_token APP端一键登录返回的access_token
|
||||
* @param {String} params.openid APP端一键登录返回的openid
|
||||
* @param {String} params.inviteCode 邀请码
|
||||
* @returns
|
||||
*/
|
||||
module.exports = async function (params = {}) {
|
||||
const schema = {
|
||||
access_token: 'string',
|
||||
openid: 'string',
|
||||
inviteCode: {
|
||||
required: false,
|
||||
type: 'string'
|
||||
}
|
||||
}
|
||||
this.middleware.validate(params, schema)
|
||||
const {
|
||||
// eslint-disable-next-line camelcase
|
||||
access_token,
|
||||
openid,
|
||||
inviteCode
|
||||
} = params
|
||||
|
||||
let mobile
|
||||
try {
|
||||
const phoneInfo = await getPhoneNumber.call(this, {
|
||||
// eslint-disable-next-line camelcase
|
||||
access_token,
|
||||
openid
|
||||
})
|
||||
mobile = phoneInfo.phoneNumber
|
||||
} catch (error) {
|
||||
await this.middleware.uniIdLog({
|
||||
success: false,
|
||||
type: LOG_TYPE.LOGIN
|
||||
})
|
||||
throw error
|
||||
}
|
||||
const {
|
||||
user,
|
||||
type
|
||||
} = await preUnifiedLogin.call(this, {
|
||||
user: {
|
||||
mobile
|
||||
}
|
||||
})
|
||||
return postUnifiedLogin.call(this, {
|
||||
user,
|
||||
extraData: {
|
||||
mobile_confirmed: 1
|
||||
},
|
||||
type,
|
||||
inviteCode
|
||||
})
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
/**
|
||||
* 微博登录
|
||||
* @param {Object} params
|
||||
* @returns
|
||||
*/
|
||||
module.exports = async function (params = {}) {
|
||||
// 此接口暂未实现,欢迎向我们提交pr
|
||||
throw new Error('api[loginByWeibo] is not yet implemented')
|
||||
}
|
@ -0,0 +1,106 @@
|
||||
const {
|
||||
initWeixin
|
||||
} = require('../../lib/third-party/index')
|
||||
const {
|
||||
getWeixinAccessToken
|
||||
} = require('../../lib/utils/weixin')
|
||||
const {
|
||||
ERROR
|
||||
} = require('../../common/error')
|
||||
const {
|
||||
preUnifiedLogin,
|
||||
postUnifiedLogin
|
||||
} = require('../../lib/utils/unified-login')
|
||||
const {
|
||||
LOG_TYPE
|
||||
} = require('../../common/constants')
|
||||
const {
|
||||
preBind,
|
||||
postBind
|
||||
} = require('../../lib/utils/relate')
|
||||
|
||||
/**
|
||||
* 微信授权手机号登录
|
||||
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#login-by-weixin-mobile
|
||||
* @param {Object} params
|
||||
* @param {String} params.phoneCode 微信手机号返回的code
|
||||
* @param {String} params.inviteCode 邀请码
|
||||
* @returns
|
||||
*/
|
||||
module.exports = async function (params = {}) {
|
||||
const schema = {
|
||||
phoneCode: 'string',
|
||||
inviteCode: {
|
||||
type: 'string',
|
||||
required: false
|
||||
}
|
||||
}
|
||||
|
||||
this.middleware.validate(params, schema)
|
||||
|
||||
const { phoneCode, inviteCode } = params
|
||||
|
||||
const weixinApi = initWeixin.call(this)
|
||||
let mobile
|
||||
|
||||
try {
|
||||
const accessToken = await getWeixinAccessToken.call(this)
|
||||
const mobileRes = await weixinApi.getPhoneNumber(accessToken, phoneCode)
|
||||
mobile = mobileRes.purePhoneNumber
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
await this.middleware.uniIdLog({
|
||||
success: false,
|
||||
type: LOG_TYPE.LOGIN
|
||||
})
|
||||
throw {
|
||||
errCode: ERROR.GET_THIRD_PARTY_ACCOUNT_FAILED
|
||||
}
|
||||
}
|
||||
|
||||
const { type, user } = await preUnifiedLogin.call(this, {
|
||||
user: {
|
||||
mobile
|
||||
}
|
||||
})
|
||||
|
||||
let extraData = {
|
||||
mobile_confirmed: 1
|
||||
}
|
||||
|
||||
if (type === 'login') {
|
||||
// 绑定手机号
|
||||
if (!user.mobile_confirmed) {
|
||||
const bindAccount = {
|
||||
mobile
|
||||
}
|
||||
await preBind.call(this, {
|
||||
uid: user._id,
|
||||
bindAccount,
|
||||
logType: LOG_TYPE.BIND_MOBILE
|
||||
})
|
||||
await postBind.call(this, {
|
||||
uid: user._id,
|
||||
bindAccount,
|
||||
extraData: {
|
||||
mobile_confirmed: 1
|
||||
},
|
||||
logType: LOG_TYPE.BIND_MOBILE
|
||||
})
|
||||
extraData = {
|
||||
...extraData,
|
||||
...bindAccount
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return postUnifiedLogin.call(this, {
|
||||
user,
|
||||
extraData: {
|
||||
...extraData
|
||||
},
|
||||
isThirdParty: false,
|
||||
type,
|
||||
inviteCode
|
||||
})
|
||||
}
|
@ -0,0 +1,176 @@
|
||||
const {
|
||||
initWeixin
|
||||
} = require('../../lib/third-party/index')
|
||||
const {
|
||||
ERROR
|
||||
} = require('../../common/error')
|
||||
const {
|
||||
preUnifiedLogin,
|
||||
postUnifiedLogin
|
||||
} = require('../../lib/utils/unified-login')
|
||||
const {
|
||||
generateWeixinCache,
|
||||
getWeixinPlatform,
|
||||
saveWeixinUserKey,
|
||||
saveSecureNetworkCache
|
||||
} = require('../../lib/utils/weixin')
|
||||
const {
|
||||
LOG_TYPE
|
||||
} = require('../../common/constants')
|
||||
const url = require('url')
|
||||
|
||||
/**
|
||||
* 微信登录
|
||||
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#login-by-weixin
|
||||
* @param {Object} params
|
||||
* @param {String} params.code 微信登录返回的code
|
||||
* @param {String} params.inviteCode 邀请码
|
||||
* @returns
|
||||
*/
|
||||
module.exports = async function (params = {}) {
|
||||
const schema = {
|
||||
code: 'string',
|
||||
inviteCode: {
|
||||
type: 'string',
|
||||
required: false
|
||||
}
|
||||
}
|
||||
this.middleware.validate(params, schema)
|
||||
const {
|
||||
code,
|
||||
inviteCode,
|
||||
// 内部参数,暂不暴露
|
||||
secureNetworkCache = false
|
||||
} = params
|
||||
const {
|
||||
appId
|
||||
} = this.getUniversalClientInfo()
|
||||
const weixinApi = initWeixin.call(this)
|
||||
const weixinPlatform = getWeixinPlatform.call(this)
|
||||
let apiName
|
||||
switch (weixinPlatform) {
|
||||
case 'mp':
|
||||
apiName = 'code2Session'
|
||||
break
|
||||
case 'app':
|
||||
case 'h5':
|
||||
case 'web':
|
||||
apiName = 'getOauthAccessToken'
|
||||
break
|
||||
default:
|
||||
throw new Error('Unsupported weixin platform')
|
||||
}
|
||||
let getWeixinAccountResult
|
||||
try {
|
||||
getWeixinAccountResult = await weixinApi[apiName](code)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
await this.middleware.uniIdLog({
|
||||
success: false,
|
||||
type: LOG_TYPE.LOGIN
|
||||
})
|
||||
throw {
|
||||
errCode: ERROR.GET_THIRD_PARTY_ACCOUNT_FAILED
|
||||
}
|
||||
}
|
||||
|
||||
const {
|
||||
openid,
|
||||
unionid,
|
||||
// 保存下面四个字段
|
||||
sessionKey, // 微信小程序用户sessionKey
|
||||
accessToken, // App端微信用户accessToken
|
||||
refreshToken, // App端微信用户refreshToken
|
||||
expired: accessTokenExpired // App端微信用户accessToken过期时间
|
||||
} = getWeixinAccountResult
|
||||
|
||||
if (secureNetworkCache) {
|
||||
if (weixinPlatform !== 'mp') {
|
||||
throw new Error('Unsupported weixin platform, expect mp-weixin')
|
||||
}
|
||||
await saveSecureNetworkCache.call(this, {
|
||||
code,
|
||||
openid,
|
||||
unionid,
|
||||
sessionKey
|
||||
})
|
||||
}
|
||||
|
||||
const {
|
||||
type,
|
||||
user
|
||||
} = await preUnifiedLogin.call(this, {
|
||||
user: {
|
||||
wx_openid: {
|
||||
[weixinPlatform]: openid
|
||||
},
|
||||
wx_unionid: unionid
|
||||
}
|
||||
})
|
||||
const extraData = {
|
||||
wx_openid: {
|
||||
[`${weixinPlatform}_${appId}`]: openid
|
||||
},
|
||||
wx_unionid: unionid
|
||||
}
|
||||
if (type === 'register' && weixinPlatform !== 'mp') {
|
||||
const {
|
||||
nickname,
|
||||
avatar
|
||||
} = await weixinApi.getUserInfo({
|
||||
accessToken,
|
||||
openid
|
||||
})
|
||||
|
||||
if (avatar) {
|
||||
// eslint-disable-next-line n/no-deprecated-api
|
||||
const avatarPath = url.parse(avatar).pathname
|
||||
const extName = avatarPath.indexOf('.') > -1 ? url.parse(avatar).pathname.split('.').pop() : 'jpg'
|
||||
const cloudPath = `user/avatar/${openid.slice(-8) + Date.now()}-avatar.${extName}`
|
||||
const getAvatarRes = await uniCloud.httpclient.request(avatar)
|
||||
if (getAvatarRes.status >= 400) {
|
||||
throw {
|
||||
errCode: ERROR.GET_THIRD_PARTY_USER_INFO_FAILED
|
||||
}
|
||||
}
|
||||
|
||||
const {
|
||||
fileID
|
||||
} = await uniCloud.uploadFile({
|
||||
cloudPath,
|
||||
fileContent: getAvatarRes.data
|
||||
})
|
||||
|
||||
extraData.avatar_file = {
|
||||
name: cloudPath,
|
||||
extname: extName,
|
||||
url: fileID
|
||||
}
|
||||
}
|
||||
|
||||
extraData.nickname = nickname
|
||||
}
|
||||
await saveWeixinUserKey.call(this, {
|
||||
openid,
|
||||
sessionKey,
|
||||
accessToken,
|
||||
refreshToken,
|
||||
accessTokenExpired
|
||||
})
|
||||
return postUnifiedLogin.call(this, {
|
||||
user,
|
||||
extraData: {
|
||||
...extraData,
|
||||
...generateWeixinCache.call(this, {
|
||||
openid,
|
||||
sessionKey, // 微信小程序用户sessionKey
|
||||
accessToken, // App端微信用户accessToken
|
||||
refreshToken, // App端微信用户refreshToken
|
||||
accessTokenExpired // App端微信用户accessToken过期时间
|
||||
})
|
||||
},
|
||||
isThirdParty: true,
|
||||
type,
|
||||
inviteCode
|
||||
})
|
||||
}
|
@ -0,0 +1,94 @@
|
||||
const {
|
||||
preLoginWithPassword,
|
||||
postLogin
|
||||
} = require('../../lib/utils/login')
|
||||
const {
|
||||
getNeedCaptcha,
|
||||
verifyCaptcha
|
||||
} = require('../../lib/utils/captcha')
|
||||
const {
|
||||
CAPTCHA_SCENE
|
||||
} = require('../../common/constants')
|
||||
const {
|
||||
ERROR
|
||||
} = require('../../common/error')
|
||||
|
||||
/**
|
||||
* 用户名密码登录
|
||||
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#login
|
||||
* @param {Object} params
|
||||
* @param {String} params.username 用户名
|
||||
* @param {String} params.mobile 手机号
|
||||
* @param {String} params.email 邮箱
|
||||
* @param {String} params.password 密码
|
||||
* @param {String} params.captcha 图形验证码
|
||||
* @returns
|
||||
*/
|
||||
module.exports = async function (params = {}) {
|
||||
const schema = {
|
||||
username: {
|
||||
required: false,
|
||||
type: 'username'
|
||||
},
|
||||
mobile: {
|
||||
required: false,
|
||||
type: 'mobile'
|
||||
},
|
||||
email: {
|
||||
required: false,
|
||||
type: 'email'
|
||||
},
|
||||
password: 'password',
|
||||
captcha: {
|
||||
required: false,
|
||||
type: 'string'
|
||||
}
|
||||
}
|
||||
this.middleware.validate(params, schema)
|
||||
const {
|
||||
username,
|
||||
mobile,
|
||||
email,
|
||||
password,
|
||||
captcha
|
||||
} = params
|
||||
if (!username && !mobile && !email) {
|
||||
throw {
|
||||
errCode: ERROR.INVALID_USERNAME
|
||||
}
|
||||
} else if (
|
||||
(username && email) ||
|
||||
(username && mobile) ||
|
||||
(email && mobile)
|
||||
) {
|
||||
throw {
|
||||
errCode: ERROR.INVALID_PARAM
|
||||
}
|
||||
}
|
||||
const needCaptcha = await getNeedCaptcha.call(this, {
|
||||
username,
|
||||
mobile,
|
||||
email
|
||||
})
|
||||
if (needCaptcha) {
|
||||
await verifyCaptcha.call(this, {
|
||||
captcha,
|
||||
scene: CAPTCHA_SCENE.LOGIN_BY_PWD
|
||||
})
|
||||
}
|
||||
const {
|
||||
user,
|
||||
extraData
|
||||
} = await preLoginWithPassword.call(this, {
|
||||
user: {
|
||||
username,
|
||||
mobile,
|
||||
email
|
||||
},
|
||||
password
|
||||
})
|
||||
return postLogin.call(this, {
|
||||
user,
|
||||
extraData
|
||||
})
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
logout: require('./logout')
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
const {
|
||||
logout
|
||||
} = require('../../lib/utils/logout')
|
||||
|
||||
/**
|
||||
* 用户退出登录
|
||||
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#logout
|
||||
* @returns
|
||||
*/
|
||||
module.exports = async function () {
|
||||
await logout.call(this)
|
||||
return {
|
||||
errCode: 0
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
const {
|
||||
isAuthorizeApproved
|
||||
} = require('./utils')
|
||||
const {
|
||||
dbCmd,
|
||||
userCollection
|
||||
} = require('../../common/constants')
|
||||
|
||||
/**
|
||||
* 授权用户登录应用
|
||||
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#authorize-app-login
|
||||
* @param {Object} params
|
||||
* @param {String} params.uid 用户id
|
||||
* @param {String} params.appId 授权的应用的AppId
|
||||
* @returns
|
||||
*/
|
||||
module.exports = async function (params = {}) {
|
||||
const schema = {
|
||||
uid: 'string',
|
||||
appId: 'string'
|
||||
}
|
||||
this.middleware.validate(params, schema)
|
||||
const {
|
||||
uid,
|
||||
appId
|
||||
} = params
|
||||
await isAuthorizeApproved({
|
||||
uid,
|
||||
appIdList: [appId]
|
||||
})
|
||||
await userCollection.doc(uid).update({
|
||||
dcloud_appid: dbCmd.push(appId)
|
||||
})
|
||||
return {
|
||||
errCode: 0
|
||||
}
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
module.exports = {
|
||||
authorizeAppLogin: require('./authorize-app-login'),
|
||||
removeAuthorizedApp: require('./remove-authorized-app'),
|
||||
setAuthorizedApp: require('./set-authorized-app')
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
const {
|
||||
dbCmd,
|
||||
userCollection
|
||||
} = require('../../common/constants')
|
||||
|
||||
/**
|
||||
* 移除用户登录授权
|
||||
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#remove-authorized-app
|
||||
* @param {Object} params
|
||||
* @param {String} params.uid 用户id
|
||||
* @param {String} params.appId 取消授权的应用的AppId
|
||||
* @returns
|
||||
*/
|
||||
module.exports = async function (params = {}) {
|
||||
const schema = {
|
||||
uid: 'string',
|
||||
appId: 'string'
|
||||
}
|
||||
this.middleware.validate(params, schema)
|
||||
const {
|
||||
uid,
|
||||
appId
|
||||
} = params
|
||||
await userCollection.doc(uid).update({
|
||||
dcloud_appid: dbCmd.pull(appId)
|
||||
})
|
||||
return {
|
||||
errCode: 0
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
const {
|
||||
isAuthorizeApproved
|
||||
} = require('./utils')
|
||||
const {
|
||||
userCollection
|
||||
} = require('../../common/constants')
|
||||
|
||||
/**
|
||||
* 设置用户允许登录的应用列表
|
||||
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#set-authorized-app
|
||||
* @param {Object} params
|
||||
* @param {String} params.uid 用户id
|
||||
* @param {Array} params.appIdList 允许登录的应用AppId列表
|
||||
* @returns
|
||||
*/
|
||||
module.exports = async function (params = {}) {
|
||||
const schema = {
|
||||
uid: 'string',
|
||||
appIdList: 'array<string>'
|
||||
}
|
||||
this.middleware.validate(params, schema)
|
||||
const {
|
||||
uid,
|
||||
appIdList
|
||||
} = params
|
||||
await isAuthorizeApproved({
|
||||
uid,
|
||||
appIdList
|
||||
})
|
||||
await userCollection.doc(uid).update({
|
||||
dcloud_appid: appIdList
|
||||
})
|
||||
return {
|
||||
errCode: 0
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
const {
|
||||
userCollection
|
||||
} = require('../../common/constants')
|
||||
const {
|
||||
ERROR
|
||||
} = require('../../common/error')
|
||||
const {
|
||||
findUser
|
||||
} = require('../../lib/utils/account')
|
||||
|
||||
async function isAuthorizeApproved ({
|
||||
uid,
|
||||
appIdList
|
||||
} = {}) {
|
||||
const getUserRes = await userCollection.doc(uid).get()
|
||||
const userRecord = getUserRes.data[0]
|
||||
if (!userRecord) {
|
||||
throw {
|
||||
errCode: ERROR.ACCOUNT_NOT_EXISTS
|
||||
}
|
||||
}
|
||||
const {
|
||||
userMatched
|
||||
} = await findUser({
|
||||
userQuery: userRecord,
|
||||
authorizedApp: appIdList
|
||||
})
|
||||
|
||||
if (userMatched.some(item => item._id !== uid)) {
|
||||
throw {
|
||||
errCode: ERROR.ACCOUNT_CONFLICT
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
isAuthorizeApproved
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
module.exports = {
|
||||
registerUser: require('./register-user'),
|
||||
registerAdmin: require('./register-admin'),
|
||||
registerUserByEmail: require('./register-user-by-email')
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user