首次完整推送,
V:1.20240808.006
This commit is contained in:
@ -0,0 +1,16 @@
|
||||
const AlipayBase = require('../alipayBase')
|
||||
const protocols = require('./protocols')
|
||||
module.exports = class Auth extends AlipayBase {
|
||||
constructor (options) {
|
||||
super(options)
|
||||
this._protocols = protocols
|
||||
}
|
||||
|
||||
async code2Session (code) {
|
||||
const result = await this._exec('alipay.system.oauth.token', {
|
||||
grantType: 'authorization_code',
|
||||
code
|
||||
})
|
||||
return result
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
module.exports = {
|
||||
code2Session: {
|
||||
// args (fromArgs) {
|
||||
// return fromArgs
|
||||
// },
|
||||
returnValue: {
|
||||
openid: 'userId'
|
||||
}
|
||||
}
|
||||
}
|
231
uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/third-party/alipay/alipayBase.js
vendored
Normal file
231
uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/third-party/alipay/alipayBase.js
vendored
Normal file
@ -0,0 +1,231 @@
|
||||
const {
|
||||
camel2snakeJson,
|
||||
snake2camelJson,
|
||||
getOffsetDate,
|
||||
getFullTimeStr
|
||||
} = require('../../../common/utils')
|
||||
const crypto = require('crypto')
|
||||
|
||||
const ALIPAY_ALGORITHM_MAPPING = {
|
||||
RSA: 'RSA-SHA1',
|
||||
RSA2: 'RSA-SHA256'
|
||||
}
|
||||
|
||||
module.exports = class AlipayBase {
|
||||
constructor (options = {}) {
|
||||
if (!options.appId) throw new Error('appId required')
|
||||
if (!options.privateKey) throw new Error('privateKey required')
|
||||
const defaultOptions = {
|
||||
gateway: 'https://openapi.alipay.com/gateway.do',
|
||||
timeout: 5000,
|
||||
charset: 'utf-8',
|
||||
version: '1.0',
|
||||
signType: 'RSA2',
|
||||
timeOffset: -new Date().getTimezoneOffset() / 60,
|
||||
keyType: 'PKCS8'
|
||||
}
|
||||
|
||||
if (options.sandbox) {
|
||||
options.gateway = 'https://openapi.alipaydev.com/gateway.do'
|
||||
}
|
||||
|
||||
this.options = Object.assign({}, defaultOptions, options)
|
||||
|
||||
const privateKeyType =
|
||||
this.options.keyType === 'PKCS8' ? 'PRIVATE KEY' : 'RSA PRIVATE KEY'
|
||||
|
||||
this.options.privateKey = this._formatKey(
|
||||
this.options.privateKey,
|
||||
privateKeyType
|
||||
)
|
||||
if (this.options.alipayPublicKey) {
|
||||
this.options.alipayPublicKey = this._formatKey(
|
||||
this.options.alipayPublicKey,
|
||||
'PUBLIC KEY'
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
_formatKey (key, type) {
|
||||
return `-----BEGIN ${type}-----\n${key}\n-----END ${type}-----`
|
||||
}
|
||||
|
||||
_formatUrl (url, params) {
|
||||
let requestUrl = url
|
||||
// 需要放在 url 中的参数列表
|
||||
const urlArgs = [
|
||||
'app_id',
|
||||
'method',
|
||||
'format',
|
||||
'charset',
|
||||
'sign_type',
|
||||
'sign',
|
||||
'timestamp',
|
||||
'version',
|
||||
'notify_url',
|
||||
'return_url',
|
||||
'auth_token',
|
||||
'app_auth_token'
|
||||
]
|
||||
|
||||
for (const key in params) {
|
||||
if (urlArgs.indexOf(key) > -1) {
|
||||
const val = encodeURIComponent(params[key])
|
||||
requestUrl = `${requestUrl}${requestUrl.includes('?') ? '&' : '?'
|
||||
}${key}=${val}`
|
||||
// 删除 postData 中对应的数据
|
||||
delete params[key]
|
||||
}
|
||||
}
|
||||
|
||||
return { execParams: params, url: requestUrl }
|
||||
}
|
||||
|
||||
_getSign (method, params) {
|
||||
const bizContent = params.bizContent || null
|
||||
delete params.bizContent
|
||||
|
||||
const signParams = Object.assign({
|
||||
method,
|
||||
appId: this.options.appId,
|
||||
charset: this.options.charset,
|
||||
version: this.options.version,
|
||||
signType: this.options.signType,
|
||||
timestamp: getFullTimeStr(getOffsetDate(this.options.timeOffset))
|
||||
}, params)
|
||||
|
||||
if (bizContent) {
|
||||
signParams.bizContent = JSON.stringify(camel2snakeJson(bizContent))
|
||||
}
|
||||
|
||||
// params key 驼峰转下划线
|
||||
const decamelizeParams = camel2snakeJson(signParams)
|
||||
|
||||
// 排序
|
||||
const signStr = Object.keys(decamelizeParams)
|
||||
.sort()
|
||||
.map((key) => {
|
||||
let data = decamelizeParams[key]
|
||||
if (Array.prototype.toString.call(data) !== '[object String]') {
|
||||
data = JSON.stringify(data)
|
||||
}
|
||||
return `${key}=${data}`
|
||||
})
|
||||
.join('&')
|
||||
|
||||
// 计算签名
|
||||
const sign = crypto
|
||||
.createSign(ALIPAY_ALGORITHM_MAPPING[this.options.signType])
|
||||
.update(signStr, 'utf8')
|
||||
.sign(this.options.privateKey, 'base64')
|
||||
|
||||
return Object.assign(decamelizeParams, { sign })
|
||||
}
|
||||
|
||||
async _exec (method, params = {}, option = {}) {
|
||||
// 计算签名
|
||||
const signData = this._getSign(method, params)
|
||||
const { url, execParams } = this._formatUrl(this.options.gateway, signData)
|
||||
const { status, data } = await uniCloud.httpclient.request(url, {
|
||||
method: 'POST',
|
||||
data: execParams,
|
||||
// 按 text 返回(为了验签)
|
||||
dataType: 'text',
|
||||
timeout: this.options.timeout
|
||||
})
|
||||
if (status !== 200) throw new Error('request fail')
|
||||
/**
|
||||
* 示例响应格式
|
||||
* {"alipay_trade_precreate_response":
|
||||
* {"code": "10000","msg": "Success","out_trade_no": "111111","qr_code": "https:\/\/"},
|
||||
* "sign": "abcde="
|
||||
* }
|
||||
* 或者
|
||||
* {"error_response":
|
||||
* {"code":"40002","msg":"Invalid Arguments","sub_code":"isv.code-invalid","sub_msg":"授权码code无效"},
|
||||
* }
|
||||
*/
|
||||
const result = JSON.parse(data)
|
||||
const responseKey = `${method.replace(/\./g, '_')}_response`
|
||||
const response = result[responseKey]
|
||||
const errorResponse = result.error_response
|
||||
if (response) {
|
||||
// 按字符串验签
|
||||
const validateSuccess = option.validateSign ? this._checkResponseSign(data, responseKey) : true
|
||||
if (validateSuccess) {
|
||||
if (!response.code || response.code === '10000') {
|
||||
const errCode = 0
|
||||
const errMsg = response.msg || ''
|
||||
return {
|
||||
errCode,
|
||||
errMsg,
|
||||
...snake2camelJson(response)
|
||||
}
|
||||
}
|
||||
const msg = response.sub_code ? `${response.sub_code} ${response.sub_msg}` : `${response.msg || 'unkonwn error'}`
|
||||
throw new Error(msg)
|
||||
} else {
|
||||
throw new Error('check sign error')
|
||||
}
|
||||
} else if (errorResponse) {
|
||||
throw new Error(errorResponse.sub_msg || errorResponse.msg || 'request fail')
|
||||
}
|
||||
|
||||
throw new Error('request fail')
|
||||
}
|
||||
|
||||
_checkResponseSign (signStr, responseKey) {
|
||||
if (!this.options.alipayPublicKey || this.options.alipayPublicKey === '') {
|
||||
console.warn('options.alipayPublicKey is empty')
|
||||
// 支付宝公钥不存在时不做验签
|
||||
return true
|
||||
}
|
||||
|
||||
// 带验签的参数不存在时返回失败
|
||||
if (!signStr) { return false }
|
||||
|
||||
// 根据服务端返回的结果截取需要验签的目标字符串
|
||||
const validateStr = this._getSignStr(signStr, responseKey)
|
||||
// 服务端返回的签名
|
||||
const serverSign = JSON.parse(signStr).sign
|
||||
|
||||
// 参数存在,并且是正常的结果(不包含 sub_code)时才验签
|
||||
const verifier = crypto.createVerify(ALIPAY_ALGORITHM_MAPPING[this.options.signType])
|
||||
verifier.update(validateStr, 'utf8')
|
||||
return verifier.verify(this.options.alipayPublicKey, serverSign, 'base64')
|
||||
}
|
||||
|
||||
_getSignStr (originStr, responseKey) {
|
||||
// 待签名的字符串
|
||||
let validateStr = originStr.trim()
|
||||
// 找到 xxx_response 开始的位置
|
||||
const startIndex = originStr.indexOf(`${responseKey}"`)
|
||||
// 找到最后一个 “"sign"” 字符串的位置(避免)
|
||||
const lastIndex = originStr.lastIndexOf('"sign"')
|
||||
|
||||
/**
|
||||
* 删除 xxx_response 及之前的字符串
|
||||
* 假设原始字符串为
|
||||
* {"xxx_response":{"code":"10000"},"sign":"jumSvxTKwn24G5sAIN"}
|
||||
* 删除后变为
|
||||
* :{"code":"10000"},"sign":"jumSvxTKwn24G5sAIN"}
|
||||
*/
|
||||
validateStr = validateStr.substr(startIndex + responseKey.length + 1)
|
||||
|
||||
/**
|
||||
* 删除最后一个 "sign" 及之后的字符串
|
||||
* 删除后变为
|
||||
* :{"code":"10000"},
|
||||
* {} 之间就是待验签的字符串
|
||||
*/
|
||||
validateStr = validateStr.substr(0, lastIndex)
|
||||
|
||||
// 删除第一个 { 之前的任何字符
|
||||
validateStr = validateStr.replace(/^[^{]*{/g, '{')
|
||||
|
||||
// 删除最后一个 } 之后的任何字符
|
||||
validateStr = validateStr.replace(/\}([^}]*)$/g, '}')
|
||||
|
||||
return validateStr
|
||||
}
|
||||
}
|
@ -0,0 +1,79 @@
|
||||
const rsaPublicKeyPem = require('../rsa-public-key-pem')
|
||||
const {
|
||||
jwtVerify
|
||||
} = require('../../../npm/index')
|
||||
let authKeysCache = null
|
||||
|
||||
module.exports = class Auth {
|
||||
constructor (options) {
|
||||
this.options = Object.assign({
|
||||
baseUrl: 'https://appleid.apple.com',
|
||||
timeout: 10000
|
||||
}, options)
|
||||
}
|
||||
|
||||
async _fetch (url, options) {
|
||||
const { baseUrl } = this.options
|
||||
return uniCloud.httpclient.request(baseUrl + url, options)
|
||||
}
|
||||
|
||||
async verifyIdentityToken (identityToken) {
|
||||
// 解密出kid,拿取key
|
||||
const jwtHeader = identityToken.split('.')[0]
|
||||
const { kid } = JSON.parse(Buffer.from(jwtHeader, 'base64').toString())
|
||||
let authKeys
|
||||
if (authKeysCache) {
|
||||
authKeys = authKeysCache
|
||||
} else {
|
||||
authKeys = await this.getAuthKeys()
|
||||
authKeysCache = authKeys
|
||||
}
|
||||
const usedKey = authKeys.find(item => item.kid === kid)
|
||||
|
||||
/**
|
||||
* identityToken 格式
|
||||
*
|
||||
* {
|
||||
* iss: 'https://appleid.apple.com',
|
||||
* aud: 'io.dcloud.hellouniapp',
|
||||
* exp: 1610626724,
|
||||
* iat: 1610540324,
|
||||
* sub: '000628.30119d332d9b45a3be4a297f9391fd5c.0403',
|
||||
* c_hash: 'oFfgewoG36cJX00KUbj45A',
|
||||
* email: 'x2awmap99s@privaterelay.appleid.com',
|
||||
* email_verified: 'true',
|
||||
* is_private_email: 'true',
|
||||
* auth_time: 1610540324,
|
||||
* nonce_supported: true
|
||||
* }
|
||||
*/
|
||||
const payload = jwtVerify(
|
||||
identityToken,
|
||||
rsaPublicKeyPem(usedKey.n, usedKey.e),
|
||||
{
|
||||
algorithms: usedKey.alg
|
||||
}
|
||||
)
|
||||
|
||||
if (payload.iss !== 'https://appleid.apple.com' || payload.aud !== this.options.bundleId) {
|
||||
throw new Error('Invalid identity token')
|
||||
}
|
||||
|
||||
return {
|
||||
openid: payload.sub,
|
||||
email: payload.email,
|
||||
emailVerified: payload.email_verified === 'true',
|
||||
isPrivateEmail: payload.is_private_email === 'true'
|
||||
}
|
||||
}
|
||||
|
||||
async getAuthKeys () {
|
||||
const { status, data } = await this._fetch('/auth/keys', {
|
||||
method: 'GET',
|
||||
dataType: 'json',
|
||||
timeout: this.options.timeout
|
||||
})
|
||||
if (status !== 200) throw new Error('request https://appleid.apple.com/auth/keys fail')
|
||||
return data.keys
|
||||
}
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
// http://stackoverflow.com/questions/18835132/xml-to-pem-in-node-js
|
||||
/* eslint-disable camelcase */
|
||||
function rsaPublicKeyPem (modulus_b64, exponent_b64) {
|
||||
const modulus = Buffer.from(modulus_b64, 'base64')
|
||||
const exponent = Buffer.from(exponent_b64, 'base64')
|
||||
|
||||
let modulus_hex = modulus.toString('hex')
|
||||
let exponent_hex = exponent.toString('hex')
|
||||
|
||||
modulus_hex = prepadSigned(modulus_hex)
|
||||
exponent_hex = prepadSigned(exponent_hex)
|
||||
|
||||
const modlen = modulus_hex.length / 2
|
||||
const explen = exponent_hex.length / 2
|
||||
|
||||
const encoded_modlen = encodeLengthHex(modlen)
|
||||
const encoded_explen = encodeLengthHex(explen)
|
||||
const encoded_pubkey = '30' +
|
||||
encodeLengthHex(
|
||||
modlen +
|
||||
explen +
|
||||
encoded_modlen.length / 2 +
|
||||
encoded_explen.length / 2 + 2
|
||||
) +
|
||||
'02' + encoded_modlen + modulus_hex +
|
||||
'02' + encoded_explen + exponent_hex
|
||||
|
||||
const der_b64 = Buffer.from(encoded_pubkey, 'hex').toString('base64')
|
||||
|
||||
const pem = '-----BEGIN RSA PUBLIC KEY-----\n' +
|
||||
der_b64.match(/.{1,64}/g).join('\n') +
|
||||
'\n-----END RSA PUBLIC KEY-----\n'
|
||||
|
||||
return pem
|
||||
}
|
||||
|
||||
function prepadSigned (hexStr) {
|
||||
const msb = hexStr[0]
|
||||
if (msb < '0' || msb > '7') {
|
||||
return '00' + hexStr
|
||||
} else {
|
||||
return hexStr
|
||||
}
|
||||
}
|
||||
|
||||
function toHex (number) {
|
||||
const nstr = number.toString(16)
|
||||
if (nstr.length % 2) return '0' + nstr
|
||||
return nstr
|
||||
}
|
||||
|
||||
// encode ASN.1 DER length field
|
||||
// if <=127, short form
|
||||
// if >=128, long form
|
||||
function encodeLengthHex (n) {
|
||||
if (n <= 127) return toHex(n)
|
||||
else {
|
||||
const n_hex = toHex(n)
|
||||
const length_of_length_byte = 128 + n_hex.length / 2 // 0x80+numbytes
|
||||
return toHex(length_of_length_byte) + n_hex
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = rsaPublicKeyPem
|
36
uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/third-party/index.js
vendored
Normal file
36
uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/third-party/index.js
vendored
Normal file
@ -0,0 +1,36 @@
|
||||
const WxAccount = require('./weixin/account/index')
|
||||
const QQAccount = require('./qq/account/index')
|
||||
const AliAccount = require('./alipay/account/index')
|
||||
const AppleAccount = require('./apple/account/index')
|
||||
|
||||
const createApi = require('./share/create-api')
|
||||
|
||||
module.exports = {
|
||||
initWeixin: function () {
|
||||
const oauthConfig = this.configUtils.getOauthConfig({ provider: 'weixin' })
|
||||
return createApi(WxAccount, {
|
||||
appId: oauthConfig.appid,
|
||||
secret: oauthConfig.appsecret
|
||||
})
|
||||
},
|
||||
initQQ: function () {
|
||||
const oauthConfig = this.configUtils.getOauthConfig({ provider: 'qq' })
|
||||
return createApi(QQAccount, {
|
||||
appId: oauthConfig.appid,
|
||||
secret: oauthConfig.appsecret
|
||||
})
|
||||
},
|
||||
initAlipay: function () {
|
||||
const oauthConfig = this.configUtils.getOauthConfig({ provider: 'alipay' })
|
||||
return createApi(AliAccount, {
|
||||
appId: oauthConfig.appid,
|
||||
privateKey: oauthConfig.privateKey
|
||||
})
|
||||
},
|
||||
initApple: function () {
|
||||
const oauthConfig = this.configUtils.getOauthConfig({ provider: 'apple' })
|
||||
return createApi(AppleAccount, {
|
||||
bundleId: oauthConfig.bundleId
|
||||
})
|
||||
}
|
||||
}
|
97
uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/third-party/qq/account/index.js
vendored
Normal file
97
uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/third-party/qq/account/index.js
vendored
Normal file
@ -0,0 +1,97 @@
|
||||
const {
|
||||
UniCloudError
|
||||
} = require('../../../../common/error')
|
||||
const {
|
||||
resolveUrl
|
||||
} = require('../../../../common/utils')
|
||||
const {
|
||||
callQQOpenApi
|
||||
} = require('../normalize')
|
||||
|
||||
module.exports = class Auth {
|
||||
constructor (options) {
|
||||
this.options = Object.assign({
|
||||
baseUrl: 'https://graph.qq.com',
|
||||
timeout: 5000
|
||||
}, options)
|
||||
}
|
||||
|
||||
async _requestQQOpenapi ({ name, url, data, options }) {
|
||||
const defaultOptions = {
|
||||
method: 'GET',
|
||||
dataType: 'json',
|
||||
dataAsQueryString: true,
|
||||
timeout: this.options.timeout
|
||||
}
|
||||
const result = await callQQOpenApi({
|
||||
name: `auth.${name}`,
|
||||
url: resolveUrl(this.options.baseUrl, url),
|
||||
data,
|
||||
options,
|
||||
defaultOptions
|
||||
})
|
||||
return result
|
||||
}
|
||||
|
||||
async getUserInfo ({
|
||||
accessToken,
|
||||
openid
|
||||
} = {}) {
|
||||
const url = '/user/get_user_info'
|
||||
const result = await this._requestQQOpenapi({
|
||||
name: 'getUserInfo',
|
||||
url,
|
||||
data: {
|
||||
oauthConsumerKey: this.options.appId,
|
||||
accessToken,
|
||||
openid
|
||||
}
|
||||
})
|
||||
return {
|
||||
nickname: result.nickname,
|
||||
avatar: result.figureurl_qq_1
|
||||
}
|
||||
}
|
||||
|
||||
async getOpenidByToken ({
|
||||
accessToken
|
||||
} = {}) {
|
||||
const url = '/oauth2.0/me'
|
||||
const result = await this._requestQQOpenapi({
|
||||
name: 'getOpenidByToken',
|
||||
url,
|
||||
data: {
|
||||
accessToken,
|
||||
unionid: 1,
|
||||
fmt: 'json'
|
||||
}
|
||||
})
|
||||
if (result.clientId !== this.options.appId) {
|
||||
throw new UniCloudError({
|
||||
code: 'APPID_NOT_MATCH',
|
||||
message: 'appid not match'
|
||||
})
|
||||
}
|
||||
return {
|
||||
openid: result.openid,
|
||||
unionid: result.unionid
|
||||
}
|
||||
}
|
||||
|
||||
async code2Session ({
|
||||
code
|
||||
} = {}) {
|
||||
const url = 'https://api.q.qq.com/sns/jscode2session'
|
||||
const result = await this._requestQQOpenapi({
|
||||
name: 'getOpenidByToken',
|
||||
url,
|
||||
data: {
|
||||
grant_type: 'authorization_code',
|
||||
appid: this.options.appId,
|
||||
secret: this.options.secret,
|
||||
js_code: code
|
||||
}
|
||||
})
|
||||
return result
|
||||
}
|
||||
}
|
85
uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/third-party/qq/normalize.js
vendored
Normal file
85
uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/third-party/qq/normalize.js
vendored
Normal file
@ -0,0 +1,85 @@
|
||||
const {
|
||||
UniCloudError
|
||||
} = require('../../../common/error')
|
||||
const {
|
||||
camel2snakeJson,
|
||||
snake2camelJson
|
||||
} = require('../../../common/utils')
|
||||
|
||||
function generateApiResult (apiName, data) {
|
||||
if (data.ret || data.error) {
|
||||
// 这三种都是qq的错误码规范
|
||||
const code = data.ret || data.error || data.errcode || -2
|
||||
const message = data.msg || data.error_description || data.errmsg || `${apiName} fail`
|
||||
throw new UniCloudError({
|
||||
code,
|
||||
message
|
||||
})
|
||||
} else {
|
||||
delete data.ret
|
||||
delete data.msg
|
||||
delete data.error
|
||||
delete data.error_description
|
||||
delete data.errcode
|
||||
delete data.errmsg
|
||||
return {
|
||||
...data,
|
||||
errMsg: `${apiName} ok`,
|
||||
errCode: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function nomalizeError (apiName, error) {
|
||||
throw new UniCloudError({
|
||||
code: error.code || -2,
|
||||
message: error.message || `${apiName} fail`
|
||||
})
|
||||
}
|
||||
|
||||
async function callQQOpenApi ({
|
||||
name,
|
||||
url,
|
||||
data,
|
||||
options,
|
||||
defaultOptions
|
||||
}) {
|
||||
options = Object.assign({}, defaultOptions, options, { data: camel2snakeJson(Object.assign({}, data)) })
|
||||
let result
|
||||
try {
|
||||
result = await uniCloud.httpclient.request(url, options)
|
||||
} catch (e) {
|
||||
return nomalizeError(name, e)
|
||||
}
|
||||
let resData = result.data
|
||||
const contentType = result.headers['content-type']
|
||||
if (
|
||||
Buffer.isBuffer(resData) &&
|
||||
(contentType.indexOf('text/plain') === 0 ||
|
||||
contentType.indexOf('application/json') === 0)
|
||||
) {
|
||||
try {
|
||||
resData = JSON.parse(resData.toString())
|
||||
} catch (e) {
|
||||
resData = resData.toString()
|
||||
}
|
||||
} else if (Buffer.isBuffer(resData)) {
|
||||
resData = {
|
||||
buffer: resData,
|
||||
contentType
|
||||
}
|
||||
}
|
||||
return snake2camelJson(
|
||||
generateApiResult(
|
||||
name,
|
||||
resData || {
|
||||
errCode: -2,
|
||||
errMsg: 'Request failed'
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
callQQOpenApi
|
||||
}
|
73
uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/third-party/share/create-api.js
vendored
Normal file
73
uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/third-party/share/create-api.js
vendored
Normal file
@ -0,0 +1,73 @@
|
||||
const {
|
||||
isFn,
|
||||
isPlainObject
|
||||
} = require('../../../common/utils')
|
||||
|
||||
// 注意:不进行递归处理
|
||||
function parseParams (params = {}, rule) {
|
||||
if (!rule || !params) {
|
||||
return params
|
||||
}
|
||||
const internalKeys = ['_pre', '_purify', '_post']
|
||||
// 转换之前的处理
|
||||
if (rule._pre) {
|
||||
params = rule._pre(params)
|
||||
}
|
||||
// 净化参数
|
||||
let purify = { shouldDelete: new Set([]) }
|
||||
if (rule._purify) {
|
||||
const _purify = rule._purify
|
||||
for (const purifyKey in _purify) {
|
||||
_purify[purifyKey] = new Set(_purify[purifyKey])
|
||||
}
|
||||
purify = Object.assign(purify, _purify)
|
||||
}
|
||||
if (isPlainObject(rule)) {
|
||||
for (const key in rule) {
|
||||
const parser = rule[key]
|
||||
if (isFn(parser) && internalKeys.indexOf(key) === -1) {
|
||||
params[key] = parser(params)
|
||||
} else if (typeof parser === 'string' && internalKeys.indexOf(key) === -1) {
|
||||
// 直接转换属性名称的删除旧属性名
|
||||
params[key] = params[parser]
|
||||
purify.shouldDelete.add(parser)
|
||||
}
|
||||
}
|
||||
} else if (isFn(rule)) {
|
||||
params = rule(params)
|
||||
}
|
||||
|
||||
if (purify.shouldDelete) {
|
||||
for (const item of purify.shouldDelete) {
|
||||
delete params[item]
|
||||
}
|
||||
}
|
||||
|
||||
// 转换之后的处理
|
||||
if (rule._post) {
|
||||
params = rule._post(params)
|
||||
}
|
||||
|
||||
return params
|
||||
}
|
||||
|
||||
function createApi (ApiClass, options) {
|
||||
const apiInstance = new ApiClass(options)
|
||||
return new Proxy(apiInstance, {
|
||||
get: function (obj, prop) {
|
||||
if (typeof obj[prop] === 'function' && prop.indexOf('_') !== 0 && obj._protocols && obj._protocols[prop]) {
|
||||
const protocol = obj._protocols[prop]
|
||||
return async function (params) {
|
||||
params = parseParams(params, protocol.args)
|
||||
let result = await obj[prop](params)
|
||||
result = parseParams(result, protocol.returnValue)
|
||||
return result
|
||||
}
|
||||
} else {
|
||||
return obj[prop]
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = createApi
|
111
uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/third-party/weixin/account/index.js
vendored
Normal file
111
uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/third-party/weixin/account/index.js
vendored
Normal file
@ -0,0 +1,111 @@
|
||||
const {
|
||||
callWxOpenApi,
|
||||
buildUrl
|
||||
} = require('../normalize')
|
||||
|
||||
module.exports = class Auth {
|
||||
constructor (options) {
|
||||
this.options = Object.assign({
|
||||
baseUrl: 'https://api.weixin.qq.com',
|
||||
timeout: 5000
|
||||
}, options)
|
||||
}
|
||||
|
||||
async _requestWxOpenapi ({ name, url, data, options }) {
|
||||
const defaultOptions = {
|
||||
method: 'GET',
|
||||
dataType: 'json',
|
||||
dataAsQueryString: true,
|
||||
timeout: this.options.timeout
|
||||
}
|
||||
const result = await callWxOpenApi({
|
||||
name: `auth.${name}`,
|
||||
url: `${this.options.baseUrl}${buildUrl(url, data)}`,
|
||||
data,
|
||||
options,
|
||||
defaultOptions
|
||||
})
|
||||
return result
|
||||
}
|
||||
|
||||
async code2Session (code) {
|
||||
const url = '/sns/jscode2session'
|
||||
const result = await this._requestWxOpenapi({
|
||||
name: 'code2Session',
|
||||
url,
|
||||
data: {
|
||||
grant_type: 'authorization_code',
|
||||
appid: this.options.appId,
|
||||
secret: this.options.secret,
|
||||
js_code: code
|
||||
}
|
||||
})
|
||||
return result
|
||||
}
|
||||
|
||||
async getOauthAccessToken (code) {
|
||||
const url = '/sns/oauth2/access_token'
|
||||
const result = await this._requestWxOpenapi({
|
||||
name: 'getOauthAccessToken',
|
||||
url,
|
||||
data: {
|
||||
grant_type: 'authorization_code',
|
||||
appid: this.options.appId,
|
||||
secret: this.options.secret,
|
||||
code
|
||||
}
|
||||
})
|
||||
if (result.expiresIn) {
|
||||
result.expired = Date.now() + result.expiresIn * 1000
|
||||
// delete result.expiresIn
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
async getUserInfo ({
|
||||
accessToken,
|
||||
openid
|
||||
} = {}) {
|
||||
const url = '/sns/userinfo'
|
||||
const {
|
||||
nickname,
|
||||
headimgurl: avatar
|
||||
} = await this._requestWxOpenapi({
|
||||
name: 'getUserInfo',
|
||||
url,
|
||||
data: {
|
||||
accessToken,
|
||||
openid,
|
||||
appid: this.options.appId,
|
||||
secret: this.options.secret,
|
||||
scope: 'snsapi_userinfo'
|
||||
}
|
||||
})
|
||||
return {
|
||||
nickname,
|
||||
avatar
|
||||
}
|
||||
}
|
||||
|
||||
async getPhoneNumber (accessToken, code) {
|
||||
const url = `/wxa/business/getuserphonenumber?access_token=${accessToken}`
|
||||
const { phoneInfo } = await this._requestWxOpenapi({
|
||||
name: 'getPhoneNumber',
|
||||
url,
|
||||
data: {
|
||||
code
|
||||
},
|
||||
options: {
|
||||
method: 'POST',
|
||||
dataAsQueryString: false,
|
||||
headers: {
|
||||
'content-type': 'application/json'
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
purePhoneNumber: phoneInfo.purePhoneNumber
|
||||
}
|
||||
}
|
||||
}
|
95
uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/third-party/weixin/normalize.js
vendored
Normal file
95
uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/third-party/weixin/normalize.js
vendored
Normal file
@ -0,0 +1,95 @@
|
||||
const {
|
||||
UniCloudError
|
||||
} = require('../../../common/error')
|
||||
const {
|
||||
camel2snakeJson, snake2camelJson
|
||||
} = require('../../../common/utils')
|
||||
|
||||
function generateApiResult (apiName, data) {
|
||||
if (data.errcode) {
|
||||
throw new UniCloudError({
|
||||
code: data.errcode || -2,
|
||||
message: data.errmsg || `${apiName} fail`
|
||||
})
|
||||
} else {
|
||||
delete data.errcode
|
||||
delete data.errmsg
|
||||
return {
|
||||
...data,
|
||||
errMsg: `${apiName} ok`,
|
||||
errCode: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function nomalizeError (apiName, error) {
|
||||
throw new UniCloudError({
|
||||
code: error.code || -2,
|
||||
message: error.message || `${apiName} fail`
|
||||
})
|
||||
}
|
||||
|
||||
// 微信openapi接口接收蛇形(snake case)参数返回蛇形参数,这里进行转化,如果是formdata里面的参数需要在对应api实现时就转为蛇形
|
||||
async function callWxOpenApi ({
|
||||
name,
|
||||
url,
|
||||
data,
|
||||
options,
|
||||
defaultOptions
|
||||
}) {
|
||||
let result = {}
|
||||
// 获取二维码的接口wxacode.get和wxacode.getUnlimited不可以传入access_token(可能有其他接口也不可以),否则会返回data format error
|
||||
const dataCopy = camel2snakeJson(Object.assign({}, data))
|
||||
if (dataCopy && dataCopy.access_token) {
|
||||
delete dataCopy.access_token
|
||||
}
|
||||
try {
|
||||
options = Object.assign({}, defaultOptions, options, { data: dataCopy })
|
||||
result = await uniCloud.httpclient.request(url, options)
|
||||
} catch (e) {
|
||||
return nomalizeError(name, e)
|
||||
}
|
||||
|
||||
// 有几个接口成功返回buffer失败返回json,对这些接口统一成返回buffer,然后分别解析
|
||||
let resData = result.data
|
||||
const contentType = result.headers['content-type']
|
||||
if (
|
||||
Buffer.isBuffer(resData) &&
|
||||
(contentType.indexOf('text/plain') === 0 ||
|
||||
contentType.indexOf('application/json') === 0)
|
||||
) {
|
||||
try {
|
||||
resData = JSON.parse(resData.toString())
|
||||
} catch (e) {
|
||||
resData = resData.toString()
|
||||
}
|
||||
} else if (Buffer.isBuffer(resData)) {
|
||||
resData = {
|
||||
buffer: resData,
|
||||
contentType
|
||||
}
|
||||
}
|
||||
return snake2camelJson(
|
||||
generateApiResult(
|
||||
name,
|
||||
resData || {
|
||||
errCode: -2,
|
||||
errMsg: 'Request failed'
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
function buildUrl (url, data) {
|
||||
let query = ''
|
||||
if (data && data.accessToken) {
|
||||
const divider = url.indexOf('?') > -1 ? '&' : '?'
|
||||
query = `${divider}access_token=${data.accessToken}`
|
||||
}
|
||||
return `${url}${query}`
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
callWxOpenApi,
|
||||
buildUrl
|
||||
}
|
87
uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/third-party/weixin/utils.js
vendored
Normal file
87
uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/third-party/weixin/utils.js
vendored
Normal file
@ -0,0 +1,87 @@
|
||||
const crypto = require('crypto')
|
||||
const {
|
||||
isPlainObject
|
||||
} = require('../../../common/utils')
|
||||
|
||||
// 退款通知解密key=md5(key)
|
||||
function decryptData (encryptedData, key, iv = '') {
|
||||
// 解密
|
||||
const decipher = crypto.createDecipheriv('aes-128-cbc', key, iv)
|
||||
// 设置自动 padding 为 true,删除填充补位
|
||||
decipher.setAutoPadding(true)
|
||||
let decoded = decipher.update(encryptedData, 'base64', 'utf8')
|
||||
decoded += decipher.final('utf8')
|
||||
return decoded
|
||||
}
|
||||
|
||||
function md5 (str, encoding = 'utf8') {
|
||||
return crypto
|
||||
.createHash('md5')
|
||||
.update(str, encoding)
|
||||
.digest('hex')
|
||||
}
|
||||
|
||||
function sha256 (str, key, encoding = 'utf8') {
|
||||
return crypto
|
||||
.createHmac('sha256', key)
|
||||
.update(str, encoding)
|
||||
.digest('hex')
|
||||
}
|
||||
|
||||
function getSignStr (obj) {
|
||||
return Object.keys(obj)
|
||||
.filter(key => key !== 'sign' && obj[key] !== undefined && obj[key] !== '')
|
||||
.sort()
|
||||
.map(key => key + '=' + obj[key])
|
||||
.join('&')
|
||||
}
|
||||
|
||||
function getNonceStr (length = 16) {
|
||||
let str = ''
|
||||
while (str.length < length) {
|
||||
str += Math.random().toString(32).substring(2)
|
||||
}
|
||||
return str.substring(0, length)
|
||||
}
|
||||
|
||||
// 简易版Object转XML,只可在微信支付时使用,不支持嵌套
|
||||
function buildXML (obj, rootName = 'xml') {
|
||||
const content = Object.keys(obj).map(item => {
|
||||
if (isPlainObject(obj[item])) {
|
||||
return `<${item}><![CDATA[${JSON.stringify(obj[item])}]]></${item}>`
|
||||
} else {
|
||||
return `<${item}><![CDATA[${obj[item]}]]></${item}>`
|
||||
}
|
||||
})
|
||||
return `<${rootName}>${content.join('')}</${rootName}>`
|
||||
}
|
||||
|
||||
function isXML (str) {
|
||||
const reg = /^(<\?xml.*\?>)?(\r?\n)*<xml>(.|\r?\n)*<\/xml>$/i
|
||||
return reg.test(str.trim())
|
||||
};
|
||||
|
||||
// 简易版XML转Object,只可在微信支付时使用,不支持嵌套
|
||||
function parseXML (xml) {
|
||||
const xmlReg = /<(?:xml|root).*?>([\s|\S]*)<\/(?:xml|root)>/
|
||||
const str = xmlReg.exec(xml)[1]
|
||||
const obj = {}
|
||||
const nodeReg = /<(.*?)>(?:<!\[CDATA\[){0,1}(.*?)(?:\]\]>){0,1}<\/.*?>/g
|
||||
let matches = null
|
||||
// eslint-disable-next-line no-cond-assign
|
||||
while ((matches = nodeReg.exec(str))) {
|
||||
obj[matches[1]] = matches[2]
|
||||
}
|
||||
return obj
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
decryptData,
|
||||
md5,
|
||||
sha256,
|
||||
getSignStr,
|
||||
getNonceStr,
|
||||
buildXML,
|
||||
parseXML,
|
||||
isXML
|
||||
}
|
Reference in New Issue
Block a user