e35ac6a41c
Security Enhancements: - Fix SQL injection vulnerabilities by replacing whereRaw queries with parameterized queries - Add LIKE pattern escaping to prevent SQL injection in search functionality - Implement account lockout protection (5 failed attempts = 30 min lockout) - Add comprehensive login attempt tracking and audit trail - Enhance JWT tokens with issuer validation, IP tracking, and password change detection - Add logout endpoint and session management - Prevent user enumeration with generic error messages Database Changes: - Add login_attempts table for authentication tracking - Add security columns to admin_users (password_changed_at, last_login_ip, two_factor_enabled) New Security Features: - Brute force protection with configurable lockout duration - Automatic cleanup of old login attempts - Enhanced authentication middleware with stricter validation - Monitoring scripts for security health checks All fixes are backward compatible and production-ready with rollback plans included. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
136 lines
4.1 KiB
JavaScript
Executable File
136 lines
4.1 KiB
JavaScript
Executable File
#!/usr/bin/env node
|
|
|
|
/**
|
|
* Monitor authentication health after deployment
|
|
* Run this after activating enhanced auth to watch for issues
|
|
*/
|
|
|
|
const { db } = require('../src/database/db');
|
|
|
|
console.log('=== Authentication Health Monitor ===\n');
|
|
console.log('Monitoring auth system... (Press Ctrl+C to stop)\n');
|
|
|
|
// Color codes
|
|
const GREEN = '\x1b[32m';
|
|
const RED = '\x1b[31m';
|
|
const YELLOW = '\x1b[33m';
|
|
const RESET = '\x1b[0m';
|
|
|
|
let previousStats = {
|
|
totalAttempts: 0,
|
|
failedAttempts: 0,
|
|
lockedAccounts: 0
|
|
};
|
|
|
|
async function getAuthStats() {
|
|
try {
|
|
const stats = {};
|
|
|
|
// Total login attempts in last hour
|
|
const totalAttempts = await db('login_attempts')
|
|
.where('attempt_time', '>', new Date(Date.now() - 60 * 60 * 1000).toISOString())
|
|
.count('id as count')
|
|
.first();
|
|
stats.totalAttempts = totalAttempts.count || 0;
|
|
|
|
// Failed attempts in last hour
|
|
const failedAttempts = await db('login_attempts')
|
|
.where('attempt_time', '>', new Date(Date.now() - 60 * 60 * 1000).toISOString())
|
|
.where('success', false)
|
|
.count('id as count')
|
|
.first();
|
|
stats.failedAttempts = failedAttempts.count || 0;
|
|
|
|
// Currently locked accounts
|
|
const recentWindow = new Date(Date.now() - 15 * 60 * 1000);
|
|
const lockedAccounts = await db('login_attempts')
|
|
.select('identifier')
|
|
.where('success', false)
|
|
.where('attempt_time', '>=', recentWindow.toISOString())
|
|
.groupBy('identifier')
|
|
.havingRaw('COUNT(*) >= 5');
|
|
stats.lockedAccounts = lockedAccounts.length;
|
|
|
|
// Success rate
|
|
stats.successRate = stats.totalAttempts > 0
|
|
? ((stats.totalAttempts - stats.failedAttempts) / stats.totalAttempts * 100).toFixed(1)
|
|
: 100;
|
|
|
|
// Recent failures (last 5 minutes)
|
|
const recentFailures = await db('login_attempts')
|
|
.where('success', false)
|
|
.where('attempt_time', '>', new Date(Date.now() - 5 * 60 * 1000).toISOString())
|
|
.orderBy('attempt_time', 'desc')
|
|
.limit(5)
|
|
.select('identifier', 'ip_address', 'attempt_time');
|
|
stats.recentFailures = recentFailures;
|
|
|
|
return stats;
|
|
} catch (error) {
|
|
return { error: error.message };
|
|
}
|
|
}
|
|
|
|
async function displayStats() {
|
|
const stats = await getAuthStats();
|
|
|
|
if (stats.error) {
|
|
console.log(`${RED}Error: ${stats.error}${RESET}`);
|
|
console.log('Enhanced auth might not be active yet.\n');
|
|
return;
|
|
}
|
|
|
|
// Clear console for clean display
|
|
console.clear();
|
|
console.log('=== Authentication Health Monitor ===\n');
|
|
console.log(new Date().toLocaleString());
|
|
console.log('─'.repeat(50));
|
|
|
|
// Display metrics
|
|
console.log(`\n📊 Last Hour Statistics:`);
|
|
console.log(` Total Login Attempts: ${stats.totalAttempts}`);
|
|
console.log(` Failed Attempts: ${stats.failedAttempts}`);
|
|
console.log(` Success Rate: ${stats.successRate}%`);
|
|
console.log(` Currently Locked: ${stats.lockedAccounts} accounts`);
|
|
|
|
// Alerts
|
|
if (stats.failedAttempts > previousStats.failedAttempts + 10) {
|
|
console.log(`\n${RED}⚠️ ALERT: Spike in failed login attempts!${RESET}`);
|
|
}
|
|
|
|
if (stats.lockedAccounts > 5) {
|
|
console.log(`\n${YELLOW}⚠️ WARNING: Multiple accounts locked (${stats.lockedAccounts})${RESET}`);
|
|
}
|
|
|
|
if (stats.successRate < 50) {
|
|
console.log(`\n${RED}⚠️ ALERT: Low success rate (${stats.successRate}%)${RESET}`);
|
|
}
|
|
|
|
// Recent failures
|
|
if (stats.recentFailures && stats.recentFailures.length > 0) {
|
|
console.log(`\n📋 Recent Failed Attempts (last 5 min):`);
|
|
stats.recentFailures.forEach(failure => {
|
|
const time = new Date(failure.attempt_time).toLocaleTimeString();
|
|
console.log(` ${time} - ${failure.identifier} from ${failure.ip_address}`);
|
|
});
|
|
}
|
|
|
|
// Health status
|
|
console.log(`\n✅ Status: ${stats.failedAttempts === 0 ? 'Healthy' : 'Active'}`);
|
|
console.log('\nPress Ctrl+C to stop monitoring\n');
|
|
|
|
previousStats = stats;
|
|
}
|
|
|
|
// Monitor every 10 seconds
|
|
setInterval(displayStats, 10000);
|
|
|
|
// Initial display
|
|
displayStats();
|
|
|
|
// Graceful shutdown
|
|
process.on('SIGINT', async () => {
|
|
console.log('\nStopping monitor...');
|
|
await db.destroy();
|
|
process.exit(0);
|
|
}); |