[feat] Init code

This commit is contained in:
JasonWu
2021-08-31 18:24:42 +08:00
commit b1e9c5e62a
57 changed files with 17957 additions and 0 deletions
+26
View File
@@ -0,0 +1,26 @@
const { resp } = require('src/utils/response/index.js');
const redis = require('src/utils/redis.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 controller = {};
module.exports = controller;
controller.loginSSO = () => async ctx => {
const { back_url: backURL } = ctx.query;
const state = uuid.v4();
const authURL = sso.getAuthURL(state);
// store back url to cache
const cacheKey = redis.Key.ssoLoginCache(state);
await redis.set(cacheKey, JSON.stringify({ back_url: backURL }), 'EX', OPENID_EXPIRE);
const u = new url.URL(authURL);
ctx.resp(resp.Success, { url: u.toString() });
};
+113
View File
@@ -0,0 +1,113 @@
/* 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 { copyObject, toNumber } = require('src/utils/index.js');
const { Success, InternalError, DataFormat } = 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();
};
};
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');
Object.assign(ctx.state, { appVersion, appBuildNumber, appPlatform });
return next();
};
+28
View File
@@ -0,0 +1,28 @@
const controller = {};
module.exports = controller;
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'],
},
};
};
+73
View File
@@ -0,0 +1,73 @@
const debug = require('debug')('ctrl:common');
const util = require('util');
const url = require('url');
const sso = require('src/utils/sso/index.js');
const redis = require('src/utils/redis.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;
controller.verifyCode = () => async ctx => {
const { code, session_state: sessionState, state } = ctx.query;
// logout flow redirect tot frontend
if (state === 'logout') {
ctx.redirect(config.server.frontend_url);
return;
}
// get back url from redis
const cacheKey = redis.Key.ssoLoginCache(state);
const data = await redis.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);
try {
const token = await sso.getToken(code, sessionState);
// generate jwt token
const jwtToken = jwt.sign(
{
user_id: `${token}-id`,
sso: true,
},
config.server.jwt_secret,
{
expiresIn: config.server.jwt_expire,
issuer: 'lawsnote',
}
);
u.searchParams.append('success', Buffer.from(JSON.stringify({ token: jwtToken })).toString('base64'));
try {
await redis.del(cacheKey);
} catch (err) {
debug(`delete cache fail: ${util.inspect(err, false, null)}`);
}
} catch (err) {
debug(`openid verify fail: ${util.inspect(err, false, null)}`);
/** @type {object} */
const errObj = { ...codeMessage.CodeInternalError };
if (err instanceof APIError) {
// @ts-ignore
Object.assign(errObj, err.object.object);
}
errObj.errorStack = err.stack;
errObj.errorMessage = err.message;
u.searchParams.append('error', Buffer.from(JSON.stringify(errObj)).toString('base64'));
}
ctx.redirect(u.toString());
};