[feat] Update route format
This commit is contained in:
parent
b1e9c5e62a
commit
9174b540fd
@ -4,6 +4,7 @@ const constants = {
|
||||
OPENID_EXPIRE: 300, // 5min
|
||||
INTERNAL_REGULATION_CACHE_TTL: 1800, // 30min
|
||||
REPORT_CACHE_TTL: 600, // 10 min
|
||||
ALLOW_GROUP_ROLE: ['Ironman3']
|
||||
};
|
||||
|
||||
module.exports = constants;
|
||||
|
@ -24,3 +24,17 @@ controller.loginSSO = () => async ctx => {
|
||||
|
||||
ctx.resp(resp.Success, { url: u.toString() });
|
||||
};
|
||||
|
||||
controller.logout = () => async ctx => {
|
||||
let link = '';
|
||||
|
||||
if (ctx.token.sso) {
|
||||
link = sso.getLogoutURL();
|
||||
}
|
||||
|
||||
ctx.resp(resp.Success, { url: link });
|
||||
};
|
||||
|
||||
controller.getInfo = () => async ctx => {
|
||||
ctx.resp(resp.Success, {});
|
||||
};
|
@ -3,9 +3,11 @@ 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 } = response.resp;
|
||||
const { Success, InternalError, DataFormat, Forbidden, Unauthorized } = response.resp;
|
||||
|
||||
const controller = {};
|
||||
module.exports = controller;
|
||||
@ -101,13 +103,66 @@ controller.validate = schema => {
|
||||
};
|
||||
};
|
||||
|
||||
controller.getAppVersion = () => async (ctx, next) => {
|
||||
// appVersion Format x.y.z (major.minor.patch)
|
||||
const appVersion = ctx.get('x-app-version');
|
||||
const appBuildNumber = toNumber(ctx.get('x-app-buildnumber'), 0);
|
||||
const appPlatform = ctx.get('x-app-platform');
|
||||
/**
|
||||
* @param {boolean=} allowExpired
|
||||
* @return {import('koa').Middleware}
|
||||
*/
|
||||
controller.authorization = allowExpired => {
|
||||
return async (ctx, next) => {
|
||||
ctx.token = {};
|
||||
/** @type {string} */
|
||||
const token = ctx.get('authorization');
|
||||
|
||||
Object.assign(ctx.state, { appVersion, appBuildNumber, appPlatform });
|
||||
if (!token) ctx.err(Unauthorized);
|
||||
|
||||
return next();
|
||||
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();
|
||||
};
|
||||
};
|
||||
|
@ -5,24 +5,3 @@ controller.healthCheck = async ctx => {
|
||||
ctx.body = 'ok';
|
||||
ctx.status = 200;
|
||||
};
|
||||
|
||||
controller.appleAppSiteAssociation = async ctx => {
|
||||
ctx.status = 200;
|
||||
ctx.body = {
|
||||
applinks: {
|
||||
details: [
|
||||
{
|
||||
appID: 'CL3K9D5FDN.com.lawsnote.college.staging',
|
||||
paths: ['*'],
|
||||
},
|
||||
{
|
||||
appID: 'CL3K9D5FDN.com.lawsnote.college',
|
||||
paths: ['*'],
|
||||
},
|
||||
],
|
||||
},
|
||||
webcredentials: {
|
||||
apps: ['CL3K9D5FDN.com.lawsnote.college.staging', 'CL3K9D5FDN.com.lawsnote.college'],
|
||||
},
|
||||
};
|
||||
};
|
||||
|
@ -36,7 +36,7 @@ controller.verifyCode = () => async ctx => {
|
||||
// generate jwt token
|
||||
const jwtToken = jwt.sign(
|
||||
{
|
||||
user_id: `${token}-id`,
|
||||
user_id: token.user_id,
|
||||
sso: true,
|
||||
},
|
||||
config.server.jwt_secret,
|
||||
|
@ -1,12 +1,58 @@
|
||||
const Router = require('@koa/router');
|
||||
|
||||
const joi = require('joi');
|
||||
const commonCtrl = require('src/controllers/common/index.js');
|
||||
const v1Router = require('./v1/index.js');
|
||||
const accCtrl = require('src/controllers/account/index.js');
|
||||
|
||||
const r = new Router({ prefix: '/api' });
|
||||
module.exports = r;
|
||||
|
||||
// set api handler middleware
|
||||
r.use(commonCtrl.apiHandler(), commonCtrl.getAppVersion());
|
||||
r.use(commonCtrl.apiHandler());
|
||||
|
||||
r.use(v1Router.routes());
|
||||
/**
|
||||
* get account info
|
||||
* @swagger
|
||||
* @route GET /api/login
|
||||
* @group account - account apis
|
||||
* @param {string} back_url.query.required - back to url
|
||||
* @returns {RespDefault.model} default -
|
||||
*/
|
||||
r.get(
|
||||
'/login',
|
||||
commonCtrl.validate({
|
||||
query: {
|
||||
back_url: joi.string().required(),
|
||||
},
|
||||
}),
|
||||
accCtrl.loginSSO()
|
||||
);
|
||||
|
||||
/**
|
||||
* account refresh token
|
||||
* @swagger
|
||||
* @route POST /api/refresh
|
||||
* @group account - account apis
|
||||
* @security JWT
|
||||
* @returns {RespDefault.model} default -
|
||||
*/
|
||||
r.post('/refresh', commonCtrl.authorization(true), accCtrl.logout());
|
||||
|
||||
/**
|
||||
* account logout
|
||||
* @swagger
|
||||
* @route POST /api/logout
|
||||
* @group account - account apis
|
||||
* @security JWT
|
||||
* @returns {RespDefault.model} default -
|
||||
*/
|
||||
r.post('/logout', commonCtrl.authorization(false), accCtrl.logout());
|
||||
|
||||
/**
|
||||
* account get info
|
||||
* @swagger
|
||||
* @route GET /api/userinfo
|
||||
* @group account - account apis
|
||||
* @security JWT
|
||||
* @returns {RespDefault.model} default -
|
||||
*/
|
||||
r.get('/userinfo', commonCtrl.authorization(false), accCtrl.getInfo());
|
||||
|
@ -1,25 +0,0 @@
|
||||
const Router = require('@koa/router');
|
||||
const joi = require('joi');
|
||||
const commonCtrl = require('src/controllers/common/index.js');
|
||||
const accCtrl = require('src/controllers/account/v1/index.js');
|
||||
|
||||
const r = new Router({ prefix: '/account' });
|
||||
module.exports = r;
|
||||
|
||||
/**
|
||||
* get account info
|
||||
* @swagger
|
||||
* @route GET /api/v1/account/login/sso
|
||||
* @group account - account apis
|
||||
* @param {string} back_url.query.required - back to url
|
||||
* @returns {RespDefault.model} default -
|
||||
*/
|
||||
r.get(
|
||||
'/login/sso',
|
||||
commonCtrl.validate({
|
||||
query: {
|
||||
back_url: joi.string().required(),
|
||||
},
|
||||
}),
|
||||
accCtrl.loginSSO()
|
||||
);
|
@ -1,8 +0,0 @@
|
||||
const Router = require('@koa/router');
|
||||
|
||||
const accountRouter = require('./account/index.js');
|
||||
|
||||
const r = new Router({ prefix: '/v1' });
|
||||
module.exports = r;
|
||||
|
||||
r.use(accountRouter.routes());
|
@ -8,7 +8,6 @@ const r = new Router();
|
||||
module.exports = r;
|
||||
|
||||
r.get('/', controller.healthCheck);
|
||||
r.get(['/apple-app-site-association', '/.well-known/apple-app-site-association'], controller.appleAppSiteAssociation);
|
||||
|
||||
r.use(apiRouter.routes());
|
||||
r.use(oauthRouter.routes());
|
||||
|
@ -1,11 +1,11 @@
|
||||
const IORedis = require('ioredis');
|
||||
const config = require('src/config/index.js');
|
||||
const IORedis = require("ioredis");
|
||||
const config = require("src/config/index.js");
|
||||
|
||||
class Redis extends IORedis {
|
||||
constructor() {
|
||||
let { prefix } = config.redis;
|
||||
const { host, port, password, db } = config.redis;
|
||||
if (prefix && !/:$/.test(prefix)) prefix += ':';
|
||||
if (prefix && !/:$/.test(prefix)) prefix += ":";
|
||||
super({
|
||||
host,
|
||||
port,
|
||||
@ -23,7 +23,13 @@ class Redis extends IORedis {
|
||||
* @param {string} s state
|
||||
* @return {string}
|
||||
*/
|
||||
ssoLoginCache: s => self.getKeyWithPrefix(`sso-login:${s}`),
|
||||
ssoLoginCache: (s) => self.getKeyWithPrefix(`sso-login:${s}`),
|
||||
/**
|
||||
* 儲存 Token
|
||||
* @param {string} s state
|
||||
* @return {string}
|
||||
*/
|
||||
userToken: (s) => self.getKeyWithPrefix(`token:${s}`),
|
||||
};
|
||||
}
|
||||
|
||||
@ -33,7 +39,7 @@ class Redis extends IORedis {
|
||||
* @return {string}
|
||||
*/
|
||||
getKeyWithPrefix(s) {
|
||||
if (typeof s !== 'string') throw new Error('input key not a string');
|
||||
if (typeof s !== "string") throw new Error("input key not a string");
|
||||
|
||||
return `${this.prefix}${s}`;
|
||||
}
|
||||
|
@ -42,6 +42,9 @@ mod.getAuthURL = state => {
|
||||
return `${value.authorized_endpoint}?${querystring.stringify(qs)}`;
|
||||
};
|
||||
|
||||
/**
|
||||
* @return {string}
|
||||
*/
|
||||
mod.getLogoutURL = () => {
|
||||
const input = joi
|
||||
.object({
|
||||
@ -59,6 +62,9 @@ mod.getLogoutURL = () => {
|
||||
|
||||
/**
|
||||
* @typedef SSOAccount
|
||||
* @property {string} access_token
|
||||
* @property {string} refresh_token
|
||||
* @property {string} user_id
|
||||
* @property {string} username
|
||||
* @property {string} display_name
|
||||
* @property {string} email
|
||||
@ -106,13 +112,13 @@ mod.getToken = async (code, state) => {
|
||||
const { body } = resp;
|
||||
if (!body) throw new Error('resopnse body empty');
|
||||
|
||||
const { id_token: idToken, access_token: accessToken } = body;
|
||||
const { id_token: idToken, access_token: accessToken, refresh_token: refreshToken } = body;
|
||||
if (!idToken) throw new Error('get id token fail');
|
||||
|
||||
const decoded = jwt.decode(idToken);
|
||||
if (!decoded || typeof decoded !== 'object') throw new Error('jwt decode fail');
|
||||
console.log(decoded)
|
||||
console.log(body)
|
||||
console.log('decoded ::: ', decoded)
|
||||
console.log('body ::: ', body)
|
||||
// @ts-ignore
|
||||
const { preferred_username: preferredUsername } = decoded;
|
||||
if (!preferredUsername) throw new Error('id token field missing');
|
||||
@ -121,6 +127,9 @@ mod.getToken = async (code, state) => {
|
||||
|
||||
/** @type {SSOAccount} */
|
||||
const ssoAccount = {
|
||||
access_token: accessToken,
|
||||
refresh_token: refreshToken,
|
||||
user_id: decoded.sub,
|
||||
username: preferredUsername.toLowerCase(),
|
||||
display_name: displayName ?? preferredUsername,
|
||||
email: decoded.email ?? '',
|
||||
|
Loading…
Reference in New Issue
Block a user