webupdatev1
This commit is contained in:
152
backend/middleware/bruteForceProtection.js
Normal file
152
backend/middleware/bruteForceProtection.js
Normal file
@@ -0,0 +1,152 @@
|
||||
/**
|
||||
* Brute force protection middleware
|
||||
* Tracks failed login attempts and temporarily blocks IPs with too many failures
|
||||
*/
|
||||
|
||||
const logger = require("../config/logger");
|
||||
|
||||
// Store failed attempts in memory (use Redis in production)
|
||||
const failedAttempts = new Map();
|
||||
const blockedIPs = new Map();
|
||||
|
||||
// Configuration
|
||||
const MAX_FAILED_ATTEMPTS = 5;
|
||||
const BLOCK_DURATION = 15 * 60 * 1000; // 15 minutes
|
||||
const ATTEMPT_WINDOW = 15 * 60 * 1000; // 15 minutes
|
||||
const CLEANUP_INTERVAL = 60 * 1000; // 1 minute
|
||||
|
||||
/**
|
||||
* Clean up old entries periodically
|
||||
*/
|
||||
const cleanup = () => {
|
||||
const now = Date.now();
|
||||
|
||||
// Clean up failed attempts
|
||||
for (const [ip, data] of failedAttempts.entries()) {
|
||||
if (now - data.firstAttempt > ATTEMPT_WINDOW) {
|
||||
failedAttempts.delete(ip);
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up blocked IPs
|
||||
for (const [ip, blockTime] of blockedIPs.entries()) {
|
||||
if (now - blockTime > BLOCK_DURATION) {
|
||||
blockedIPs.delete(ip);
|
||||
logger.info("IP unblocked after cooldown", { ip });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Start cleanup interval
|
||||
setInterval(cleanup, CLEANUP_INTERVAL);
|
||||
|
||||
/**
|
||||
* Record a failed login attempt
|
||||
* @param {string} ip - IP address
|
||||
*/
|
||||
const recordFailedAttempt = (ip) => {
|
||||
const now = Date.now();
|
||||
|
||||
if (!failedAttempts.has(ip)) {
|
||||
failedAttempts.set(ip, {
|
||||
count: 1,
|
||||
firstAttempt: now,
|
||||
});
|
||||
} else {
|
||||
const data = failedAttempts.get(ip);
|
||||
|
||||
// Reset if outside window
|
||||
if (now - data.firstAttempt > ATTEMPT_WINDOW) {
|
||||
data.count = 1;
|
||||
data.firstAttempt = now;
|
||||
} else {
|
||||
data.count++;
|
||||
}
|
||||
|
||||
// Block if too many attempts
|
||||
if (data.count >= MAX_FAILED_ATTEMPTS) {
|
||||
blockedIPs.set(ip, now);
|
||||
logger.warn("IP blocked due to failed login attempts", {
|
||||
ip,
|
||||
attempts: data.count,
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Reset failed attempts for an IP (on successful login)
|
||||
* @param {string} ip - IP address
|
||||
*/
|
||||
const resetFailedAttempts = (ip) => {
|
||||
failedAttempts.delete(ip);
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if an IP is currently blocked
|
||||
* @param {string} ip - IP address
|
||||
* @returns {boolean}
|
||||
*/
|
||||
const isBlocked = (ip) => {
|
||||
if (!blockedIPs.has(ip)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const blockTime = blockedIPs.get(ip);
|
||||
const now = Date.now();
|
||||
|
||||
// Check if block has expired
|
||||
if (now - blockTime > BLOCK_DURATION) {
|
||||
blockedIPs.delete(ip);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get remaining block time in seconds
|
||||
* @param {string} ip - IP address
|
||||
* @returns {number} Seconds remaining
|
||||
*/
|
||||
const getRemainingBlockTime = (ip) => {
|
||||
if (!blockedIPs.has(ip)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const blockTime = blockedIPs.get(ip);
|
||||
const elapsed = Date.now() - blockTime;
|
||||
const remaining = Math.max(0, BLOCK_DURATION - elapsed);
|
||||
|
||||
return Math.ceil(remaining / 1000);
|
||||
};
|
||||
|
||||
/**
|
||||
* Middleware to check if IP is blocked
|
||||
*/
|
||||
const checkBlocked = (req, res, next) => {
|
||||
const ip = req.ip || req.connection.remoteAddress;
|
||||
|
||||
if (isBlocked(ip)) {
|
||||
const remainingSeconds = getRemainingBlockTime(ip);
|
||||
logger.warn("Blocked IP attempted access", { ip, path: req.path });
|
||||
|
||||
return res.status(429).json({
|
||||
success: false,
|
||||
message: `Too many failed attempts. Please try again in ${Math.ceil(
|
||||
remainingSeconds / 60
|
||||
)} minutes.`,
|
||||
retryAfter: remainingSeconds,
|
||||
});
|
||||
}
|
||||
|
||||
next();
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
recordFailedAttempt,
|
||||
resetFailedAttempts,
|
||||
isBlocked,
|
||||
checkBlocked,
|
||||
getRemainingBlockTime,
|
||||
};
|
||||
Reference in New Issue
Block a user