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>
171 lines
5.0 KiB
JavaScript
171 lines
5.0 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
/**
|
|
* Test script to verify routes work correctly after SQL security fixes
|
|
* Run this before deploying to production
|
|
*/
|
|
|
|
const request = require('supertest');
|
|
const app = require('../src/app');
|
|
const { db } = require('../src/database/db');
|
|
const jwt = require('jsonwebtoken');
|
|
|
|
// Generate admin token for testing
|
|
const adminToken = jwt.sign(
|
|
{ id: 1, username: 'admin', role: 'admin' },
|
|
process.env.JWT_SECRET || 'test-secret'
|
|
);
|
|
|
|
console.log('=== Testing Routes After SQL Security Fixes ===\n');
|
|
|
|
let passed = 0;
|
|
let failed = 0;
|
|
|
|
async function testRoute(description, testFn) {
|
|
try {
|
|
await testFn();
|
|
console.log(`✅ ${description}`);
|
|
passed++;
|
|
} catch (error) {
|
|
console.log(`❌ ${description}`);
|
|
console.error(` Error: ${error.message}`);
|
|
failed++;
|
|
}
|
|
}
|
|
|
|
async function runTests() {
|
|
// Test Dashboard Stats (uses whereRaw fixes)
|
|
await testRoute('Dashboard stats endpoint', async () => {
|
|
const res = await request(app)
|
|
.get('/api/admin/dashboard/stats')
|
|
.set('Authorization', `Bearer ${adminToken}`)
|
|
.expect(200);
|
|
|
|
if (!res.body.hasOwnProperty('activeEvents')) {
|
|
throw new Error('Missing activeEvents in response');
|
|
}
|
|
});
|
|
|
|
// Test Analytics with days parameter (uses sanitizeDays)
|
|
await testRoute('Analytics with valid days parameter', async () => {
|
|
const res = await request(app)
|
|
.get('/api/admin/dashboard/analytics?days=7')
|
|
.set('Authorization', `Bearer ${adminToken}`)
|
|
.expect(200);
|
|
|
|
if (!res.body.chartData || res.body.chartData.length !== 7) {
|
|
throw new Error('Invalid chart data');
|
|
}
|
|
});
|
|
|
|
// Test Analytics with SQL injection attempt in days
|
|
await testRoute('Analytics rejects SQL injection in days parameter', async () => {
|
|
const res = await request(app)
|
|
.get('/api/admin/dashboard/analytics?days=7; DROP TABLE events; --')
|
|
.set('Authorization', `Bearer ${adminToken}`)
|
|
.expect(200);
|
|
|
|
// Should default to 7 days
|
|
if (res.body.chartData.length !== 7) {
|
|
throw new Error('Days parameter not properly sanitized');
|
|
}
|
|
});
|
|
|
|
// Test Event search with normal text (uses escapeLikePattern)
|
|
await testRoute('Event search with normal text', async () => {
|
|
const res = await request(app)
|
|
.get('/api/admin/events?search=test')
|
|
.set('Authorization', `Bearer ${adminToken}`)
|
|
.expect(200);
|
|
|
|
if (!res.body.hasOwnProperty('events')) {
|
|
throw new Error('Missing events in response');
|
|
}
|
|
});
|
|
|
|
// Test Event search with special characters
|
|
await testRoute('Event search with special characters', async () => {
|
|
const res = await request(app)
|
|
.get('/api/admin/events?search=50%_test')
|
|
.set('Authorization', `Bearer ${adminToken}`)
|
|
.expect(200);
|
|
|
|
// Should handle special chars safely
|
|
if (!res.body.hasOwnProperty('events')) {
|
|
throw new Error('Failed to handle special characters');
|
|
}
|
|
});
|
|
|
|
// Test Event search with SQL injection attempt
|
|
await testRoute('Event search prevents SQL injection', async () => {
|
|
const res = await request(app)
|
|
.get("/api/admin/events?search=' OR 1=1 --")
|
|
.set('Authorization', `Bearer ${adminToken}`)
|
|
.expect(200);
|
|
|
|
// Should return empty results, not all events
|
|
if (!res.body.hasOwnProperty('events')) {
|
|
throw new Error('SQL injection may not be prevented');
|
|
}
|
|
});
|
|
|
|
// Test Photo search (if event exists)
|
|
await testRoute('Photo search functionality', async () => {
|
|
// First check if we have any events
|
|
const event = await db('events').first();
|
|
if (event) {
|
|
const res = await request(app)
|
|
.get(`/api/admin/events/${event.id}/photos?search=test`)
|
|
.set('Authorization', `Bearer ${adminToken}`)
|
|
.expect(200);
|
|
|
|
if (!res.body.hasOwnProperty('photos')) {
|
|
throw new Error('Missing photos in response');
|
|
}
|
|
}
|
|
});
|
|
|
|
// Test Activity endpoint
|
|
await testRoute('Activity log endpoint', async () => {
|
|
const res = await request(app)
|
|
.get('/api/admin/dashboard/activity?limit=10')
|
|
.set('Authorization', `Bearer ${adminToken}`)
|
|
.expect(200);
|
|
|
|
if (!Array.isArray(res.body)) {
|
|
throw new Error('Activity should return array');
|
|
}
|
|
});
|
|
|
|
// Test Health endpoint
|
|
await testRoute('Health check endpoint', async () => {
|
|
const res = await request(app)
|
|
.get('/api/admin/dashboard/health')
|
|
.set('Authorization', `Bearer ${adminToken}`)
|
|
.expect(200);
|
|
|
|
if (!res.body.hasOwnProperty('overall')) {
|
|
throw new Error('Missing overall health status');
|
|
}
|
|
});
|
|
|
|
// Summary
|
|
console.log('\n=== Test Summary ===');
|
|
console.log(`Total tests: ${passed + failed}`);
|
|
console.log(`Passed: ${passed}`);
|
|
console.log(`Failed: ${failed}`);
|
|
|
|
if (failed === 0) {
|
|
console.log('\n✅ All route tests passed! Safe to deploy.');
|
|
process.exit(0);
|
|
} else {
|
|
console.log('\n❌ Some tests failed. Review the fixes before deploying.');
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
// Run tests
|
|
runTests().catch(error => {
|
|
console.error('Test runner error:', error);
|
|
process.exit(1);
|
|
}); |