Add rate limiting and tidy up various code.

Will need complete review long before merging to main.

Signed-off-by: Fred Boniface <fred@fjla.uk>
This commit is contained in:
Fred Boniface 2023-04-06 22:01:37 +01:00
parent 3f7defcf8e
commit 096ce154da
10 changed files with 58 additions and 38 deletions

9
app.js
View File

@ -12,6 +12,7 @@ const app = express();
// Middleware
const compression = require('compression')
const rateLimit = require('express-rate-limit')
const authenticate= require('./src/middlewares/auth.middlewares')
// Internal Requires
@ -29,6 +30,13 @@ const regRtr = require('./src/routes/registration.routes'); // /auth end
const srvListen = process.env.OWL_SRV_LISTEN || "0.0.0.0"
const srvPort = process.env.OWL_SRV_PORT || 8460
const limiter = rateLimit({
windowMs: 15 * (60 * 1000), // 15 minutes
max: 100, // Limit each IP to 100 requests per `window` (here, per 15 minutes)
standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers
legacyHeaders: true, // Disable the `X-RateLimit-*` headers
})
// Print version number:
log.out(`app: Starting OwlBoard - Backend Version: ${version.app} - API versions: ${version.api}`, "init");
@ -52,6 +60,7 @@ app.use((err, req, res, next) => {
// Global Middleware:
app.use(express.json()); //JSON Parsing for POST Requests
app.use(compression()) // Compress API Data if supported by client
app.use(limiter)
// Unauthenticated Routes
app.use('/api/v1/list', listRtr);

12
package-lock.json generated
View File

@ -12,6 +12,7 @@
"axios": "^1.2.1",
"compression": "^1.7.4",
"express": "^4.18.2",
"express-rate-limit": "^6.7.0",
"ldbs-json": "^1.2.1",
"mongodb": "^4.13.0",
"nodemailer": "^6.9.1",
@ -1426,6 +1427,17 @@
"node": ">= 0.10.0"
}
},
"node_modules/express-rate-limit": {
"version": "6.7.0",
"resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-6.7.0.tgz",
"integrity": "sha512-vhwIdRoqcYB/72TK3tRZI+0ttS8Ytrk24GfmsxDXK9o9IhHNO5bXRiXQSExPQ4GbaE5tvIS7j1SGrxsuWs+sGA==",
"engines": {
"node": ">= 12.9.0"
},
"peerDependencies": {
"express": "^4 || ^5"
}
},
"node_modules/express/node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",

View File

@ -3,6 +3,7 @@
"axios": "^1.2.1",
"compression": "^1.7.4",
"express": "^4.18.2",
"express-rate-limit": "^6.7.0",
"ldbs-json": "^1.2.1",
"mongodb": "^4.13.0",
"nodemailer": "^6.9.1",

View File

