From 69cba82d7530a05fc77fb7b7e8728534ecf58c4d Mon Sep 17 00:00:00 2001 From: Jack Foltz Date: Wed, 26 Dec 2018 20:47:04 -0500 Subject: [PATCH] Add rate limiting instead of fail2ban --- app/routes/api/auth.js | 21 ++++++++- app/util/auth.js | 47 +++++++++++++++++-- config/default.json | 15 +++++++ config/dev.json | 3 ++ config/test.json | 3 ++ package-lock.json | 119 +++++++++++++++++++++++++++++-------------------- package.json | 1 + server.js | 10 +++++ 8 files changed, 165 insertions(+), 54 deletions(-) diff --git a/app/routes/api/auth.js b/app/routes/api/auth.js index f13be3c..738e804 100644 --- a/app/routes/api/auth.js +++ b/app/routes/api/auth.js @@ -13,6 +13,7 @@ const canonicalizeRequest = require('../../util/canonicalize').canonicalizeReque const requireAuth = require('../../util/auth').requireAuth; const wrap = require('../../util/wrap.js'); const bodyVerifier = require('../../util/verifyBody').bodyVerifier; +const rateLimit = require('express-rate-limit'); // Wraps passport.authenticate to return a promise const authenticate = (req, res, next) => { @@ -61,6 +62,13 @@ const validateUsername = wrap(async (req, res, next) => { next(); }); +const registerLimiter = config.get('RateLimit.enable') + ? rateLimit({ + windowMs: config.get('RateLimit.register.window') * 1000, + max: config.get('RateLimit.register.max'), + skipSuccessfulRequests: true + }) + : (req, res, next) => { next(); }; const registerProps = [ { name: 'displayname', @@ -72,6 +80,7 @@ const registerProps = [ {name: 'password', type: 'string'}, {name: 'invite', type: 'string'}]; router.post('/register', + registerLimiter, bodyVerifier(registerProps), canonicalizeRequest, validateInvite, validateUsername, wrap(async (req, res, next) => { @@ -89,11 +98,21 @@ router.post('/register', res.status(200).json({'message': 'Registration successful.'}); })); +const loginLimiter = config.get('RateLimit.enable') + ? rateLimit({ + windowMs: config.get('RateLimit.login.window') * 1000, + max: config.get('RateLimit.login.max'), + skipSuccessfulRequests: true + }) + : (req, res, next) => { next(); }; const loginProps = [ {name: 'username', type: 'string', optional: true}, {name: 'displayname', type: 'string', optional: true}, {name: 'password', type: 'string'}]; -router.post('/login', bodyVerifier(loginProps), canonicalizeRequest, wrap(async (req, res, next) => { +router.post('/login', + bodyVerifier(loginProps), + canonicalizeRequest, + wrap(async (req, res, next) => { // Authenticate const user = await authenticate(req, res, next); if (!user) { diff --git a/app/util/auth.js b/app/util/auth.js index 9b6cfab..9c4b43d 100644 --- a/app/util/auth.js +++ b/app/util/auth.js @@ -1,10 +1,13 @@ +const fs = require('fs').promises; +const config = require('config'); + const ModelPath = '../models/'; const Key = require(ModelPath + 'Key.js'); const User = require(ModelPath + 'User.js'); -const fs = require('fs').promises; const wrap = require('./wrap.js'); const verifyScope = require('./verifyScope.js'); +const rateLimit = require('express-rate-limit'); const checkSession = (req, scope, status) => { if (req.isAuthenticated()) { @@ -38,12 +41,20 @@ const checkKey = async (req, scope, status) => { } }; + +const apiLimiter = config.get('RateLimit.enable') + ? rateLimit({ + windowMs: config.get('RateLimit.api.window') * 1000, + max: config.get('RateLimit.api.max'), + skip: (req, res) => res.statusCode !== 401 && res.statusCode !== 403 + }) + : (req, res, next) => { next(); }; // Middleware that checks for authentication by either API key or session // sets req.username, req.displayname, req.scope, and req.key if authenticated properly, // otherwise throws an error code. // If the user is banned, also throw an error. -const requireAuth = scope => - wrap(async (req, res, next) => { +/* +const requireAuth = scope => wrap(async (req, res, next) => { const status = { authenticated: false, permission: false @@ -62,11 +73,39 @@ const requireAuth = scope => // Check if the user is banned const user = await User.findOne({username: req.username}); - if(user && user.banned) + if (user && user.banned) return res.status(403).json({message: 'Forbidden.'}); next(); }); + */ +const requireAuth = scope => (req, res, next) => { + apiLimiter(req, res, wrap(async () => { + + const status = { + authenticated: false, + permission: false + }; + + // First, check the session + checkSession(req, scope, status); + // If not authenticated yet, check for a key + if (!status.authenticated) + await checkKey(req, scope, status); + + if (!status.authenticated) + return res.status(401).json({message: 'Unauthorized.'}); + else if (!status.permission) + return res.status(403).json({message: 'Forbidden.'}); + + // Check if the user is banned + const user = await User.findOne({username: req.username}); + if (user && user.banned) + return res.status(403).json({message: 'Forbidden.'}); + + next(); + })); +}; module.exports.checkSession = checkSession; module.exports.checkKey = checkKey; diff --git a/config/default.json b/config/default.json index 5d2d758..60a6197 100644 --- a/config/default.json +++ b/config/default.json @@ -36,5 +36,20 @@ "Log": { "http": true, "httpLevel": "combined" + }, + "RateLimit": { + "enable": true, + "login": { + "window": 600, + "max": 5 + }, + "register": { + "window": 600, + "max": 5 + }, + "api": { + "window": 600, + "max": 10 + } } } diff --git a/config/dev.json b/config/dev.json index 953995e..92f9358 100644 --- a/config/dev.json +++ b/config/dev.json @@ -13,5 +13,8 @@ }, "Log": { "httpLevel": "dev" + }, + "RateLimit": { + "enable": false } } diff --git a/config/test.json b/config/test.json index 4597e23..4562988 100644 --- a/config/test.json +++ b/config/test.json @@ -19,5 +19,8 @@ "saltLength": 8, "hashIterations": 1 } + }, + "RateLimit": { + "enable": false } } \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 4a7def9..8caf095 100644 --- a/package-lock.json +++ b/package-lock.json @@ -440,7 +440,7 @@ }, "array-flatten": { "version": "1.1.1", - "resolved": "http://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" }, "array-initial": { @@ -541,7 +541,7 @@ }, "util": { "version": "0.10.3", - "resolved": "http://registry.npmjs.org/util/-/util-0.10.3.tgz", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", "dev": true, "requires": { @@ -988,7 +988,7 @@ }, "browser-pack": { "version": "6.1.0", - "resolved": "http://registry.npmjs.org/browser-pack/-/browser-pack-6.1.0.tgz", + "resolved": "https://registry.npmjs.org/browser-pack/-/browser-pack-6.1.0.tgz", "integrity": "sha512-erYug8XoqzU3IfcU8fUgyHqyOXqIE4tUTTQ+7mqUjQlvnXkOO6OlT9c/ZoJVHYoAaqGxr09CN53G7XIsO4KtWA==", "dev": true, "requires": { @@ -1011,7 +1011,7 @@ "dependencies": { "resolve": { "version": "1.1.7", - "resolved": "http://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", "dev": true } @@ -1093,7 +1093,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -1130,7 +1130,7 @@ }, "browserify-aes": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", "dev": true, "requires": { @@ -1175,7 +1175,7 @@ }, "browserify-rsa": { "version": "4.0.1", - "resolved": "http://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", + "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", "dev": true, "requires": { @@ -1518,6 +1518,11 @@ } } }, + "clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=" + }, "clone-buffer": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/clone-buffer/-/clone-buffer-1.0.0.tgz", @@ -1735,7 +1740,7 @@ }, "content-disposition": { "version": "0.5.2", - "resolved": "http://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=" }, "content-security-policy-builder": { @@ -1750,7 +1755,7 @@ }, "convert-source-map": { "version": "1.1.3", - "resolved": "http://registry.npmjs.org/convert-source-map/-/convert-source-map-1.1.3.tgz", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.1.3.tgz", "integrity": "sha1-SCnId+n+SbMWHzvzZziI4gRpmGA=", "dev": true }, @@ -1820,7 +1825,7 @@ }, "create-hash": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", "dev": true, "requires": { @@ -1833,7 +1838,7 @@ }, "create-hmac": { "version": "1.1.7", - "resolved": "http://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", "dev": true, "requires": { @@ -1967,6 +1972,14 @@ "resolved": "https://registry.npmjs.org/default-resolution/-/default-resolution-2.0.0.tgz", "integrity": "sha1-vLgrqnKtebQmp2cy8aga1t8m1oQ=" }, + "defaults": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", + "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=", + "requires": { + "clone": "^1.0.2" + } + }, "define-properties": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.2.tgz", @@ -2047,7 +2060,7 @@ }, "deps-sort": { "version": "2.0.0", - "resolved": "http://registry.npmjs.org/deps-sort/-/deps-sort-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/deps-sort/-/deps-sort-2.0.0.tgz", "integrity": "sha1-CRckkC6EZYJg65EHSMzNGvbiH7U=", "dev": true, "requires": { @@ -2088,7 +2101,7 @@ }, "detective": { "version": "5.1.0", - "resolved": "http://registry.npmjs.org/detective/-/detective-5.1.0.tgz", + "resolved": "https://registry.npmjs.org/detective/-/detective-5.1.0.tgz", "integrity": "sha512-TFHMqfOvxlgrfVzTEkNBSh9SvSNX/HfF4OFI2QFGCyPm02EsyILqnUeb5P6q7JZ3SFNTBL5t2sePRgrN4epUWQ==", "dev": true, "requires": { @@ -2114,7 +2127,7 @@ }, "diffie-hellman": { "version": "5.0.3", - "resolved": "http://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", "dev": true, "requires": { @@ -2149,7 +2162,7 @@ }, "duplexer": { "version": "0.1.1", - "resolved": "http://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=", "dev": true }, @@ -2322,7 +2335,7 @@ }, "es6-promise": { "version": "3.2.1", - "resolved": "http://registry.npmjs.org/es6-promise/-/es6-promise-3.2.1.tgz", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.2.1.tgz", "integrity": "sha1-7FYjOGgDKQkgcXDDlEjiREndH8Q=" }, "es6-symbol": { @@ -2521,6 +2534,14 @@ } } }, + "express-rate-limit": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-3.3.2.tgz", + "integrity": "sha512-JZnnTf6ZX9ntQalCZiPHsOG9zhxyRGqfaur+WD4yIcdqzf5FJQao5dmxXbWHk093K8WRSYwNwnzkFXVYnBNudg==", + "requires": { + "defaults": "^1.0.3" + } + }, "express-sanitizer": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/express-sanitizer/-/express-sanitizer-1.0.4.tgz", @@ -2672,7 +2693,7 @@ }, "finalhandler": { "version": "1.1.1", - "resolved": "http://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==", "requires": { "debug": "2.6.9", @@ -3595,7 +3616,7 @@ }, "get-stream": { "version": "3.0.0", - "resolved": "http://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=" }, "get-value": { @@ -4094,7 +4115,7 @@ }, "got": { "version": "6.7.1", - "resolved": "http://registry.npmjs.org/got/-/got-6.7.1.tgz", + "resolved": "https://registry.npmjs.org/got/-/got-6.7.1.tgz", "integrity": "sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA=", "requires": { "create-error-class": "^3.0.0", @@ -4569,7 +4590,7 @@ }, "htmlescape": { "version": "1.1.1", - "resolved": "http://registry.npmjs.org/htmlescape/-/htmlescape-1.1.1.tgz", + "resolved": "https://registry.npmjs.org/htmlescape/-/htmlescape-1.1.1.tgz", "integrity": "sha1-OgPtwiFLyjtmQko+eVk0lQnLA1E=", "dev": true }, @@ -4679,7 +4700,7 @@ "dependencies": { "concat-stream": { "version": "1.6.2", - "resolved": "http://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", "dev": true, "requires": { @@ -4703,7 +4724,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -4718,7 +4739,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { @@ -4903,7 +4924,7 @@ }, "is-obj": { "version": "1.0.1", - "resolved": "http://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=" }, "is-path-inside": { @@ -5246,7 +5267,7 @@ }, "labeled-stream-splicer": { "version": "2.0.1", - "resolved": "http://registry.npmjs.org/labeled-stream-splicer/-/labeled-stream-splicer-2.0.1.tgz", + "resolved": "https://registry.npmjs.org/labeled-stream-splicer/-/labeled-stream-splicer-2.0.1.tgz", "integrity": "sha512-MC94mHZRvJ3LfykJlTUipBqenZz1pacOZEMhhQ8dMGcDHs0SBE5GbsavUXV7YtP3icBW17W0Zy1I0lfASmo9Pg==", "dev": true, "requires": { @@ -5996,7 +6017,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -6011,7 +6032,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { @@ -12542,7 +12563,7 @@ }, "parse-asn1": { "version": "5.1.1", - "resolved": "http://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.1.tgz", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.1.tgz", "integrity": "sha512-KPx7flKXg775zZpnp9SxJlz00gTd4BmJ2yJufSc44gMCRrRQ7NSzAcSJQfifuOLgW6bEi+ftrALtsgALeB2Adw==", "dev": true, "requires": { @@ -12716,7 +12737,7 @@ }, "pause-stream": { "version": "0.0.11", - "resolved": "http://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", + "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", "integrity": "sha1-/lo0sMvOErWqaitAPuLnO2AvFEU=", "dev": true, "requires": { @@ -12955,7 +12976,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -12970,7 +12991,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { @@ -13394,7 +13415,7 @@ }, "sha.js": { "version": "2.4.11", - "resolved": "http://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", "dev": true, "requires": { @@ -13404,7 +13425,7 @@ }, "shasum": { "version": "1.0.2", - "resolved": "http://registry.npmjs.org/shasum/-/shasum-1.0.2.tgz", + "resolved": "https://registry.npmjs.org/shasum/-/shasum-1.0.2.tgz", "integrity": "sha1-5wEjENj0F/TetXEhUOVni4euVl8=", "dev": true, "requires": { @@ -13668,7 +13689,7 @@ }, "stream-browserify": { "version": "2.0.1", - "resolved": "http://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.1.tgz", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.1.tgz", "integrity": "sha1-ZiZu5fm9uZQKTkUUyvtDu3Hlyds=", "dev": true, "requires": { @@ -13690,7 +13711,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -13705,7 +13726,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { @@ -13748,7 +13769,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -13763,7 +13784,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { @@ -13804,7 +13825,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -13819,7 +13840,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { @@ -13835,7 +13856,7 @@ }, "stream-splicer": { "version": "2.0.0", - "resolved": "http://registry.npmjs.org/stream-splicer/-/stream-splicer-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/stream-splicer/-/stream-splicer-2.0.0.tgz", "integrity": "sha1-G2O+Q4oTPktnHMGTUZdgAXWRDYM=", "dev": true, "requires": { @@ -13857,7 +13878,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -13872,7 +13893,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { @@ -13933,7 +13954,7 @@ }, "strip-eof": { "version": "1.0.0", - "resolved": "http://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=" }, "strip-json-comments": { @@ -13997,7 +14018,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -14012,7 +14033,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { @@ -14038,7 +14059,7 @@ }, "syntax-error": { "version": "1.4.0", - "resolved": "http://registry.npmjs.org/syntax-error/-/syntax-error-1.4.0.tgz", + "resolved": "https://registry.npmjs.org/syntax-error/-/syntax-error-1.4.0.tgz", "integrity": "sha512-YPPlu67mdnHGTup2A8ff7BC2Pjq0e0Yp/IyTFN03zWO0RcK07uLcbi7C2KpGR2FvWbaB0+bfE27a+sBKebSo7w==", "dev": true, "requires": { @@ -14055,7 +14076,7 @@ }, "through": { "version": "2.3.8", - "resolved": "http://registry.npmjs.org/through/-/through-2.3.8.tgz", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", "dev": true }, @@ -14118,7 +14139,7 @@ }, "timers-browserify": { "version": "1.4.2", - "resolved": "http://registry.npmjs.org/timers-browserify/-/timers-browserify-1.4.2.tgz", + "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-1.4.2.tgz", "integrity": "sha1-ycWLV1voQHN1y14kYtrO50NZ9B0=", "dev": true, "requires": { diff --git a/package.json b/package.json index 5e3ee44..ea8495c 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "config": "^1.31.0", "connect-mongo": "^2.0.3", "express": "^4.16.4", + "express-rate-limit": "^3.3.2", "express-sanitizer": "^1.0.4", "express-session": "^1.15.6", "gulp-nodemon": "^2.4.2", diff --git a/server.js b/server.js index 3a94d28..4778b30 100755 --- a/server.js +++ b/server.js @@ -9,6 +9,7 @@ const passport = require('passport'); const session = require('express-session'); const sanitizer = require('express-sanitizer'); const helmet = require('helmet'); +const rateLimit = require('express-rate-limit'); const app = express(); const config = require('config'); @@ -56,6 +57,15 @@ app.use(bodyParser.text()); app.use(sanitizer()); app.use(methodOverride('X-HTTP-Method-Override')); +// Rate limiter +const global_limiter = rateLimit({ + windowMs: 60 * 1000, // 1 minute + max: 60 // limit to 1 request/second +}); + +if (config.get('RateLimit.enable')) + app.use(global_limiter); + // Static directories and favicon //app.use(favicon(__dirname + '/public/img/favicon.ico')); app.use(express.static(__dirname + '/public'));