Initial commit - Project start (July 17, 2025)
Mirror to GitHub / mirror (push) Successful in 26s
Test and Lint / backend-test (push) Successful in 1m11s
continuous-integration/drone/push Build is passing
Test and Lint / frontend-test (push) Successful in 2m28s
Version and Release / version-bump (push) Successful in 32s
Version and Release / trigger-drone (push) Has been skipped
Mirror to GitHub / mirror (push) Successful in 26s
Test and Lint / backend-test (push) Successful in 1m11s
continuous-integration/drone/push Build is passing
Test and Lint / frontend-test (push) Successful in 2m28s
Version and Release / version-bump (push) Successful in 32s
Version and Release / trigger-drone (push) Has been skipped
Original: feat: enhance security logging and ensure rate limit blocks are properly tracked - Add comprehensive logging for rate limit blocks with full request details - IP address (with proper proxy detection), user agent, headers, timestamps - Rate limit info (current count, limit, remaining, reset time) - Separate tracking for auth vs general endpoints - Enhance authentication failure logging - JWT validation failures with detailed error info - Admin auth attempts without token - Failed token validation with user context - All events include IP, path, method, user agent - Improve Winston logger configuration for production - Add automatic log rotation (10MB errors, 50MB combined) - Create separate security.log for auth/rate limit events - Ensure logs directory exists automatically - Add structured JSON format for log aggregation - Support container logging with LOG_TO_CONSOLE env var - Create comprehensive documentation - Security logging guide with examples - Monitoring recommendations - Configuration reference - Add test script to verify logging functionality All rate limit settings remain configurable via admin panel: - Window duration, max requests, auth limits - Skip authenticated requests option - Public endpoints only option 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,190 @@
|
||||
/**
|
||||
* Authentication Security Utilities
|
||||
* Provides enhanced security features for authentication
|
||||
*/
|
||||
|
||||
const { db } = require('../database/db');
|
||||
const { formatBoolean } = require('./dbCompat');
|
||||
const logger = require('./logger');
|
||||
|
||||
// Configuration constants
|
||||
const MAX_LOGIN_ATTEMPTS = 5;
|
||||
const LOCKOUT_DURATION = 30 * 60 * 1000; // 30 minutes in milliseconds
|
||||
const ATTEMPT_WINDOW = 15 * 60 * 1000; // 15 minutes window for counting attempts
|
||||
|
||||
/**
|
||||
* Track failed login attempt
|
||||
* @param {string} identifier - Username or email
|
||||
* @param {string} ipAddress - IP address of the attempt
|
||||
* @param {string} userAgent - User agent string
|
||||
*/
|
||||
async function trackFailedAttempt(identifier, ipAddress, userAgent) {
|
||||
try {
|
||||
await db('login_attempts').insert({
|
||||
identifier,
|
||||
ip_address: ipAddress,
|
||||
user_agent: userAgent,
|
||||
attempt_time: new Date().toISOString(),
|
||||
success: false
|
||||
});
|
||||
|
||||
// Log security event
|
||||
logger.warn('Failed login attempt', {
|
||||
identifier,
|
||||
ipAddress,
|
||||
userAgent,
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Error tracking failed login attempt:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Track successful login
|
||||
* @param {string} identifier - Username or email
|
||||
* @param {string} ipAddress - IP address
|
||||
* @param {string} userAgent - User agent string
|
||||
*/
|
||||
async function trackSuccessfulLogin(identifier, ipAddress, userAgent) {
|
||||
try {
|
||||
await db('login_attempts').insert({
|
||||
identifier,
|
||||
ip_address: ipAddress,
|
||||
user_agent: userAgent,
|
||||
attempt_time: new Date().toISOString(),
|
||||
success: true
|
||||
});
|
||||
|
||||
// Clear old failed attempts for this user
|
||||
const cutoffTime = new Date(Date.now() - ATTEMPT_WINDOW);
|
||||
await db('login_attempts')
|
||||
.where('identifier', identifier)
|
||||
.where('success', formatBoolean(false))
|
||||
.where('attempt_time', '<', cutoffTime.toISOString())
|
||||
.delete();
|
||||
} catch (error) {
|
||||
logger.error('Error tracking successful login:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if account is locked due to too many failed attempts
|
||||
* @param {string} identifier - Username or email
|
||||
* @returns {Promise<{isLocked: boolean, remainingTime?: number}>}
|
||||
*/
|
||||
async function checkAccountLockout(identifier) {
|
||||
try {
|
||||
const recentWindow = new Date(Date.now() - ATTEMPT_WINDOW);
|
||||
|
||||
// Get recent failed attempts
|
||||
const failedAttempts = await db('login_attempts')
|
||||
.where('identifier', identifier)
|
||||
.where('success', formatBoolean(false))
|
||||
.where('attempt_time', '>=', recentWindow.toISOString())
|
||||
.orderBy('attempt_time', 'desc')
|
||||
.limit(MAX_LOGIN_ATTEMPTS);
|
||||
|
||||
if (failedAttempts.length >= MAX_LOGIN_ATTEMPTS) {
|
||||
// Check if still within lockout period
|
||||
const oldestAttempt = failedAttempts[failedAttempts.length - 1];
|
||||
const lockoutEnd = new Date(oldestAttempt.attempt_time).getTime() + LOCKOUT_DURATION;
|
||||
const now = Date.now();
|
||||
|
||||
if (now < lockoutEnd) {
|
||||
return {
|
||||
isLocked: true,
|
||||
remainingTime: Math.ceil((lockoutEnd - now) / 1000) // seconds
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return { isLocked: false };
|
||||
} catch (error) {
|
||||
logger.error('Error checking account lockout:', error);
|
||||
return { isLocked: false }; // Fail open to avoid locking users out due to errors
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for suspicious login patterns
|
||||
* @param {string} identifier - Username or email
|
||||
* @param {string} ipAddress - Current IP address
|
||||
* @returns {Promise<boolean>} - True if suspicious
|
||||
*/
|
||||
async function checkSuspiciousActivity(identifier, ipAddress) {
|
||||
try {
|
||||
// Check for rapid attempts from different IPs
|
||||
const recentWindow = new Date(Date.now() - 5 * 60 * 1000); // 5 minutes
|
||||
|
||||
const recentAttempts = await db('login_attempts')
|
||||
.where('identifier', identifier)
|
||||
.where('attempt_time', '>=', recentWindow.toISOString())
|
||||
.select('ip_address')
|
||||
.distinct('ip_address');
|
||||
|
||||
// If more than 3 different IPs in 5 minutes, it's suspicious
|
||||
if (recentAttempts.length > 3) {
|
||||
logger.warn('Suspicious login activity detected', {
|
||||
identifier,
|
||||
uniqueIPs: recentAttempts.length,
|
||||
currentIP: ipAddress
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
} catch (error) {
|
||||
logger.error('Error checking suspicious activity:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get generic error message to prevent user enumeration
|
||||
* @returns {string}
|
||||
*/
|
||||
function getGenericAuthError() {
|
||||
return 'Invalid credentials';
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up old login attempts (should be run periodically)
|
||||
*/
|
||||
async function cleanupOldAttempts() {
|
||||
try {
|
||||
const cutoffDate = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000); // 7 days
|
||||
|
||||
const deleted = await db('login_attempts')
|
||||
.where('attempt_time', '<', cutoffDate.toISOString())
|
||||
.delete();
|
||||
|
||||
if (deleted > 0) {
|
||||
logger.info(`Cleaned up ${deleted} old login attempts`);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Error cleaning up login attempts:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize cleanup job
|
||||
*/
|
||||
function initializeCleanupJob() {
|
||||
// Run cleanup every 24 hours
|
||||
setInterval(cleanupOldAttempts, 24 * 60 * 60 * 1000);
|
||||
|
||||
// Run initial cleanup
|
||||
cleanupOldAttempts();
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
trackFailedAttempt,
|
||||
trackSuccessfulLogin,
|
||||
checkAccountLockout,
|
||||
checkSuspiciousActivity,
|
||||
getGenericAuthError,
|
||||
initializeCleanupJob,
|
||||
MAX_LOGIN_ATTEMPTS,
|
||||
LOCKOUT_DURATION
|
||||
};
|
||||
Reference in New Issue
Block a user