@ -11,7 +11,7 @@ module.exports = async function authCheck(req, res, next) {
return next(err)
}
try {
var result = await utils.isAuthed(uuid) | false
var result = await utils.isAuthed(uuid) || false
if (!result) {
const err = new Error("Unauthorised");
err.status = 401

View File

@ -23,7 +23,7 @@ async function send(message){ // message is an object containing strings for: *t
try {
var res = await transporter.sendMail(message)
} catch(err) {
log.out(`mailServices.send: Message send failed`, "err")
log.out(`mailServices.send: Message send failed: ${err}`, "err")
return false;
}
log.out(`mailServices.send: SMTP Response: ${res.response}`)

View File

@ -7,15 +7,14 @@ const domains = require('../configs/domains.configs')
async function createRegKey(body) {
log.out(`registerServices.createRegKey: Incoming request`, "INFO")
log.out(`registerServices.createRegKey: ${JSON.stringify(domains)}`)
const domain = await clean.splitDomain(body.email)
const domain = await clean.getDomainFromEmail(body.email) // The function should validate the email
log.out(`registerServices: New registration request from domain: ${domain}`, "info")
if (domains.includes(domain)) { // Needs testing
if (domains.includes(domain)) {
log.out(`registerServices.createRegKey: Key from valid domain: ${domain}`)
const uuid = auth.generateKey();
db.addRegReq(await uuid, domain)
const message = auth.generateConfirmationEmail(body.email, uuid)
if (await message == false) { // This error should be handled in the upstream function
const uuid = await auth.generateKey();
db.addRegReq(uuid, domain)
const message = await auth.generateConfirmationEmail(body.email, uuid)
if (!message) {
const err = new Error("Message generation error");
log.out(`registerServices.createRegKey: Error generating registration email`, "err")
return 500;
@ -28,19 +27,19 @@ async function createRegKey(body){
return {status: 403, message: "forbidden, domain is not on whitelist"}
}
async function regUser(req) {
log.out(`registrationServices.regUser: Checking validity of : ${req.uuid}`)
let res = await auth.checkRequest(req.uuid)
log.out(`registrationServices.regUser: checkRequest returned: ${JSON.stringify(res)}`)
async function regUser(req) { // Add input validation
log.out(`registrationServices.regUser: Checking validity of : ${req.uuid}`, "info")
const res = await auth.checkRequest(req.uuid)
log.out(`registrationServices.regUser: checkRequest returned: ${JSON.stringify(res)}`, "info")
if (res.result) {
let uuid = await auth.generateKey()
let apiKey = await db.addUser(uuid, res.domain)
const uuid = await auth.generateKey()
const apiKey = await db.addUser(uuid, res.domain)
if (apiKey) {
db.delRegReq(req.uuid)
return {status: 201, message: "User added", api_key: uuid}
}
}
return {status: 401, message: "Unautorised"}
return {status: 401, message: "Unauthorised"}
}
module.exports = {

View File

@ -5,27 +5,24 @@ const fs = require('fs/promises')
// Checks users registration key against issued keys
async function isAuthed(uuid) { // Needs testing
q = {uuid: uuid};
res = await db.query("users", q);
log.out(`authUtils.checkUser: DB Query answer: ${JSON.stringify(res[0])}`)
// Do something here to determine if authorised or not and simply return a BOOL
if (res[0].domain) {
db.userAtime(uuid)
return true
}
return false
const q = {uuid: uuid}
const res = await db.query("users", q)
log.out(`authUtils.checkUser: DB Query answer: ${JSON.stringify(res[0])}`, "dbug")
const authorized = res && res[0] && res[0].domain;
if (authorized) db.userAtime(uuid)
return authorized
}
// Checks whether a registration request key is valid
async function checkRequest(key) {
let collection = "registrations"
let query = {uuid: key}
const collection = "registrations"
const query = {uuid: key}
const res = await db.query(collection, query)
log.out(`authUtils.checkRequest: DB Query result: ${JSON.stringify(res)}`, "info")
if (res[0].time) {
return {result: true, domain: res[0].domain}
}
return {result: false}
log.out(`authUtils.checkRequest: DB Query result: ${JSON.stringify(res)}`, "dbug")
const result = res.length > 0 && res[0].time
? { result: true, domain: res[0].domain }
: { result: false };
return result;
}
// Creates an API key for a user

View File

@ -4,10 +4,10 @@ const san = require('../utils/sanitizer.utils') // Sanitiser
async function checkCrs(input){
var INPUT = input.toUpperCase()
log.out(`ldbUtils.checkCrs: Building database query to find: '${INPUT}'`, "info")
log.out(`ldbUtils.checkCrs: Building database query to find: '${INPUT}'`, "dbug")
var query = {'$or':[{'3ALPHA':INPUT},{'TIPLOC':INPUT},{'STANOX':INPUT}]};
var result = await db.query("stations", query)
log.out(`ldbUtils.checkCrs: Query results: ${JSON.stringify(result)}`, "info")
log.out(`ldbUtils.checkCrs: Query results: ${JSON.stringify(result)}`, "dbug")
return result
}

View File

@ -1,7 +1,9 @@
const environment = process.env.NODE_ENV;
const hideInProduction = ["info", "dbug"]
async function out(msg, level = 'othr') {
if (environment === "production" && level === "info") {
if (environment === "production" && hideInProduction.includes(level)) {
return;
} else {
const time = new Date().toISOString();

View File

@ -38,7 +38,7 @@ function cleanNrcc(input) {
return rmPara;
}
async function splitDomain(mail) { // Needs testing
async function getDomainFromEmail(mail) { // Needs testing
split = mail.split("@")
return split[1]
}
@ -47,5 +47,5 @@ module.exports = {
cleanApiEndpointTxt,
cleanApiEndpointNum,
cleanNrcc,
splitDomain
getDomainFromEmail
}