Files
picpeak/backend/scripts/test-routes-after-security-fix.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

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