update
This commit is contained in:
@@ -1,41 +1,40 @@
|
||||
const { resp } = require("src/utils/response/index.js");
|
||||
const { get: getCacheInstance } = require("src/utils/cache.js");
|
||||
const sso = require("src/utils/sso/index.js");
|
||||
const { OPENID_EXPIRE } = require("src/constants/index.js");
|
||||
const uuid = require("uuid");
|
||||
const url = require("url");
|
||||
const { resp } = require('src/utils/response/index.js')
|
||||
const { get: getCacheInstance } = require('src/utils/cache.js')
|
||||
const sso = require('src/utils/sso/index.js')
|
||||
const uuid = require('uuid')
|
||||
const url = require('url')
|
||||
|
||||
const controller = {};
|
||||
module.exports = controller;
|
||||
const controller = {}
|
||||
module.exports = controller
|
||||
|
||||
controller.loginSSO = () => async (ctx) => {
|
||||
const { back_url: backURL } = ctx.query;
|
||||
const { back_url: backURL } = ctx.query
|
||||
|
||||
const state = uuid.v4();
|
||||
const state = uuid.v4()
|
||||
|
||||
const authURL = sso.getAuthURL(state);
|
||||
const authURL = sso.getAuthURL(state)
|
||||
|
||||
// store back url to cache
|
||||
const cacheKey = `login-${state}`;
|
||||
const cache = getCacheInstance();
|
||||
const cacheKey = `login-${state}`
|
||||
const cache = getCacheInstance()
|
||||
|
||||
cache.set(cacheKey, JSON.stringify({ back_url: backURL }), true);
|
||||
cache.set(cacheKey, JSON.stringify({ back_url: backURL }), true)
|
||||
|
||||
const u = new url.URL(authURL);
|
||||
const u = new url.URL(authURL)
|
||||
|
||||
ctx.resp(resp.Success, { url: u.toString() });
|
||||
};
|
||||
ctx.resp(resp.Success, { url: u.toString() })
|
||||
}
|
||||
|
||||
controller.logout = () => async (ctx) => {
|
||||
let link = "";
|
||||
let link = ''
|
||||
|
||||
if (ctx.token.sso) {
|
||||
link = sso.getLogoutURL();
|
||||
link = sso.getLogoutURL()
|
||||
}
|
||||
|
||||
ctx.resp(resp.Success, { url: link });
|
||||
};
|
||||
ctx.resp(resp.Success, { url: link })
|
||||
}
|
||||
|
||||
controller.getInfo = () => async (ctx) => {
|
||||
ctx.resp(resp.Success, {});
|
||||
};
|
||||
ctx.resp(resp.Success, {})
|
||||
}
|
||||
|
||||
+74
-74
@@ -1,33 +1,33 @@
|
||||
/* 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 { copyObject, toNumber } = require('src/utils/index.js');
|
||||
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 { copyObject, toNumber } = require('src/utils/index.js')
|
||||
|
||||
const { Success, InternalError, DataFormat, Forbidden, Unauthorized } = response.resp;
|
||||
const { Success, InternalError, DataFormat, Forbidden, Unauthorized } = response.resp
|
||||
|
||||
const controller = {};
|
||||
module.exports = controller;
|
||||
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) {
|
||||
function responseFunc (resp, body) {
|
||||
/** @type {import('src/utils/response/index.js').respObject} */
|
||||
const copy = copyObject(response.checkStruct(resp) ? resp : Success);
|
||||
const copy = copyObject(response.checkStruct(resp) ? resp : Success)
|
||||
|
||||
if (typeof body === 'string') copy.object.message = body;
|
||||
else if (typeof body === 'object') copy.object = body;
|
||||
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;
|
||||
if (!('obj' in this)) this.obj = {}
|
||||
this.obj.status = copy.status
|
||||
this.obj.object = copy.object
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -35,134 +35,134 @@ function responseFunc(resp, body) {
|
||||
* @param {import('src/utils/response/index.js').respObject} resp
|
||||
* @param {import('src/utils/response/index.js').codeMessage=} code
|
||||
*/
|
||||
function responseError(resp, code) {
|
||||
function responseError (resp, code) {
|
||||
/** @type {import('src/utils/response/index.js').respObject} */
|
||||
const copy = copyObject(response.checkStruct(resp) ? resp : InternalError);
|
||||
const copy = copyObject(response.checkStruct(resp) ? resp : InternalError)
|
||||
|
||||
if (code && typeof code === 'object' && 'message' in code && 'code' in code) {
|
||||
copy.object = code;
|
||||
copy.object = code
|
||||
}
|
||||
|
||||
const err = new response.APIError(copy.object.message, copy);
|
||||
throw err;
|
||||
const err = new response.APIError(copy.object.message, copy)
|
||||
throw err
|
||||
}
|
||||
|
||||
controller.apiHandler = () => async (ctx, next) => {
|
||||
ctx.obj = {};
|
||||
ctx.token = {};
|
||||
ctx.obj = {}
|
||||
ctx.token = {}
|
||||
|
||||
ctx.resp = responseFunc.bind(ctx);
|
||||
ctx.err = responseError;
|
||||
ctx.resp = responseFunc.bind(ctx)
|
||||
ctx.err = responseError
|
||||
|
||||
ctx.getBody = key => (ctx.request.body || {})[key];
|
||||
ctx.getFile = key => (ctx.request.files || {})[key];
|
||||
ctx.getBody = key => (ctx.request.body || {})[key]
|
||||
ctx.getFile = key => (ctx.request.files || {})[key]
|
||||
|
||||
// run next
|
||||
try {
|
||||
await next();
|
||||
await next()
|
||||
} catch (err) {
|
||||
debug(`Get API Throw Error: ${util.inspect(err, false, null)}`);
|
||||
debug(`Get API Throw Error: ${util.inspect(err, false, null)}`)
|
||||
// debug(err.stack);
|
||||
if (!(err instanceof response.APIError)) {
|
||||
ctx.resp(InternalError);
|
||||
ctx.resp(InternalError)
|
||||
} else {
|
||||
ctx.obj = err.object;
|
||||
ctx.obj = err.object
|
||||
}
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
ctx.obj.object.errorStack = err.stack;
|
||||
ctx.obj.object.errorMessage = err.message;
|
||||
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;
|
||||
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();
|
||||
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 });
|
||||
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);
|
||||
debug(`data validate error: ${util.inspect(err, false, null)}`)
|
||||
responseError(DataFormat)
|
||||
}
|
||||
return next();
|
||||
};
|
||||
};
|
||||
return next()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {boolean=} allowExpired
|
||||
* @return {import('koa').Middleware}
|
||||
*/
|
||||
controller.authorization = allowExpired => {
|
||||
controller.authorization = allowExpired => {
|
||||
return async (ctx, next) => {
|
||||
ctx.token = {};
|
||||
ctx.token = {}
|
||||
/** @type {string} */
|
||||
const token = ctx.get('authorization');
|
||||
const token = ctx.get('authorization')
|
||||
|
||||
if (!token) ctx.err(Unauthorized);
|
||||
if (!token) ctx.err(Unauthorized)
|
||||
|
||||
try {
|
||||
const strs = token.split(/\s/);
|
||||
debug(`Get Header: ${token}`);
|
||||
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;
|
||||
[, ctx.token.origin] = strs
|
||||
|
||||
let decoded = {};
|
||||
let expired = false;
|
||||
let decoded = {}
|
||||
let expired = false
|
||||
|
||||
try {
|
||||
decoded = jwt.verify(strs[1], config.server.jwt_secret);
|
||||
decoded = jwt.verify(strs[1], config.server.jwt_secret)
|
||||
|
||||
await joi
|
||||
.object({
|
||||
user_id: joi.string().required(),
|
||||
user_id: joi.string().required()
|
||||
})
|
||||
.unknown()
|
||||
.validateAsync(decoded);
|
||||
.validateAsync(decoded)
|
||||
} catch (err) {
|
||||
debug(`jwt token verify fail: ${util.inspect(err, false, null)}`);
|
||||
debug(`jwt token verify fail: ${util.inspect(err, false, null)}`)
|
||||
if (err instanceof jwt.TokenExpiredError) {
|
||||
decoded = jwt.decode(ctx.token.origin);
|
||||
expired = true;
|
||||
decoded = jwt.decode(ctx.token.origin)
|
||||
expired = true
|
||||
} else {
|
||||
throw err;
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
ctx.token.user_id = decoded.user_id;
|
||||
ctx.token.sso = !!decoded.sso;
|
||||
ctx.token.user_id = decoded.user_id
|
||||
ctx.token.sso = !!decoded.sso
|
||||
|
||||
if (expired) ctx.err(Forbidden, response.codeMessage.CodeTokenExpired);
|
||||
if (expired) ctx.err(Forbidden, response.codeMessage.CodeTokenExpired)
|
||||
|
||||
ctx.verified = true;
|
||||
ctx.verified = true
|
||||
} catch (err) {
|
||||
debug(`Token valid fail: ${util.inspect(err, false, null)}`);
|
||||
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();
|
||||
if (allowExpired) return next()
|
||||
}
|
||||
}
|
||||
throw err;
|
||||
throw err
|
||||
}
|
||||
|
||||
return next();
|
||||
};
|
||||
};
|
||||
return next()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const controller = {};
|
||||
module.exports = controller;
|
||||
const controller = {}
|
||||
module.exports = controller
|
||||
|
||||
controller.healthCheck = async ctx => {
|
||||
ctx.body = 'ok';
|
||||
ctx.status = 200;
|
||||
};
|
||||
ctx.body = 'ok'
|
||||
ctx.status = 200
|
||||
}
|
||||
|
||||
+40
-50
@@ -1,80 +1,70 @@
|
||||
const debug = require("debug")("ctrl:common");
|
||||
const util = require("util");
|
||||
const url = require("url");
|
||||
const sso = require("src/utils/sso/index.js");
|
||||
const { get: getCacheInstance } = require("src/utils/cache.js");
|
||||
const { codeMessage, APIError } = require("src/utils/response/index.js");
|
||||
const config = require("src/config/index.js");
|
||||
const { jwt } = require("src/utils/pkgs.js");
|
||||
const debug = require('debug')('ctrl:common')
|
||||
const util = require('util')
|
||||
const url = require('url')
|
||||
const sso = require('src/utils/sso/index.js')
|
||||
const { get: getCacheInstance } = require('src/utils/cache.js')
|
||||
const { codeMessage, APIError } = require('src/utils/response/index.js')
|
||||
const config = require('src/config/index.js')
|
||||
const { jwt } = require('src/utils/pkgs.js')
|
||||
|
||||
const controller = {};
|
||||
module.exports = controller;
|
||||
const controller = {}
|
||||
module.exports = controller
|
||||
|
||||
controller.verifyCode = () => async (ctx) => {
|
||||
const { code, session_state: sessionState, state } = ctx.query;
|
||||
const { code, session_state: sessionState, state } = ctx.query
|
||||
|
||||
// logout flow redirect tot frontend
|
||||
if (state === "logout") {
|
||||
ctx.redirect(config.server.frontend_url);
|
||||
return;
|
||||
if (state === 'logout') {
|
||||
ctx.redirect(config.server.frontend_url)
|
||||
return
|
||||
}
|
||||
|
||||
// get back url from redis
|
||||
const cacheKey = `login-${state}`;
|
||||
const cache = getCacheInstance();
|
||||
const cacheKey = `login-${state}`
|
||||
const cache = getCacheInstance()
|
||||
|
||||
const data = cache.get(cacheKey);
|
||||
if (!data) ctx.throw("get login cache fail");
|
||||
const stateObj = JSON.parse(data);
|
||||
const { back_url: backURL } = stateObj;
|
||||
if (!backURL) ctx.throw("cache data missing");
|
||||
const data = cache.get(cacheKey)
|
||||
if (!data) ctx.throw('get login cache fail')
|
||||
const stateObj = JSON.parse(data)
|
||||
const { back_url: backURL } = stateObj
|
||||
if (!backURL) ctx.throw('cache data missing')
|
||||
|
||||
const u = new url.URL(backURL);
|
||||
const u = new url.URL(backURL)
|
||||
|
||||
try {
|
||||
const token = await sso.getToken(code, sessionState);
|
||||
const token = await sso.getToken(code, sessionState)
|
||||
|
||||
// generate jwt token
|
||||
const jwtToken = jwt.sign(
|
||||
{
|
||||
user_id: token.user_id,
|
||||
sso: true,
|
||||
},
|
||||
config.server.jwt_secret,
|
||||
{
|
||||
expiresIn: config.server.jwt_expire,
|
||||
issuer: "lawsnote",
|
||||
}
|
||||
);
|
||||
// set accessToken/refreshToken cache
|
||||
cache.set(token.access_token, token.refresh_token, false)
|
||||
|
||||
u.searchParams.append(
|
||||
"success",
|
||||
Buffer.from(JSON.stringify({ token: jwtToken })).toString("base64")
|
||||
);
|
||||
'success',
|
||||
Buffer.from(JSON.stringify({ token: token.access_token })).toString('base64')
|
||||
)
|
||||
|
||||
try {
|
||||
cache.del(cacheKey);
|
||||
cache.del(cacheKey)
|
||||
} catch (err) {
|
||||
debug(`delete cache fail: ${util.inspect(err, false, null)}`);
|
||||
debug(`delete cache fail: ${util.inspect(err, false, null)}`)
|
||||
}
|
||||
} catch (err) {
|
||||
debug(`openid verify fail: ${util.inspect(err, false, null)}`);
|
||||
debug(`openid verify fail: ${util.inspect(err, false, null)}`)
|
||||
|
||||
/** @type {object} */
|
||||
const errObj = { ...codeMessage.CodeInternalError };
|
||||
const errObj = { ...codeMessage.CodeInternalError }
|
||||
|
||||
if (err instanceof APIError) {
|
||||
// @ts-ignore
|
||||
Object.assign(errObj, err.object.object);
|
||||
Object.assign(errObj, err.object.object)
|
||||
}
|
||||
|
||||
errObj.errorStack = err.stack;
|
||||
errObj.errorMessage = err.message;
|
||||
errObj.errorStack = err.stack
|
||||
errObj.errorMessage = err.message
|
||||
u.searchParams.append(
|
||||
"error",
|
||||
Buffer.from(JSON.stringify(errObj)).toString("base64")
|
||||
);
|
||||
'error',
|
||||
Buffer.from(JSON.stringify(errObj)).toString('base64')
|
||||
)
|
||||
}
|
||||
|
||||
ctx.redirect(u.toString());
|
||||
};
|
||||
ctx.redirect(u.toString())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user