Skip to content

Commit

Permalink
Merge pull request GEOLYTIX#1842 from GEOLYTIX/login-rate-limit
Browse files Browse the repository at this point in the history
Login rate limit
  • Loading branch information
RobAndrewHurst authored Jan 27, 2025
2 parents 52d666e + 1c152a2 commit c37410b
Show file tree
Hide file tree
Showing 3 changed files with 258 additions and 226 deletions.
65 changes: 55 additions & 10 deletions mod/user/_user.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ The _user module exports the user method to route User API requests.
@module /user
*/

const reqHost = require('../utils/reqHost')
const reqHost = require('../utils/reqHost');

const methods = {
add: require('./add'),
Expand All @@ -36,7 +36,9 @@ const methods = {
token: require('./token'),
update: require('./update'),
verify: require('./verify'),
}
};

const previousAddress = {};

/**
@function user
Expand All @@ -49,26 +51,69 @@ The route method assigns the host param from /utils/reqHost before the request a
The method request parameter must be an own member of the methods object, eg. `admin`, `register`, `verify`, `add`, `delete`, `update`, `list`, `log`, `key`, `token`, `cookie`, or `login`.
Requests to the user module are debounced by 5 seconds preventing registration, login, etc in quick succession from the same IP address.
@param {Object} req HTTP request.
@param {Object} res HTTP response.
@param {Object} req.params Request parameter.
@param {string} req.params.method Method request parameter.
*/

module.exports = async function user(req, res) {

if (!Object.hasOwn(methods, req.params.method)) {
return res.send(`Failed to evaluate 'method' param.`);
}

if (req.body) {
debounceRequest(req, res);

return res.send(`Failed to evaluate 'method' param.`)
if (res.finished) return;
}

req.params.host = reqHost(req)
req.params.host = reqHost(req);

const method = await methods[req.params.method](req, res)
const method = await methods[req.params.method](req, res);

if (method instanceof Error) {
req.params.msg = method.message;
methods.login(req, res);
}
};

req.params.msg = method.message
methods.login(req, res)
/**
@function debounceRequest
@description
The remote_address determined from the request header is stored in the previousAddress module variable. Requests from the same address within 30 seconds will be bounced.
@param {req} req HTTP request.
@param {res} res HTTP response.
@property {Object} req.params HTTP request parameter.
@property {Object} req.header HTTP request header.
*/
function debounceRequest(req, res) {
if (!req.headers['x-forwarded-for']) {
req.params.remote_address = 'unknown';
} else {
req.params.remote_address = /^[A-Za-z0-9.,_-\s]*$/.test(
req.headers['x-forwarded-for'],
)
? req.headers['x-forwarded-for']
: 'invalid';
}
}

// The remote_address has been previously used
if (
Object.hasOwn(previousAddress, req.params.remote_address) &&
// within 5 seconds or less.
new Date() - previousAddress[req.params.remote_address] < 5000
) {
res
.status(429)
.send(`Address ${req.params.remote_address} temporarily locked.`);

return;
}

// Log the remote_address with the current datetime.
previousAddress[req.params.remote_address] = new Date();
}
Loading

0 comments on commit c37410b

Please sign in to comment.