keycloak-demo/controllers/common/index.js

167 lines
5.0 KiB
JavaScript
Raw Normal View History

2021-08-31 10:24:42 +00:00
/* eslint-disable no-bitwise */
2021-09-01 12:46:41 +00:00
const debug = require('debug')('ctrl:common')
const util = require('util')
const joi = require('joi')
const response = require('src/utils/response/index.js')
const config = require('src/config/index.js')
const { jwt } = require('src/utils/pkgs.js')
2021-09-01 13:15:26 +00:00
const sso = require('src/utils/sso/index.js')
const { copyObject } = require('src/utils/index.js')
const { get: getCacheInstance } = require('src/utils/cache.js')
2021-08-31 10:24:42 +00:00
2021-09-01 12:46:41 +00:00
const { Success, InternalError, DataFormat, Forbidden, Unauthorized } = response.resp
2021-08-31 10:24:42 +00:00
2021-09-01 12:46:41 +00:00
const controller = {}
module.exports = controller
2021-08-31 10:24:42 +00:00
/**
* api reponse function
* @param {import('src/utils/response/index.js').respObject} resp
* @param {string|Object} body
*/
2021-09-01 12:46:41 +00:00
function responseFunc (resp, body) {
2021-08-31 10:24:42 +00:00
/** @type {import('src/utils/response/index.js').respObject} */
2021-09-01 12:46:41 +00:00
const copy = copyObject(response.checkStruct(resp) ? resp : Success)
2021-08-31 10:24:42 +00:00
2021-09-01 12:46:41 +00:00
if (typeof body === 'string') copy.object.message = body
else if (typeof body === 'object') copy.object = body
2021-08-31 10:24:42 +00:00
// @ts-ignore
2021-09-01 12:46:41 +00:00
if (!('obj' in this)) this.obj = {}
this.obj.status = copy.status
this.obj.object = copy.object
2021-08-31 10:24:42 +00:00
}
/**
* api error response function
* @param {import('src/utils/response/index.js').respObject} resp
* @param {import('src/utils/response/index.js').codeMessage=} code
*/
2021-09-01 12:46:41 +00:00
function responseError (resp, code) {
2021-08-31 10:24:42 +00:00
/** @type {import('src/utils/response/index.js').respObject} */
2021-09-01 12:46:41 +00:00
const copy = copyObject(response.checkStruct(resp) ? resp : InternalError)
2021-08-31 10:24:42 +00:00
if (code && typeof code === 'object' && 'message' in code && 'code' in code) {
2021-09-01 12:46:41 +00:00
copy.object = code
2021-08-31 10:24:42 +00:00
}
2021-09-01 12:46:41 +00:00
const err = new response.APIError(copy.object.message, copy)
throw err
2021-08-31 10:24:42 +00:00
}
controller.apiHandler = () => async (ctx, next) => {
2021-09-01 12:46:41 +00:00
ctx.obj = {}
ctx.token = {}
2021-08-31 10:24:42 +00:00
2021-09-01 12:46:41 +00:00
ctx.resp = responseFunc.bind(ctx)
ctx.err = responseError
2021-08-31 10:24:42 +00:00
2021-09-01 12:46:41 +00:00
ctx.getBody = key => (ctx.request.body || {})[key]
ctx.getFile = key => (ctx.request.files || {})[key]
2021-08-31 10:24:42 +00:00
// run next
try {
2021-09-01 12:46:41 +00:00
await next()
2021-08-31 10:24:42 +00:00
} catch (err) {
2021-09-01 12:46:41 +00:00
debug(`Get API Throw Error: ${util.inspect(err, false, null)}`)
2021-08-31 10:24:42 +00:00
// debug(err.stack);
if (!(err instanceof response.APIError)) {
2021-09-01 12:46:41 +00:00
ctx.resp(InternalError)
2021-08-31 10:24:42 +00:00
} else {
2021-09-01 12:46:41 +00:00
ctx.obj = err.object
2021-08-31 10:24:42 +00:00
}
if (process.env.NODE_ENV !== 'production') {
2021-09-01 12:46:41 +00:00
ctx.obj.object.errorStack = err.stack
ctx.obj.object.errorMessage = err.message
2021-08-31 10:24:42 +00:00
}
}
if (Object.keys(ctx.obj).length > 0) {
2021-09-01 12:46:41 +00:00
ctx.status = ctx.obj.status
ctx.body = ctx.obj.object
2021-08-31 10:24:42 +00:00
}
2021-09-01 12:46:41 +00:00
}
2021-08-31 10:24:42 +00:00
/**
* data validate middleware
* @param {{query?: any, header?: any, body?: any}} schema body,query and header is joi.Schema
*/
controller.validate = schema => {
2021-09-01 12:46:41 +00:00
if (typeof schema !== 'object') responseError(InternalError)
const v = {}
if ('body' in schema) v.body = joi.isSchema(schema.body) ? schema.body : joi.object(schema.body).unknown()
if ('header' in schema) v.header = joi.isSchema(schema.header) ? schema.header : joi.object(schema.header).unknown()
if ('query' in schema) v.query = joi.isSchema(schema.query) ? schema.query : joi.object(schema.query).unknown()
2021-08-31 10:24:42 +00:00
return async (ctx, next) => {
try {
2021-09-01 12:46:41 +00:00
await joi.object(v).unknown().validateAsync({ query: ctx.query, header: ctx.headers, body: ctx.request.body })
2021-08-31 10:24:42 +00:00
} catch (err) {
2021-09-01 12:46:41 +00:00
debug(`data validate error: ${util.inspect(err, false, null)}`)
responseError(DataFormat)
2021-08-31 10:24:42 +00:00
}
2021-09-01 12:46:41 +00:00
return next()
}
}
2021-08-31 10:24:42 +00:00
2021-09-01 07:20:53 +00:00
/**
* @param {boolean=} allowExpired
* @return {import('koa').Middleware}
*/
2021-09-01 13:15:26 +00:00
controller.authorization = () => {
2021-09-01 07:20:53 +00:00
return async (ctx, next) => {
2021-09-01 12:46:41 +00:00
ctx.token = {}
2021-09-01 07:20:53 +00:00
/** @type {string} */
2021-09-01 12:46:41 +00:00
const token = ctx.get('authorization')
2021-08-31 10:24:42 +00:00
2021-09-01 12:46:41 +00:00
if (!token) ctx.err(Unauthorized)
2021-08-31 10:24:42 +00:00
2021-09-01 07:20:53 +00:00
try {
2021-09-01 12:46:41 +00:00
const strs = token.split(/\s/)
debug(`Get Header: ${token}`)
2021-09-01 07:20:53 +00:00
if (strs.length !== 2 || !/^bearer$/i.test(strs[0])) ctx.err(Unauthorized, response.codeMessage.CodeTokenInvalid);
2021-09-01 12:46:41 +00:00
[, ctx.token.origin] = strs
2021-09-01 07:20:53 +00:00
2021-09-01 13:15:26 +00:00
const decoded = {}
2021-09-01 07:20:53 +00:00
try {
2021-09-01 13:15:26 +00:00
// 可以考慮這邊做個cache 多久之內存取不會到keycloak驗證
let userInfo = await sso.getUserInfo(ctx.token.origin)
if (!userInfo) {
// try refresh
const cache = getCacheInstance()
const refreshToken = cache.get(ctx.token.origin)
if (!refreshToken) throw new Error('no cache data')
const token = await sso.refreshToken(refreshToken)
// set new cache
cache.set(token.access_token, token.refresh_token, false)
ctx.token.origin = token.access_token
userInfo = await sso.getUserInfo(token.access_token)
if (!userInfo) throw new Error('get user info fail')
ctx.set('x-new-token', ctx.token.origin)
2021-09-01 07:20:53 +00:00
}
2021-09-01 13:15:26 +00:00
Object.assign(decoded, userInfo)
} catch (err) {
debug(`user info get fail ::: ${util.inspect(err, false, null)}`)
ctx.err(Unauthorized)
2021-09-01 07:20:53 +00:00
}
2021-09-01 13:15:26 +00:00
ctx.token.user_id = decoded.username
ctx.token.sso = true
2021-09-01 07:20:53 +00:00
2021-09-01 13:15:26 +00:00
ctx.token.info = decoded
2021-09-01 07:20:53 +00:00
2021-09-01 12:46:41 +00:00
ctx.verified = true
2021-09-01 07:20:53 +00:00
} catch (err) {
2021-09-01 12:46:41 +00:00
debug(`Token valid fail: ${util.inspect(err, false, null)}`)
throw err
2021-09-01 07:20:53 +00:00
}
2021-09-01 12:46:41 +00:00
return next()
}
}