巴林闲侠
2 years ago
12 changed files with 325 additions and 22 deletions
@ -0,0 +1,135 @@ |
|||
'use strict'; |
|||
// ! OAUTH2.0 模式
|
|||
|
|||
const uuid = require('uuid'); |
|||
const moment = require('moment'); |
|||
|
|||
// 暂时定制予 vcmp 视频平台 app
|
|||
|
|||
async function apply (ctx) { |
|||
try { |
|||
const { models } = ctx.fs.dc; |
|||
const { body } = ctx.request; |
|||
const { authorization } = ctx.headers; |
|||
const { utils: { oauthParseAuthHeader, oauthParseBody } } = ctx.app.fs |
|||
await oauthParseBody(body, 'apply'); |
|||
const keySplit = await oauthParseAuthHeader(authorization); |
|||
|
|||
// TODO 删除此应用下的其他 token
|
|||
|
|||
// 记录token
|
|||
const userInfo = { |
|||
appKey: keySplit[0], |
|||
appSecret: keySplit[1] |
|||
} |
|||
const token = uuid.v4(); |
|||
const expired = moment().add(1, 'year'); |
|||
await models.UserToken.create({ |
|||
token: token, |
|||
userInfo: userInfo, |
|||
expired: expired.format('YYYY-MM-DD HH:mm:ss') |
|||
}); |
|||
|
|||
ctx.status = 200; |
|||
ctx.body = { |
|||
token: token, |
|||
expires: expired.toISOString() |
|||
}; |
|||
} catch (e) { |
|||
ctx.status = 400; |
|||
ctx.body = { |
|||
name: 'RequestError', |
|||
message: e.message |
|||
}; |
|||
} |
|||
} |
|||
|
|||
async function refresh (ctx) { |
|||
const transaction = await ctx.fs.dc.orm.transaction(); |
|||
try { |
|||
const { authorization } = ctx.headers; |
|||
const { body } = ctx.request; |
|||
const { models } = ctx.fs.dc; |
|||
const { utils: { oauthParseAuthHeader, oauthParseBody } } = ctx.app.fs |
|||
const keySplit = await oauthParseAuthHeader(authorization); |
|||
const $token = await oauthParseBody(body, 'refresh'); |
|||
|
|||
const oldToken = await models.UserToken.findOne({ |
|||
where: { |
|||
token: $token, |
|||
expired: { $gte: moment().format('YYYY-MM-DD HH:mm:ss') } |
|||
} |
|||
}); |
|||
|
|||
if ( |
|||
!oldToken |
|||
|| oldToken.userInfo.appKey != keySplit[0] |
|||
|| oldToken.userInfo.appSecret != keySplit[1] |
|||
) { |
|||
throw new Error('参数无效:正文token无效或已过期,请重新申请'); |
|||
} |
|||
|
|||
const token = uuid.v4(); |
|||
const expired = moment().add(1, 'year'); |
|||
|
|||
// 记录token
|
|||
const tokenMsg = { |
|||
token: token, |
|||
userInfo: oldToken.userInfo, |
|||
expired: expired.format('YYYY-MM-DD HH:mm:ss') |
|||
} |
|||
await models.UserToken.create(tokenMsg, { transaction }); |
|||
await ctx.redis.hmset(token, tokenMsg); |
|||
|
|||
// 移除旧的token
|
|||
await models.UserToken.destroy({ where: { token: $token }, transaction }); |
|||
await ctx.redisTools.hdelall($token); |
|||
|
|||
await transaction.commit(); |
|||
|
|||
ctx.status = 200; |
|||
ctx.body = { |
|||
token: token, |
|||
expires: expired.toISOString() |
|||
}; |
|||
} catch (e) { |
|||
await transaction.rollback(); |
|||
ctx.status = 400; |
|||
ctx.body = { |
|||
name: 'RequestError', |
|||
message: e.message |
|||
}; |
|||
} |
|||
} |
|||
|
|||
async function invalidate (ctx) { |
|||
const transaction = await ctx.fs.dc.orm.transaction(); |
|||
try { |
|||
const { models } = ctx.fs.dc; |
|||
const { body } = ctx.request; |
|||
const { authorization } = ctx.headers; |
|||
const { utils: { oauthParseAuthHeader, oauthParseBody } } = ctx.app.fs |
|||
const keySplit = await oauthParseAuthHeader(authorization); |
|||
const $token = await oauthParseBody(body, 'invalidate'); |
|||
|
|||
// 删除token
|
|||
await models.UserToken.destroy({ where: { token: $token } }); |
|||
await ctx.redisTools.hdelall($token); |
|||
|
|||
await transaction.commit(); |
|||
ctx.status = 204; |
|||
} catch (e) { |
|||
await transaction.rollback(); |
|||
ctx.status = 400; |
|||
ctx.body = { |
|||
name: 'RequestError', |
|||
message: e.message |
|||
}; |
|||
} |
|||
} |
|||
|
|||
module.exports = { |
|||
apply, |
|||
refresh, |
|||
invalidate |
|||
} |
@ -0,0 +1,14 @@ |
|||
'use strict'; |
|||
|
|||
const OAuth = require('../../controllers/auth/app'); |
|||
|
|||
module.exports = function (app, router, opts) { |
|||
app.fs.api.logAttr['POST/oauth2/token'] = { content: '获取访问令牌', visible: true }; |
|||
router.post('/oauth2/token', OAuth.apply); |
|||
|
|||
app.fs.api.logAttr['POST/oauth2/token/refresh'] = { content: '刷新访问令牌', visible: false }; |
|||
router.post('/oauth2/token/refresh', OAuth.refresh); |
|||
|
|||
app.fs.api.logAttr['POST/oauth2/token/invalidate'] = { content: '作废访问令牌', visible: false }; |
|||
router.post('/oauth2/token/invalidate', OAuth.invalidate); |
|||
}; |
@ -0,0 +1,68 @@ |
|||
'use strict'; |
|||
const request = require('superagent') |
|||
|
|||
class paasRequest { |
|||
constructor(root, { query = {} } = {}, option) { |
|||
this.root = root; |
|||
this.query = query |
|||
this.option = option |
|||
} |
|||
|
|||
#buildUrl = (url) => { |
|||
return `${this.root}/${url}`; |
|||
} |
|||
|
|||
#resultHandler = (resolve, reject) => { |
|||
return (err, res) => { |
|||
if (err) { |
|||
reject(err); |
|||
} else { |
|||
resolve(res[this.option.dataWord]); |
|||
} |
|||
}; |
|||
} |
|||
|
|||
get = (url, { query = {}, header = {} } = {}) => { |
|||
return new Promise((resolve, reject) => { |
|||
request.get(this.#buildUrl(url)).set(header).query(Object.assign(query, this.query)).end(this.#resultHandler(resolve, reject)); |
|||
}) |
|||
} |
|||
|
|||
post = (url, { data = {}, query = {}, header = {} } = {}) => { |
|||
return new Promise((resolve, reject) => { |
|||
request.post(this.#buildUrl(url)).set(header).query(Object.assign(query, this.query)).send(data).end(this.#resultHandler(resolve, reject)); |
|||
}) |
|||
} |
|||
|
|||
put = (url, { data = {}, header = {}, query = {}, } = {}) => { |
|||
return new Promise((resolve, reject) => { |
|||
request.put(this.#buildUrl(url)).set(header).query(Object.assign(query, this.query)).send(data).end(this.#resultHandler(resolve, reject)); |
|||
}) |
|||
} |
|||
|
|||
delete = (url, { header = {}, query = {} } = {}) => { |
|||
return new Promise((resolve, reject) => { |
|||
request.delete(this.#buildUrl(url)).set(header).query(Object.assign(query, this.query)).end(this.#resultHandler(resolve, reject)); |
|||
}) |
|||
} |
|||
} |
|||
|
|||
function factory (app, opts) { |
|||
if (opts.pssaRequest) { |
|||
try { |
|||
for (let r of opts.pssaRequest) { |
|||
if (r.name && r.root) { |
|||
console.log(`${r.name} request factory created.`); |
|||
app.fs[r.name] = new paasRequest(r.root, { ...(r.params || {}) }, { dataWord: r.dataWord || 'body' }) |
|||
} else { |
|||
throw 'opts.pssaRequest 参数错误!' |
|||
} |
|||
} |
|||
} catch (error) { |
|||
console.error(error) |
|||
process.exit(-1); |
|||
} |
|||
} |
|||
} |
|||
|
|||
module.exports = factory; |
@ -0,0 +1,16 @@ |
|||
'use strict'; |
|||
|
|||
const path = require('path'); |
|||
const fs = require('fs'); |
|||
|
|||
module.exports = async function (app, opts) { |
|||
fs.readdirSync(__dirname).forEach((filename) => { |
|||
if (!['index.js'].some(f => filename == f)) { |
|||
const utils = require(`./${filename}`)(app, opts) |
|||
app.fs.utils = { |
|||
...app.fs.utils, |
|||
...utils, |
|||
} |
|||
} |
|||
}); |
|||
}; |
@ -0,0 +1,62 @@ |
|||
module.exports = function (app, opts) { |
|||
async function oauthParseAuthHeader (auth) { |
|||
|
|||
if ('isVcmp') { |
|||
// 去 vcmp 检查 appkey 和 appsecret 是否正确
|
|||
try { |
|||
const existRes = await app.fs.vcmpRequest.get(`application/check`, { header: { authorization: auth } }) |
|||
} catch (error) { |
|||
throw new Error('应用已禁用或不存在!'); |
|||
} |
|||
} |
|||
|
|||
if (!auth) { |
|||
throw new Error('参数无效: 未包含Authorization头'); |
|||
} |
|||
|
|||
const authSplit = auth.split('Basic'); |
|||
if (authSplit.length != 2) { |
|||
throw new Error('参数无效: Authorization头格式无效,请检查是否包含了"Basic "'); |
|||
} |
|||
|
|||
const authCode = authSplit[1]; |
|||
const apikey = Buffer.from(authCode, 'base64').toString(); |
|||
|
|||
const keySplit = apikey.split(':'); |
|||
if (keySplit.length != 2) { |
|||
throw new Error('参数无效:请检查Authorization头内容是否经过正确Base64编码'); |
|||
} |
|||
|
|||
return keySplit; |
|||
} |
|||
|
|||
async function oauthParseBody (body, type) { |
|||
let checked = true, token = ''; |
|||
if (type == 'apply' && body['grant_type'] != 'client_credentials') { |
|||
checked = false; |
|||
} else if (type == 'refresh') { |
|||
if (body['grant_type'] != 'refresh_token' || body['token'] == null) { |
|||
checked = false; |
|||
} else { |
|||
token = body['token']; |
|||
} |
|||
} else if (type == 'invalidate') { |
|||
if (body['token'] == null) { |
|||
checked = false; |
|||
} else { |
|||
token = body['token']; |
|||
} |
|||
} |
|||
|
|||
if (!checked) { |
|||
throw new Error('参数无效:请求正文中未包含正确的信息'); |
|||
} |
|||
|
|||
return token; |
|||
} |
|||
|
|||
return { |
|||
oauthParseAuthHeader, |
|||
oauthParseBody |
|||
} |
|||
} |
@ -1,15 +0,0 @@ |
|||
'use strict'; |
|||
const proxy = require('koa-proxy'); |
|||
const convert = require('koa-convert'); |
|||
|
|||
module.exports = { |
|||
entry: function (app, router, opts) { |
|||
app.use(convert(proxy({ |
|||
host: opts.host, |
|||
match: opts.match, |
|||
map: function (path) { |
|||
return path.replace(opts.match, ''); |
|||
} |
|||
}))); |
|||
} |
|||
}; |
Loading…
Reference in new issue