/* eslint-disable no-bitwise */ 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') const sso = require('src/utils/sso/index.js') const { copyObject } = require('src/utils/index.js') const { get: getCacheInstance } = require('src/utils/cache.js') const { Success, InternalError, DataFormat, Forbidden, Unauthorized } = response.resp const controller = {} module.exports = controller /** * api reponse function * @param {import('src/utils/response/index.js').respObject} resp * @param {string|Object} body */ function responseFunc (resp, body) { /** @type {import('src/utils/response/index.js').respObject} */ const copy = copyObject(response.checkStruct(resp) ? resp : Success) if (typeof body === 'string') copy.object.message = body else if (typeof body === 'object') copy.object = body // @ts-ignore if (!('obj' in this)) this.obj = {} this.obj.status = copy.status this.obj.object = copy.object } /** * api error response function * @param {import('src/utils/response/index.js').respObject} resp * @param {import('src/utils/response/index.js').codeMessage=} code */ function responseError (resp, code) { /** @type {import('src/utils/response/index.js').respObject} */ const copy = copyObject(response.checkStruct(resp) ? resp : InternalError) if (code && typeof code === 'object' && 'message' in code && 'code' in code) { copy.object = code } const err = new response.APIError(copy.object.message, copy) throw err } controller.apiHandler = () => async (ctx, next) => { ctx.obj = {} ctx.token = {} ctx.resp = responseFunc.bind(ctx) ctx.err = responseError ctx.getBody = key => (ctx.request.body || {})[key] ctx.getFile = key => (ctx.request.files || {})[key] // run next try { await next() } catch (err) { debug(`Get API Throw Error: ${util.inspect(err, false, null)}`) // debug(err.stack); if (!(err instanceof response.APIError)) { ctx.resp(InternalError) } else { ctx.obj = err.object } if (process.env.NODE_ENV !== 'production') { ctx.obj.object.errorStack = err.stack ctx.obj.object.errorMessage = err.message } } if (Object.keys(ctx.obj).length > 0) { ctx.status = ctx.obj.status ctx.body = ctx.obj.object } } /** * data validate middleware * @param {{query?: any, header?: any, body?: any}} schema body,query and header is joi.Schema */ controller.validate = schema => { 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() return async (ctx, next) => { try { await joi.object(v).unknown().validateAsync({ query: ctx.query, header: ctx.headers, body: ctx.request.body }) } catch (err) { debug(`data validate error: ${util.inspect(err, false, null)}`) responseError(DataFormat) } return next() } } /** * @param {boolean=} allowExpired * @return {import('koa').Middleware} */ controller.authorization = () => { return async (ctx, next) => { ctx.token = {} /** @type {string} */ const token = ctx.get('authorization') if (!token) ctx.err(Unauthorized) try { const strs = token.split(/\s/) debug(`Get Header: ${token}`) if (strs.length !== 2 || !/^bearer$/i.test(strs[0])) ctx.err(Unauthorized, response.codeMessage.CodeTokenInvalid); [, ctx.token.origin] = strs const decoded = {} try { // 可以考慮這邊做個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) } Object.assign(decoded, userInfo) } catch (err) { debug(`user info get fail ::: ${util.inspect(err, false, null)}`) ctx.err(Unauthorized) } ctx.token.user_id = decoded.username ctx.token.sso = true ctx.token.info = decoded ctx.verified = true } catch (err) { debug(`Token valid fail: ${util.inspect(err, false, null)}`) throw err } return next() } }