首次完整推送,
V:1.20240808.006
This commit is contained in:
@ -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
|
||||
}
|
Reference in New Issue
Block a user