2021-08-31 10:24:42 +00:00
|
|
|
/* 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');
|
2021-09-01 07:20:53 +00:00
|
|
|
const config = require('src/config/index.js');
|
|
|
|
const { jwt } = require('src/utils/pkgs.js');
|
2021-08-31 10:24:42 +00:00
|
|
|
const { copyObject, toNumber } = require('src/utils/index.js');
|
|
|
|
|
2021-09-01 07:20:53 +00:00
|
|
|
const { Success, InternalError, DataFormat, Forbidden, Unauthorized } = response.resp;
|
2021-08-31 10:24:42 +00:00
|
|
|
|
|
|
|
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();
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
2021-09-01 07:20:53 +00:00
|
|
|
/**
|
|
|
|
* @param {boolean=} allowExpired
|
|
|
|
* @return {import('koa').Middleware}
|
|
|
|
*/
|
|
|
|
controller.authorization = allowExpired => {
|
|
|
|
return async (ctx, next) => {
|
|
|
|
ctx.token = {};
|
|
|
|
/** @type {string} */
|
|
|
|
const token = ctx.get('authorization');
|
2021-08-31 10:24:42 +00:00
|
|
|
|
2021-09-01 07:20:53 +00:00
|
|
|
if (!token) ctx.err(Unauthorized);
|
2021-08-31 10:24:42 +00:00
|
|
|
|
2021-09-01 07:20:53 +00:00
|
|
|
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;
|
|
|
|
|
|
|
|
let decoded = {};
|
|
|
|
let expired = false;
|
|
|
|
|
|
|
|
try {
|
|
|
|
decoded = jwt.verify(strs[1], config.server.jwt_secret);
|
|
|
|
|
|
|
|
await joi
|
|
|
|
.object({
|
|
|
|
user_id: joi.string().required(),
|
|
|
|
})
|
|
|
|
.unknown()
|
|
|
|
.validateAsync(decoded);
|
|
|
|
} catch (err) {
|
|
|
|
debug(`jwt token verify fail: ${util.inspect(err, false, null)}`);
|
|
|
|
if (err instanceof jwt.TokenExpiredError) {
|
|
|
|
decoded = jwt.decode(ctx.token.origin);
|
|
|
|
expired = true;
|
|
|
|
} else {
|
|
|
|
throw err;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx.token.user_id = decoded.user_id;
|
|
|
|
ctx.token.sso = !!decoded.sso;
|
|
|
|
|
|
|
|
if (expired) ctx.err(Forbidden, response.codeMessage.CodeTokenExpired);
|
|
|
|
|
|
|
|
ctx.verified = true;
|
|
|
|
} catch (err) {
|
|
|
|
debug(`Token valid fail: ${util.inspect(err, false, null)}`);
|
|
|
|
if (err instanceof response.APIError) {
|
|
|
|
// 如果是過期的錯誤,判斷是否允許過期存取
|
|
|
|
// @ts-ignore
|
|
|
|
// eslint-disable-next-line
|
|
|
|
if (err._object?.object?.code === response.codeMessage.CodeTokenExpired.code) {
|
|
|
|
if (!!allowExpired) return next();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
throw err;
|
|
|
|
}
|
|
|
|
|
|
|
|
return next();
|
|
|
|
};
|
2021-08-31 10:24:42 +00:00
|
|
|
};
|