首次完整推送,

V:1.20240808.006
This commit is contained in:
fm453
2024-08-13 18:32:37 +08:00
parent 15be3e9373
commit c62d15b288
939 changed files with 111777 additions and 0 deletions

View File

@ -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
}

View File

@ -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
}

View File

@ -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

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}