diff --git a/src/app.js b/src/app.js index b4ac39a..f9e035a 100644 --- a/src/app.js +++ b/src/app.js @@ -3,6 +3,7 @@ import {Config} from './config/config' import {Database} from './models/database/database'; import {Server} from './server'; import {Router} from './controllers/routes/router'; +import {middleware, MiddleWare} from './controllers/middleware/index'; import {User} from './models/user/user'; @@ -15,6 +16,7 @@ async function init() { await Database.testConnection(); await Server.start(); await Router.initEndpoints(); + await MiddleWare.RateLimits.init(); Logger.ready(); diff --git a/src/controllers/middleware.js b/src/controllers/middleware/index.js similarity index 55% rename from src/controllers/middleware.js rename to src/controllers/middleware/index.js index 2287396..d4020e7 100644 --- a/src/controllers/middleware.js +++ b/src/controllers/middleware/index.js @@ -1,8 +1,13 @@ import stringify from 'json-stringify-safe'; -import {Logger} from '../models/logger'; +import {Logger} from '../../models/logger'; export class MiddleWare { + static async end(req, res, next) { + await MiddleWare.RateLimits.request(req, res, next); + await MiddleWare.analytics(req, res, next); + } + static analytics(req, res, next) { // TODO: Send data such as IP to an anyaltitics model Logger.middleware(`${req.method} request to ${req.url}`) @@ -13,3 +18,5 @@ export class MiddleWare { next(); } } + +MiddleWare.RateLimits = require('./rateLimits').RateLimits; diff --git a/src/controllers/middleware/rateLimits.js b/src/controllers/middleware/rateLimits.js new file mode 100644 index 0000000..d1cf7e2 --- /dev/null +++ b/src/controllers/middleware/rateLimits.js @@ -0,0 +1,53 @@ +import {Logger} from '../../models/logger'; +import {MiddleWare} from './index'; + +let requestsPerSecond = 2; +// let disposeTime = 20000; //ms 1800000 = 30 mins +let buckets = {} + +export class RateLimits extends MiddleWare{ + static async request(req, res, next) { + let ip = req.connection.remoteAddress; + + if (!buckets[ip]) { + console.log(`New bucket`) + RateLimits.newBucket(ip); + next(); + return; + } + + buckets[ip].lastUsed = new Date().getTime(); + + if (buckets[ip].tokens.length <= 0) { + Logger.middleware(`${ip} is being rate limited`); + res.status(422).end('422 TO MANY REQUESTS'); + return; + } + + buckets[ip].tokens.pop(); + next(); + } + + static newBucket(ip) { + buckets[ip] = {ip: ip, tokens: [], lastUsed: new Date().getTime()}; + for (let i = 0; i < requestsPerSecond; i++) { + buckets[ip].tokens.push(1); + } + } + + static tick() { + for (let bucket in buckets) { + // if (buckets[bucket].lastUsed += disposeTime >= new Date().getTime()) { + // delete buckets[bucket]; // remove element here, don't redefine + // continue; + // } + if (buckets[bucket].tokens.length > requestsPerSecond) continue; + buckets[bucket].tokens.push(1); + } + } + + static init() { + Logger.info('Initialized ratelimiting middleware'); + setInterval(RateLimits.tick, 1000 / requestsPerSecond); + } +} diff --git a/src/controllers/routes/router.js b/src/controllers/routes/router.js index af4ae36..30d103b 100644 --- a/src/controllers/routes/router.js +++ b/src/controllers/routes/router.js @@ -1,6 +1,6 @@ import {Logger} from '../../models/logger'; import {Server} from '../../server'; -import {MiddleWare} from '../middleware'; +import {MiddleWare} from '../middleware/index'; import {StatusCodes} from '../status'; import {Controllers} from '../index'; @@ -12,11 +12,11 @@ export class Router { app = Server.App; - app.get('/', [MiddleWare.analytics, Router.frontPage]); + app.get('/', [MiddleWare.end, Router.frontPage]); - app.get('/user/:id', [MiddleWare.analytics]); - app.delete('/user/:id', [MiddleWare.analytics]); - app.post('/user', [MiddleWare.analytics, Controllers.UserController.newUser]); + app.get('/user/:id', [MiddleWare.end]); + app.delete('/user/:id', [MiddleWare.end]); + app.post('/user', [MiddleWare.end, Controllers.UserController.newUser]); app.use([StatusCodes.pageNotFound]); Logger.info('HTTP endpoints settup'); diff --git a/src/controllers/user.js b/src/controllers/user.js index c88fce4..fdbc2d4 100644 --- a/src/controllers/user.js +++ b/src/controllers/user.js @@ -31,12 +31,15 @@ export class UserController extends ControllerHandler { if (errors.count() > 0) { errors.endpoint(); next(); + return; } - let success = new User(id, username, password, email, ip, 1234).insert(); + let user = new User(id, username, password, email, ip, 1234) + let success = await user.insert(); if (success == -1) { - errors.addError(500, 'Internal server error', 'An error occured with the databse').endpoint(); + errors.addError(500, 'Internal server error').endpoint(); next(); + return; } new API.user(res, id, username, email, new Date().toLocaleString(), token).endpoint(); diff --git a/src/models/api/APIErrors.js b/src/models/api/APIErrors.js index 425eeb7..5139e7f 100644 --- a/src/models/api/APIErrors.js +++ b/src/models/api/APIErrors.js @@ -21,19 +21,18 @@ export class APIErrors extends API { } addError(statusCode, message, verbose) { + verbose = verbose || message; this.errors.error.errors.push({status: statusCode, title: message, detail: verbose}); this.errors.status.code = statusCode; this.errors.status.type = message; this.errors.status.message = verbose; } - count() { - return this.errors.error.errors.length; - } + count() { return this.errors.error.errors.length } endpoint() { - this.res.setHeader('Content-type', 'application/json'); - this.res.status(this.errors.status.code); - this.res.end(JSON.stringify(this.errors, false, 4)); + this.res + .status(this.errors.status.code) + .end(JSON.stringify(this.errors, false, 4)); } } diff --git a/src/models/api/userResponses.js b/src/models/api/userResponses.js index 3f63eda..f0fd6e3 100644 --- a/src/models/api/userResponses.js +++ b/src/models/api/userResponses.js @@ -27,8 +27,9 @@ export class User extends API { } endpoint() { - this.res.setHeader('Content-type', 'application/json'); - this.res.status(200); - this.res.end(JSON.stringify(this.response, false, 4)); + this.res + .setHeader('Content-type', 'application/json') + .status(200) + .end(JSON.stringify(this.response, false, 4)); } } diff --git a/src/models/database/sqlite/database.sqlite b/src/models/database/sqlite/database.sqlite index 7411751..9c1b673 100644 Binary files a/src/models/database/sqlite/database.sqlite and b/src/models/database/sqlite/database.sqlite differ