1
0
Fork 0

Error Handling & Minification

refactor/models
Matthew 2019-11-17 16:11:41 -05:00
parent 69a7af127b
commit 42ed3da0d3
No known key found for this signature in database
GPG Key ID: 766BE43AE75F7559
5 changed files with 69 additions and 24 deletions

View File

@ -18,6 +18,7 @@
"eris-pagination": "bsian03/eris-pagination", "eris-pagination": "bsian03/eris-pagination",
"express": "^4.17.1", "express": "^4.17.1",
"fs-extra": "^8.1.0", "fs-extra": "^8.1.0",
"helmet": "^3.21.2",
"ioredis": "^4.14.1", "ioredis": "^4.14.1",
"moment": "^2.24.0", "moment": "^2.24.0",
"moment-precise-range-plugin": "^1.3.0", "moment-precise-range-plugin": "^1.3.0",
@ -29,6 +30,7 @@
"devDependencies": { "devDependencies": {
"@types/express": "^4.17.2", "@types/express": "^4.17.2",
"@types/fs-extra": "^8.0.0", "@types/fs-extra": "^8.0.0",
"@types/helmet": "^0.0.45",
"@types/ioredis": "^4.0.18", "@types/ioredis": "^4.0.18",
"@types/mongoose": "^5.5.20", "@types/mongoose": "^5.5.20",
"@types/nodemailer": "^6.2.1", "@types/nodemailer": "^6.2.1",

View File

@ -38,6 +38,10 @@ export default class Security {
return `${salt}:${encrypted}`; return `${salt}:${encrypted}`;
} }
/**
* If the bearer token is valid, will return the Account, else will return null.
* @param bearer The bearer token provided.
*/
public async checkBearer(bearer: string): Promise<null | AccountInterface> { public async checkBearer(bearer: string): Promise<null | AccountInterface> {
const decipher = crypto.createDecipheriv('aes-256-gcm', this.keys.key, this.keys.iv); const decipher = crypto.createDecipheriv('aes-256-gcm', this.keys.key, this.keys.iv);
try { try {
@ -57,6 +61,10 @@ export default class Security {
} }
} }
/**
* Returns the Bearer token, searches in headers and query.
* @param req The Request object from Express.
*/
public extractBearer(req: Request): string { public extractBearer(req: Request): string {
if (req.headers.authorization && req.headers.authorization.split(' ')[0] === 'Bearer') { if (req.headers.authorization && req.headers.authorization.split(' ')[0] === 'Bearer') {
return req.headers.authorization.split(' ')[1]; return req.headers.authorization.split(' ')[1];

View File

@ -1,6 +1,7 @@
/* eslint-disable no-useless-return */ /* eslint-disable no-useless-return */
import express from 'express'; import express from 'express';
import bodyParser from 'body-parser'; import bodyParser from 'body-parser';
import helmet from 'helmet';
import fs from 'fs-extra'; import fs from 'fs-extra';
import { Client } from '..'; import { Client } from '..';
import { Security } from '.'; import { Security } from '.';
@ -34,7 +35,11 @@ export default class Server {
try { try {
// eslint-disable-next-line new-cap // eslint-disable-next-line new-cap
const route = new (require(`${__dirname}/routes/${routeFile}`).default)(this); const route = new (require(`${__dirname}/routes/${routeFile}`).default)(this);
route.bind(); if (route.conf.deprecated === true) {
route.deprecated();
} else {
route.bind();
}
this.routes.set(route.conf.path, route); this.routes.set(route.conf.path, route);
this.app.use(route.conf.path, route.router); this.app.use(route.conf.path, route.router);
this.client.signale.success(`Successfully loaded route ${route.conf.path}`); this.client.signale.success(`Successfully loaded route ${route.conf.path}`);
@ -45,6 +50,12 @@ export default class Server {
} }
private connect(): void { private connect(): void {
this.app.set('trust proxy', 'loopback');
this.app.use(helmet({
hsts: false,
hidePoweredBy: false,
contentSecurityPolicy: true,
}));
this.app.use(bodyParser.json()); this.app.use(bodyParser.json());
this.app.listen(this.options.port, () => { this.app.listen(this.options.port, () => {
this.client.signale.success(`API Server listening on port ${this.options.port}`); this.client.signale.success(`API Server listening on port ${this.options.port}`);

View File

@ -10,33 +10,38 @@ export default class Account extends Route {
public bind() { public bind() {
this.router.use(async (req, res, next) => { this.router.use(async (req, res, next) => {
const account = await this.server.security.checkBearer(this.server.security.extractBearer(req)); await this.authorize(req, res, next);
if (!account) return res.status(401).json({ code: this.constants.codes.UNAUTHORIZED, message: 'BEARER_TOKEN_INVALID' });
Object.defineProperty(req, 'account', { value: account, writable: true, enumerable: true, configurable: true });
next();
}); });
this.router.get('/', async (req: Req, res) => { this.router.get('/', async (req: Req, res) => {
const acc: any = {}; try {
acc.username = req.account.username; const acc: any = {};
acc.userID = req.account.userID; acc.username = req.account.username;
acc.email = req.account.emailAddress; acc.userID = req.account.userID;
acc.locked = req.account.locked; acc.email = req.account.emailAddress;
acc.root = req.account.root; acc.locked = req.account.locked;
acc.createdAt = req.account.createdAt; acc.root = req.account.root;
acc.createdBy = req.account.createdBy; acc.createdAt = req.account.createdAt;
acc.permissions = req.account.permissions; acc.createdBy = req.account.createdBy;
res.status(200).json({ code: this.constants.codes.SUCCESS, message: acc }); acc.permissions = req.account.permissions;
res.status(200).json({ code: this.constants.codes.SUCCESS, message: acc });
} catch (error) {
this.handleError(error, res);
}
}); });
this.router.get('/moderations/:id?', async (req: Req, res) => { this.router.get('/moderations/:id?', async (req: Req, res) => {
const moderations = await this.server.client.db.Moderation.find({ username: req.account.username }); try {
if (!moderations.length) res.sendStatus(204); const moderations = await this.server.client.db.Moderation.find({ username: req.account.username });
if (req.params.id) { if (!moderations.length) res.sendStatus(204);
const filtered = moderations.filter((moderation) => moderation.logID === req.params.id); if (req.params.id) {
res.status(200).json({ code: this.constants.codes.SUCCESS, message: { filtered } }); const filtered = moderations.filter((moderation) => moderation.logID === req.params.id);
} else { res.status(200).json({ code: this.constants.codes.SUCCESS, message: { filtered } });
res.status(200).json({ code: this.constants.codes.SUCCESS, message: moderations }); } else {
res.status(200).json({ code: this.constants.codes.SUCCESS, message: moderations });
}
} catch (error) {
this.handleError(error, res);
} }
}); });
} }

View File

@ -1,4 +1,5 @@
import { Router as router } from 'express'; /* eslint-disable consistent-return */
import { Request, Response, NextFunction, Router as router } from 'express';
import { Server } from '../api'; import { Server } from '../api';
export default class Route { export default class Route {
@ -16,6 +17,24 @@ export default class Route {
public bind() {} public bind() {}
public deprecated() {
this.router.all('*', (_req, res) => {
res.status(501).json({ code: this.constants.codes.DEPRECATED, message: 'This endpoint is deprecated.' });
});
}
public async authorize(req: Request, res: Response, next: NextFunction) {
const account = await this.server.security.checkBearer(this.server.security.extractBearer(req));
if (!account) return res.status(401).json({ code: this.constants.codes.UNAUTHORIZED, message: 'BEARER_TOKEN_INVALID' });
Object.defineProperty(req, 'account', { value: account, writable: true, enumerable: true, configurable: true });
next();
}
public handleError(error: Error, res: Response) {
this.server.client.util.handleError(error);
res.status(500).json({ code: this.constants.codes.SERVER_ERROR, message: 'An internal error has occurred, Engineers have been notified.' });
}
get constants() { get constants() {
return { return {
codes: { codes: {
@ -26,7 +45,7 @@ export default class Route {
ACCOUNT_NOT_FOUND: 1041, ACCOUNT_NOT_FOUND: 1041,
CLIENT_ERROR: 1044, CLIENT_ERROR: 1044,
SERVER_ERROR: 105, SERVER_ERROR: 105,
UNKNOWN_SERVER_ERROR: 1051, DEPRECATED: 1051,
}, },
}; };
} }