Files
picpeak/backend/scripts/monitor-auth-health.js
T
paul e35ac6a41c
Test Gitea Actions / test (push) Successful in 20s
continuous-integration/drone/push Build is passing
feat: implement critical security fixes for SQL injection and authentication vulnerabilities
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>
2025-07-13 00:40:05 +02:00

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);
});