chore: clean up obsolete files and documentation
Test and Lint / backend-test (push) Successful in 1m5s
continuous-integration/drone/push Build is passing
Test and Lint / frontend-test (push) Successful in 2m12s
Version and Release / version-bump (push) Successful in 39s
Version and Release / trigger-drone (push) Successful in 3s

- Remove completed auth migration documentation (13 files)
- Delete unused test and one-time scripts (23 files)
- Remove backup files and old logs
- Clean up duplicate/empty database files
- Remove old migration backup file
- Delete root level setup scripts

Total: ~185KB of obsolete files removed
All active functionality preserved

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-07-13 21:48:05 +02:00
parent e343106af5
commit ba6ee55bf7
46 changed files with 0 additions and 4571 deletions
-80
View File
@@ -1,80 +0,0 @@
#!/usr/bin/env node
/**
* Activate enhanced authentication in server.js
* This script safely updates the server configuration
*/
const fs = require('fs').promises;
const path = require('path');
async function activateEnhancedAuth() {
console.log('=== Activating Enhanced Authentication ===\n');
try {
const serverPath = path.join(__dirname, '../server.js');
// Read current server.js
let serverContent = await fs.readFile(serverPath, 'utf8');
// Backup current server.js
const backupPath = `${serverPath}.backup.${Date.now()}`;
await fs.writeFile(backupPath, serverContent);
console.log(`✓ Created backup: ${path.basename(backupPath)}`);
// Check current state
if (serverContent.includes("require('./src/routes/auth-enhanced')")) {
console.log('! Enhanced auth already active');
return;
}
// Replace auth routes import
const originalLine = "const authRoutes = require('./src/routes/auth');";
const enhancedLine = "const authRoutes = require('./src/routes/auth-enhanced');";
if (!serverContent.includes(originalLine)) {
console.log('✗ Could not find original auth import line');
console.log('Please manually update server.js');
return;
}
serverContent = serverContent.replace(originalLine, enhancedLine);
console.log('✓ Updated auth routes import');
// Add cleanup job initialization after database init
const dbInitLine = 'initializeDatabase()';
const cleanupAddition = `
// Initialize auth security cleanup job
const { initializeCleanupJob } = require('./src/utils/authSecurity');
initializeCleanupJob();
`;
if (!serverContent.includes('initializeCleanupJob')) {
const dbInitIndex = serverContent.indexOf(dbInitLine);
if (dbInitIndex !== -1) {
const insertPoint = serverContent.indexOf('\n', dbInitIndex) + 1;
serverContent = serverContent.slice(0, insertPoint) + cleanupAddition + serverContent.slice(insertPoint);
console.log('✓ Added cleanup job initialization');
}
}
// Write updated server.js
await fs.writeFile(serverPath, serverContent);
console.log('✓ Updated server.js');
console.log('\n✅ Enhanced authentication activated!');
console.log('\nNext steps:');
console.log('1. Restart the backend:');
console.log(' docker-compose restart backend');
console.log('\n2. Monitor auth health:');
console.log(' node scripts/monitor-auth-health.js');
console.log('\n3. To rollback if needed:');
console.log(` cp ${path.basename(backupPath)} server.js`);
console.log(' docker-compose restart backend');
} catch (error) {
console.error('❌ Error activating enhanced auth:', error);
}
}
activateEnhancedAuth();
-94
View File
@@ -1,94 +0,0 @@
#!/usr/bin/env node
/**
* Add authentication security tables to existing database
*/
const { db } = require('../src/database/db');
async function addAuthTables() {
console.log('Adding authentication security tables...\n');
try {
// 1. Create login_attempts table
const hasLoginAttempts = await db.schema.hasTable('login_attempts');
if (!hasLoginAttempts) {
await db.schema.createTable('login_attempts', table => {
table.increments('id').primary();
table.string('identifier').notNullable();
table.string('ip_address', 45).notNullable();
table.text('user_agent');
table.timestamp('attempt_time').defaultTo(db.fn.now());
table.boolean('success').defaultTo(false);
// Indexes for performance
table.index('identifier');
table.index('attempt_time');
table.index(['identifier', 'success', 'attempt_time']);
});
console.log('✓ Created login_attempts table');
} else {
console.log('! login_attempts table already exists');
}
// 2. Add columns to admin_users
const hasPasswordChangedAt = await db.schema.hasColumn('admin_users', 'password_changed_at');
if (!hasPasswordChangedAt) {
await db.schema.table('admin_users', table => {
table.timestamp('password_changed_at').nullable();
});
console.log('✓ Added password_changed_at column');
}
const hasLastLoginIp = await db.schema.hasColumn('admin_users', 'last_login_ip');
if (!hasLastLoginIp) {
await db.schema.table('admin_users', table => {
table.string('last_login_ip', 45).nullable();
});
console.log('✓ Added last_login_ip column');
}
const hasTwoFactorEnabled = await db.schema.hasColumn('admin_users', 'two_factor_enabled');
if (!hasTwoFactorEnabled) {
await db.schema.table('admin_users', table => {
table.boolean('two_factor_enabled').defaultTo(false);
});
console.log('✓ Added two_factor_enabled column');
}
const hasTwoFactorSecret = await db.schema.hasColumn('admin_users', 'two_factor_secret');
if (!hasTwoFactorSecret) {
await db.schema.table('admin_users', table => {
table.string('two_factor_secret').nullable();
});
console.log('✓ Added two_factor_secret column');
}
// 3. Verify everything
console.log('\nVerifying tables...');
const loginAttemptsInfo = await db('login_attempts').columnInfo();
console.log('✓ login_attempts columns:', Object.keys(loginAttemptsInfo).join(', '));
const adminUsersInfo = await db('admin_users').columnInfo();
const securityColumns = ['password_changed_at', 'last_login_ip', 'two_factor_enabled', 'two_factor_secret'];
const hasAllColumns = securityColumns.every(col => adminUsersInfo[col]);
if (hasAllColumns) {
console.log('✓ All security columns present in admin_users');
} else {
console.log('✗ Some security columns missing from admin_users');
}
console.log('\n✅ Authentication security tables ready!');
await db.destroy();
process.exit(0);
} catch (error) {
console.error('\n❌ Error adding auth tables:', error);
await db.destroy();
process.exit(1);
}
}
addAuthTables();
@@ -1,71 +0,0 @@
#!/usr/bin/env node
/**
* Add token revocation tables to existing database
*/
const { db } = require('../src/database/db');
async function addTokenRevocationTables() {
console.log('Adding token revocation tables...\n');
try {
// 1. Create revoked_tokens table
const hasRevokedTokens = await db.schema.hasTable('revoked_tokens');
if (!hasRevokedTokens) {
await db.schema.createTable('revoked_tokens', table => {
table.increments('id').primary();
table.string('token_id').notNullable().unique();
table.integer('user_id').nullable();
table.string('token_type', 20);
table.timestamp('revoked_at').defaultTo(db.fn.now());
table.timestamp('expires_at').notNullable();
table.string('reason', 100);
table.text('metadata');
// Indexes
table.index('token_id');
table.index('user_id');
table.index('expires_at');
});
console.log('✓ Created revoked_tokens table');
} else {
console.log('! revoked_tokens table already exists');
}
// 2. Create user_token_revocations table
const hasUserRevocations = await db.schema.hasTable('user_token_revocations');
if (!hasUserRevocations) {
await db.schema.createTable('user_token_revocations', table => {
table.integer('user_id').primary();
table.timestamp('revoked_at').notNullable();
table.string('reason', 100);
table.index('revoked_at');
});
console.log('✓ Created user_token_revocations table');
} else {
console.log('! user_token_revocations table already exists');
}
// 3. Verify tables
console.log('\nVerifying tables...');
const revokedTokensInfo = await db('revoked_tokens').columnInfo();
console.log('✓ revoked_tokens columns:', Object.keys(revokedTokensInfo).join(', '));
const userRevocationsInfo = await db('user_token_revocations').columnInfo();
console.log('✓ user_token_revocations columns:', Object.keys(userRevocationsInfo).join(', '));
console.log('\n✅ Token revocation tables ready!');
await db.destroy();
process.exit(0);
} catch (error) {
console.error('\n❌ Error adding token revocation tables:', error);
await db.destroy();
process.exit(1);
}
}
addTokenRevocationTables();
-96
View File
@@ -1,96 +0,0 @@
#!/usr/bin/env node
/**
* Check Docker deployment status for security fixes
*/
console.log('=== Docker Deployment Status Check ===\n');
// Color codes
const GREEN = '\x1b[32m';
const RED = '\x1b[31m';
const YELLOW = '\x1b[33m';
const RESET = '\x1b[0m';
let allGood = true;
// Check SQL Security
console.log('1. SQL Injection Fixes:');
try {
const { sanitizeDays, escapeLikePattern } = require('../src/utils/sqlSecurity');
console.log(`${GREEN}${RESET} sqlSecurity.js exists`);
console.log(`${GREEN}${RESET} Security functions available`);
} catch (e) {
console.log(`${RED}${RESET} sqlSecurity.js missing`);
allGood = false;
}
// Check Auth Security
console.log('\n2. Authentication Security:');
try {
const authSec = require('../src/utils/authSecurity');
console.log(`${GREEN}${RESET} authSecurity.js exists`);
const authEnhanced = require('../src/middleware/auth-enhanced');
console.log(`${GREEN}${RESET} auth-enhanced middleware exists`);
const authRoutes = require('../src/routes/auth-enhanced');
console.log(`${GREEN}${RESET} auth-enhanced routes exist`);
} catch (e) {
console.log(`${YELLOW}!${RESET} Auth security files exist but not active`);
}
// Check Database
console.log('\n3. Database Status:');
const { db } = require('../src/database/db');
async function checkDatabase() {
try {
// Check login_attempts table
await db('login_attempts').count();
console.log(`${GREEN}${RESET} login_attempts table exists`);
} catch (e) {
console.log(`${YELLOW}!${RESET} login_attempts table not created (run migrations)`);
}
try {
// Check admin_users columns
await db('admin_users').select('password_changed_at').limit(1);
console.log(`${GREEN}${RESET} Auth security columns exist`);
} catch (e) {
console.log(`${YELLOW}!${RESET} Auth security columns missing (run migrations)`);
}
// Close database connection
await db.destroy();
}
// Check server configuration
console.log('\n4. Server Configuration:');
const fs = require('fs');
const serverContent = fs.readFileSync('./server.js', 'utf8');
if (serverContent.includes("require('./src/routes/auth-enhanced')")) {
console.log(`${GREEN}${RESET} Using enhanced auth routes`);
} else if (serverContent.includes("require('./src/routes/auth')")) {
console.log(`${YELLOW}!${RESET} Using original auth routes (enhanced not active)`);
}
if (serverContent.includes('initializeCleanupJob')) {
console.log(`${GREEN}${RESET} Auth cleanup job initialized`);
} else {
console.log(`${YELLOW}!${RESET} Auth cleanup job not initialized`);
}
// Run async checks
checkDatabase().then(() => {
console.log('\n=== Summary ===');
if (allGood) {
console.log(`${GREEN}All security fixes are deployed!${RESET}`);
} else {
console.log(`${YELLOW}Some security features need activation:${RESET}`);
console.log('1. Run migrations: npx knex migrate:latest');
console.log('2. Update server.js to use auth-enhanced routes');
console.log('3. Restart the container');
}
});
-56
View File
@@ -1,56 +0,0 @@
const knex = require('knex')({
client: 'sqlite3',
connection: { filename: '/app/data/photo_sharing.db' },
useNullAsDefault: true
});
async function debugEventPhotos() {
try {
// Get all photos for event 12
const photos = await knex('photos')
.where('event_id', 12)
.select('id', 'filename', 'path', 'thumbnail_path')
.orderBy('id');
console.log('Total photos for event 12:', photos.length);
console.log('\nSample photos:');
// Show first few and specific IDs that were failing
const sampleIds = [1686, 1687, 1688, 1689, 1715, 1717, 1718, 1719];
const samples = photos.filter(p => sampleIds.includes(p.id));
samples.forEach(p => {
console.log(`\nID ${p.id}: ${p.filename}`);
console.log(` Path: ${p.path}`);
console.log(` Thumbnail: ${p.thumbnail_path}`);
});
// Check for any photos without thumbnails
const noThumbs = photos.filter(p => !p.thumbnail_path);
if (noThumbs.length > 0) {
console.log(`\nPhotos without thumbnails: ${noThumbs.length}`);
noThumbs.forEach(p => console.log(` ID ${p.id}: ${p.filename}`));
}
// Check file existence for failing photos
const fs = require('fs').promises;
console.log('\nChecking file existence for samples:');
for (const photo of samples) {
const thumbPath = `/app/storage/${photo.thumbnail_path}`;
try {
await fs.access(thumbPath);
console.log(`✓ ID ${photo.id}: Thumbnail exists at ${thumbPath}`);
} catch (err) {
console.log(`✗ ID ${photo.id}: Thumbnail NOT FOUND at ${thumbPath}`);
}
}
} catch (error) {
console.error('Error:', error);
} finally {
knex.destroy();
}
}
debugEventPhotos();
-81
View File
@@ -1,81 +0,0 @@
#!/usr/bin/env node
const sqlite3 = require('sqlite3').verbose();
const fs = require('fs');
const path = require('path');
// Connect to the database
const dbPath = '/app/data/photo_sharing.db';
console.log(`Connecting to database at: ${dbPath}`);
const db = new sqlite3.Database(dbPath, sqlite3.OPEN_READONLY, (err) => {
if (err) {
console.error('Error opening database:', err.message);
process.exit(1);
}
console.log('Connected to the SQLite database.\n');
});
// Query for photos with IDs 1688 and 1689 where event_id = 12
const query = `
SELECT p.id, p.filename, p.path, p.thumbnail_path, p.event_id,
e.slug as event_slug, e.is_active, e.is_archived
FROM photos p
JOIN events e ON p.event_id = e.id
WHERE p.id IN (1688, 1689) AND p.event_id = 12
`;
console.log('Executing query to get photo details with event information...\n');
db.all(query, [], (err, rows) => {
if (err) {
console.error('Error executing query:', err.message);
db.close();
process.exit(1);
}
console.log(`Found ${rows.length} photo(s):\n`);
if (rows.length === 0) {
console.log('No photos found matching the criteria.');
} else {
rows.forEach((row) => {
console.log('=== Photo ID:', row.id, '===');
console.log('Filename:', row.filename);
console.log('DB Path:', row.path);
console.log('DB Thumbnail Path:', row.thumbnail_path);
console.log('Event ID:', row.event_id);
console.log('Event Slug:', row.event_slug);
console.log('Event is_active:', row.is_active);
console.log('Event is_archived:', row.is_archived);
// Check file existence
const storageBase = '/app/storage';
const eventStatusDir = row.is_active ? 'active' : 'archived';
// Check full image path
const fullImagePath1 = path.join(storageBase, row.path);
const fullImagePath2 = path.join(storageBase, 'events', eventStatusDir, row.path);
console.log('\nChecking full image paths:');
console.log(` Path 1: ${fullImagePath1} - ${fs.existsSync(fullImagePath1) ? 'EXISTS' : 'NOT FOUND'}`);
console.log(` Path 2: ${fullImagePath2} - ${fs.existsSync(fullImagePath2) ? 'EXISTS' : 'NOT FOUND'}`);
// Check thumbnail path
const thumbnailPath = path.join(storageBase, row.thumbnail_path);
console.log('\nChecking thumbnail path:');
console.log(` ${thumbnailPath} - ${fs.existsSync(thumbnailPath) ? 'EXISTS' : 'NOT FOUND'}`);
console.log('\n---\n');
});
}
// Close the database connection
db.close((err) => {
if (err) {
console.error('Error closing database:', err.message);
} else {
console.log('Database connection closed.');
}
});
});
-56
View File
@@ -1,56 +0,0 @@
#!/usr/bin/env node
const sqlite3 = require('sqlite3').verbose();
const path = require('path');
// Connect to the database
const dbPath = '/app/data/photo_sharing.db';
console.log(`Connecting to database at: ${dbPath}`);
const db = new sqlite3.Database(dbPath, sqlite3.OPEN_READONLY, (err) => {
if (err) {
console.error('Error opening database:', err.message);
process.exit(1);
}
console.log('Connected to the SQLite database.');
});
// Query for photos with IDs 1688 and 1689 where event_id = 12
const query = `
SELECT id, filename, path, thumbnail_path
FROM photos
WHERE id IN (1688, 1689) AND event_id = 12
`;
console.log('\nExecuting query:', query);
db.all(query, [], (err, rows) => {
if (err) {
console.error('Error executing query:', err.message);
db.close();
process.exit(1);
}
console.log(`\nFound ${rows.length} photo(s):\n`);
if (rows.length === 0) {
console.log('No photos found matching the criteria.');
} else {
rows.forEach((row) => {
console.log('Photo ID:', row.id);
console.log('Filename:', row.filename);
console.log('Path:', row.path);
console.log('Thumbnail Path:', row.thumbnail_path);
console.log('---');
});
}
// Close the database connection
db.close((err) => {
if (err) {
console.error('Error closing database:', err.message);
} else {
console.log('\nDatabase connection closed.');
}
});
});
-132
View File
@@ -1,132 +0,0 @@
#!/bin/bash
# Authentication Security Enhancement Deployment Script
# This script helps safely deploy auth security enhancements
set -e
echo "=== PicPeak Authentication Security Deployment ==="
echo ""
# Color codes
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Check if we're in the backend directory
if [ ! -f "package.json" ] || [ ! -d "src" ]; then
echo -e "${RED}Error: Must run from backend directory${NC}"
exit 1
fi
# Function to prompt for confirmation
confirm() {
read -p "$1 (y/n): " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
echo -e "${YELLOW}Deployment cancelled${NC}"
exit 1
fi
}
echo "This script will help deploy authentication security enhancements"
echo ""
echo "Current deployment phase options:"
echo "1. Run database migrations only (safe)"
echo "2. Test enhanced auth endpoints"
echo "3. Switch to enhanced auth (full deployment)"
echo "4. Rollback to original auth"
echo ""
read -p "Select phase (1-4): " PHASE
case $PHASE in
1)
echo -e "${GREEN}Phase 1: Running database migrations${NC}"
confirm "Run migrations?"
echo "Creating backup..."
cp database.db database.db.backup.$(date +%Y%m%d_%H%M%S) 2>/dev/null || true
echo "Running migrations..."
npx knex migrate:latest
echo -e "${GREEN}✓ Migrations completed${NC}"
echo "New tables added: login_attempts"
echo "New columns added to admin_users: password_changed_at, last_login_ip"
;;
2)
echo -e "${GREEN}Phase 2: Testing enhanced auth${NC}"
# Check if server is running
if ! curl -s http://localhost:3001/health > /dev/null; then
echo -e "${RED}Server not running on port 3001${NC}"
exit 1
fi
echo "Running auth security tests..."
node scripts/test-auth-security.js
echo ""
echo "Test endpoints manually:"
echo "- Login: POST /api/auth/admin/login"
echo "- Logout: POST /api/auth/logout"
echo "- Session: GET /api/auth/session"
;;
3)
echo -e "${YELLOW}Phase 3: Full deployment${NC}"
echo "This will switch to enhanced authentication"
confirm "Deploy enhanced auth?"
# Check if migrations are run
if ! npx knex migrate:status | grep -q "015_add_login_attempts_table"; then
echo -e "${RED}Error: Migrations not run. Run phase 1 first.${NC}"
exit 1
fi
echo "Updating server.js to use enhanced auth..."
# This is where you'd update the imports
# For safety, we'll just show what needs to be done
echo -e "${YELLOW}Manual steps required:${NC}"
echo "1. Edit server.js"
echo "2. Change: const authRoutes = require('./src/routes/auth');"
echo " To: const authRoutes = require('./src/routes/auth-enhanced');"
echo "3. Restart the application"
echo ""
echo "After restart, the enhanced auth will be active with:"
echo "- Account lockout protection"
echo "- Login attempt tracking"
echo "- Enhanced security logging"
;;
4)
echo -e "${RED}Phase 4: Rollback${NC}"
confirm "Rollback auth changes?"
echo "Rolling back to original auth..."
echo ""
echo -e "${YELLOW}Manual steps required:${NC}"
echo "1. Edit server.js"
echo "2. Change: const authRoutes = require('./src/routes/auth-enhanced');"
echo " To: const authRoutes = require('./src/routes/auth');"
echo "3. Restart the application"
echo ""
echo "Optional: Clear lockouts"
echo "sqlite3 database.db \"DELETE FROM login_attempts WHERE success = 0\""
;;
*)
echo -e "${RED}Invalid option${NC}"
exit 1
;;
esac
echo ""
echo -e "${GREEN}Done!${NC}"
echo ""
echo "Monitor logs after any changes:"
echo "docker-compose logs -f backend | grep -i auth"
-37
View File
@@ -1,37 +0,0 @@
const path = require('path');
require('dotenv').config({ path: path.join(__dirname, '../.env') });
const { db } = require('../src/database/db');
async function fixDefaultThemes() {
try {
console.log('Fixing events with "default" theme...');
// Find all events with "default" as color_theme
const eventsToFix = await db('events')
.where('color_theme', 'default')
.select('id', 'event_name');
console.log(`Found ${eventsToFix.length} events to fix`);
if (eventsToFix.length > 0) {
// Update them to null so they use the global theme
await db('events')
.where('color_theme', 'default')
.update({ color_theme: null });
console.log('Updated events to use global theme');
eventsToFix.forEach(event => {
console.log(`- Fixed event: ${event.event_name} (ID: ${event.id})`);
});
}
console.log('Theme fix completed successfully');
process.exit(0);
} catch (error) {
console.error('Error fixing themes:', error);
process.exit(1);
}
}
fixDefaultThemes();
-136
View File
@@ -1,136 +0,0 @@
#!/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);
});
-269
View File
@@ -1,269 +0,0 @@
#!/usr/bin/env node
/**
* Safe Authentication Deployment Script
* Carefully activates auth security with multiple safety checks
*/
const { db } = require('../src/database/db');
const logger = require('../src/utils/logger');
console.log('=== Safe Authentication Security Deployment ===\n');
// Color codes
const GREEN = '\x1b[32m';
const RED = '\x1b[31m';
const YELLOW = '\x1b[33m';
const BLUE = '\x1b[34m';
const RESET = '\x1b[0m';
async function runSafetyChecks() {
console.log(`${BLUE}Running pre-deployment safety checks...${RESET}\n`);
const checks = {
databaseConnected: false,
adminUsersExist: false,
activeSessionsExist: false,
migrationsReady: true,
diskSpace: true
};
try {
// Check 1: Database connection
await db.raw('SELECT 1');
checks.databaseConnected = true;
console.log(`${GREEN}${RESET} Database connection healthy`);
} catch (e) {
console.log(`${RED}${RESET} Database connection failed`);
return checks;
}
try {
// Check 2: Admin users exist
const adminCount = await db('admin_users').count('id as count').first();
checks.adminUsersExist = adminCount.count > 0;
console.log(`${GREEN}${RESET} Found ${adminCount.count} admin users`);
} catch (e) {
console.log(`${RED}${RESET} Could not check admin users`);
}
try {
// Check 3: Check for active sessions (optional warning)
const recentLogins = await db('access_logs')
.where('action', 'login_success')
.where('timestamp', '>', new Date(Date.now() - 60 * 60 * 1000).toISOString())
.count('id as count')
.first();
if (recentLogins.count > 0) {
checks.activeSessionsExist = true;
console.log(`${YELLOW}!${RESET} Warning: ${recentLogins.count} active sessions in last hour`);
} else {
console.log(`${GREEN}${RESET} No recent active sessions`);
}
} catch (e) {
// Table might not exist yet, that's ok
console.log(`${GREEN}${RESET} No access logs table yet (expected)`);
}
try {
// Check 4: Check if migrations would conflict
const tables = await db.raw(`
SELECT name FROM sqlite_master
WHERE type='table' AND name IN ('login_attempts')
`);
if (tables.length > 0) {
console.log(`${YELLOW}!${RESET} login_attempts table already exists`);
checks.migrationsReady = false;
} else {
console.log(`${GREEN}${RESET} Ready to create login_attempts table`);
}
} catch (e) {
console.log(`${RED}${RESET} Could not check existing tables`);
checks.migrationsReady = false;
}
return checks;
}
async function backupDatabase() {
console.log(`\n${BLUE}Creating database backup...${RESET}`);
try {
const fs = require('fs').promises;
const path = require('path');
const dbPath = process.env.DB_PATH || './data/database.db';
const backupPath = `${dbPath}.backup.${Date.now()}`;
await fs.copyFile(dbPath, backupPath);
console.log(`${GREEN}${RESET} Database backed up to: ${path.basename(backupPath)}`);
return backupPath;
} catch (e) {
console.log(`${YELLOW}!${RESET} Could not create backup: ${e.message}`);
return null;
}
}
async function runMigrations() {
console.log(`\n${BLUE}Running database migrations...${RESET}`);
try {
// Run migrations
const knex = db;
await knex.migrate.latest();
console.log(`${GREEN}${RESET} Migrations completed successfully`);
// Verify tables exist
const loginAttempts = await db('login_attempts').count().first();
console.log(`${GREEN}${RESET} login_attempts table created`);
const adminColumns = await db('admin_users').columnInfo();
if (adminColumns.password_changed_at) {
console.log(`${GREEN}${RESET} Security columns added to admin_users`);
}
return true;
} catch (e) {
console.log(`${RED}${RESET} Migration failed: ${e.message}`);
return false;
}
}
async function testEnhancedAuth() {
console.log(`\n${BLUE}Testing enhanced authentication (without activating)...${RESET}`);
try {
// Test that enhanced modules load correctly
const authSecurity = require('../src/utils/authSecurity');
const authEnhanced = require('../src/middleware/auth-enhanced');
const authRoutes = require('../src/routes/auth-enhanced');
console.log(`${GREEN}${RESET} Enhanced auth modules load correctly`);
// Test lockout logic (without real data)
const lockoutStatus = await authSecurity.checkAccountLockout('test-user-that-doesnt-exist');
console.log(`${GREEN}${RESET} Account lockout check works: ${lockoutStatus.isLocked ? 'locked' : 'not locked'}`);
// Test generic error
const error = authSecurity.getGenericAuthError();
console.log(`${GREEN}${RESET} Generic error message: "${error}"`);
return true;
} catch (e) {
console.log(`${RED}${RESET} Enhanced auth test failed: ${e.message}`);
return false;
}
}
async function createDeploymentInstructions() {
console.log(`\n${BLUE}Deployment Instructions:${RESET}\n`);
const instructions = `
${GREEN}Step 1: Update server.js${RESET}
Change:
${YELLOW}const authRoutes = require('./src/routes/auth');${RESET}
To:
${GREEN}const authRoutes = require('./src/routes/auth-enhanced');${RESET}
Add after database initialization:
${GREEN}const { initializeCleanupJob } = require('./src/utils/authSecurity');
initializeCleanupJob();${RESET}
${GREEN}Step 2: Update middleware imports (if needed)${RESET}
In files using adminAuth, change:
${YELLOW}const { adminAuth } = require('../middleware/auth');${RESET}
To:
${GREEN}const { adminAuth } = require('../middleware/auth-enhanced');${RESET}
${GREEN}Step 3: Restart the application${RESET}
${BLUE}docker-compose restart backend${RESET}
or
${BLUE}pm2 restart picpeak-backend${RESET}
${GREEN}Step 4: Monitor logs${RESET}
${BLUE}docker-compose logs -f backend | grep -i auth${RESET}
${YELLOW}Rollback if needed:${RESET}
Revert server.js changes and restart
`;
console.log(instructions);
}
async function main() {
try {
// Step 1: Run safety checks
const checks = await runSafetyChecks();
if (!checks.databaseConnected) {
console.log(`\n${RED}Cannot proceed: Database not connected${RESET}`);
process.exit(1);
}
if (checks.activeSessionsExist) {
console.log(`\n${YELLOW}Warning: There are active user sessions.`);
console.log(`Consider deploying during low-traffic period.${RESET}`);
}
// Step 2: Backup database
const backupPath = await backupDatabase();
// Step 3: Run migrations
console.log(`\n${YELLOW}Ready to run migrations. This will:`);
console.log(`- Create login_attempts table`);
console.log(`- Add security columns to admin_users`);
console.log(`No existing data will be modified.${RESET}\n`);
const readline = require('readline').createInterface({
input: process.stdin,
output: process.stdout
});
readline.question('Continue with migrations? (y/n): ', async (answer) => {
if (answer.toLowerCase() !== 'y') {
console.log(`${YELLOW}Deployment cancelled${RESET}`);
readline.close();
await db.destroy();
return;
}
const migrationSuccess = await runMigrations();
if (!migrationSuccess) {
console.log(`\n${RED}Migrations failed. Database backup available at: ${backupPath}${RESET}`);
readline.close();
await db.destroy();
return;
}
// Step 4: Test enhanced auth
const authTestSuccess = await testEnhancedAuth();
if (!authTestSuccess) {
console.log(`\n${YELLOW}Enhanced auth tests failed, but migrations succeeded.`);
console.log(`Review the errors before activating enhanced auth.${RESET}`);
}
// Step 5: Show deployment instructions
await createDeploymentInstructions();
console.log(`\n${GREEN}✓ Pre-deployment complete!${RESET}`);
console.log(`${YELLOW}Enhanced auth is ready but NOT YET ACTIVE.${RESET}`);
console.log(`Follow the instructions above to activate when ready.\n`);
readline.close();
await db.destroy();
});
} catch (error) {
console.error(`${RED}Deployment script error:${RESET}`, error);
await db.destroy();
process.exit(1);
}
}
// Run the deployment
main();
-47
View File
@@ -1,47 +0,0 @@
const path = require('path');
require('dotenv').config({ path: path.join(__dirname, '../.env') });
const { db } = require('../src/database/db');
async function seedCategories() {
try {
console.log('Seeding default categories...');
const defaultCategories = [
{ name: 'Portraits', slug: 'portraits' },
{ name: 'Group Photos', slug: 'group-photos' },
{ name: 'Ceremony', slug: 'ceremony' },
{ name: 'Reception', slug: 'reception' },
{ name: 'Dancing', slug: 'dancing' },
{ name: 'Candids', slug: 'candids' },
{ name: 'Details', slug: 'details' },
{ name: 'Getting Ready', slug: 'getting-ready' }
];
for (const category of defaultCategories) {
// Check if category already exists
const existing = await db('photo_categories')
.where({ slug: category.slug, is_global: true })
.first();
if (!existing) {
await db('photo_categories').insert({
name: category.name,
slug: category.slug,
is_global: true,
event_id: null
});
console.log(`Created category: ${category.name}`);
} else {
console.log(`Category already exists: ${category.name}`);
}
}
console.log('Default categories seeded successfully');
process.exit(0);
} catch (error) {
console.error('Error seeding categories:', error);
process.exit(1);
}
}
seedCategories();
-55
View File
@@ -1,55 +0,0 @@
const axios = require('axios');
async function testAdminPhotoEndpoint() {
try {
// First login
console.log('1. Logging in as admin...');
const loginResponse = await axios.post('http://localhost:3000/api/admin/auth/login', {
username: 'admin',
password: 'admin123'
});
const token = loginResponse.data.token;
console.log('✓ Login successful, got token');
// Test thumbnail endpoint
console.log('\n2. Testing thumbnail endpoint for photo 1688...');
try {
const thumbResponse = await axios.get('http://localhost:3000/api/admin/events/12/thumbnail/1688', {
headers: {
Authorization: `Bearer ${token}`
},
responseType: 'arraybuffer'
});
console.log('✓ Thumbnail request successful');
console.log(' Response headers:', thumbResponse.headers);
console.log(' Data size:', thumbResponse.data.length, 'bytes');
} catch (error) {
console.error('✗ Thumbnail request failed:', error.response?.status, error.response?.data?.toString());
}
// Test from frontend proxy port
console.log('\n3. Testing through nginx proxy (port 3001)...');
try {
const proxyResponse = await axios.get('http://localhost:3001/api/admin/events/12/thumbnail/1688', {
headers: {
Authorization: `Bearer ${token}`,
Origin: 'http://localhost:3005'
},
responseType: 'arraybuffer'
});
console.log('✓ Proxy request successful');
console.log(' Response headers:', proxyResponse.headers);
console.log(' Data size:', proxyResponse.data.length, 'bytes');
} catch (error) {
console.error('✗ Proxy request failed:', error.response?.status, error.response?.data?.toString());
}
} catch (error) {
console.error('Error:', error.message);
}
}
testAdminPhotoEndpoint();
-146
View File
@@ -1,146 +0,0 @@
#!/bin/bash
# Test authentication deployment
# This script tests the enhanced auth without affecting production
set -e
echo "=== Testing Authentication Deployment ==="
echo ""
# Color codes
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Configuration
API_URL="http://localhost:3001/api"
TEST_USER="admin"
TEST_PASS="wrong-password"
echo -e "${BLUE}This script will test the authentication system${NC}"
echo "It will make failed login attempts to test lockout"
echo ""
# Check if server is running
echo -e "${BLUE}Checking server status...${NC}"
if curl -s -f "$API_URL/../health" > /dev/null; then
echo -e "${GREEN}✓ Server is running${NC}"
else
echo -e "${RED}✗ Server not accessible at $API_URL${NC}"
exit 1
fi
# Function to make login attempt
make_login_attempt() {
local username=$1
local password=$2
local expected_status=$3
response=$(curl -s -w "\n%{http_code}" -X POST "$API_URL/auth/admin/login" \
-H "Content-Type: application/json" \
-d "{\"username\":\"$username\",\"password\":\"$password\"}")
http_code=$(echo "$response" | tail -n1)
body=$(echo "$response" | head -n-1)
if [ "$http_code" -eq "$expected_status" ]; then
echo -e "${GREEN}${NC} Got expected status $http_code"
return 0
else
echo -e "${RED}${NC} Expected $expected_status, got $http_code"
echo "Response: $body"
return 1
fi
}
# Test 1: Normal failed login
echo -e "\n${BLUE}Test 1: Normal failed login${NC}"
make_login_attempt "$TEST_USER" "$TEST_PASS" 401
# Test 2: Multiple failed attempts (testing lockout)
echo -e "\n${BLUE}Test 2: Testing account lockout (5 attempts)${NC}"
echo "Making 4 more failed attempts..."
for i in {2..5}; do
echo -n "Attempt $i: "
make_login_attempt "$TEST_USER" "$TEST_PASS" 401
sleep 1
done
# Test 3: 6th attempt should be locked
echo -e "\n${BLUE}Test 3: 6th attempt (should be locked if enhanced auth active)${NC}"
echo -n "Attempt 6: "
response=$(curl -s -w "\n%{http_code}" -X POST "$API_URL/auth/admin/login" \
-H "Content-Type: application/json" \
-d "{\"username\":\"$TEST_USER\",\"password\":\"$TEST_PASS\"}")
http_code=$(echo "$response" | tail -n1)
body=$(echo "$response" | head -n-1)
if [ "$http_code" -eq "423" ]; then
echo -e "${GREEN}✓ Account locked as expected!${NC}"
echo -e "${GREEN}Enhanced auth is ACTIVE${NC}"
echo "Lockout message: $(echo $body | jq -r '.error')"
ENHANCED_ACTIVE=true
elif [ "$http_code" -eq "401" ]; then
echo -e "${YELLOW}! Still got 401 - Enhanced auth NOT active${NC}"
echo "Original auth is still in use"
ENHANCED_ACTIVE=false
else
echo -e "${RED}✗ Unexpected status: $http_code${NC}"
echo "Response: $body"
fi
# Test 4: Check if we can query login attempts
echo -e "\n${BLUE}Test 4: Checking login attempts table${NC}"
if [ "$ENHANCED_ACTIVE" = true ]; then
# This would need database access, so we'll check via API behavior
echo -e "${GREEN}✓ Login tracking is active${NC}"
else
echo -e "${YELLOW}! Login tracking not active (migrations might not be run)${NC}"
fi
# Test 5: Test logout endpoint
echo -e "\n${BLUE}Test 5: Testing logout endpoint${NC}"
# First need a valid token (this assumes you have one for testing)
# For now, just check if endpoint exists
logout_response=$(curl -s -w "\n%{http_code}" -X POST "$API_URL/auth/logout" \
-H "Authorization: Bearer invalid-token")
logout_code=$(echo "$logout_response" | tail -n1)
if [ "$logout_code" -eq "200" ] || [ "$logout_code" -eq "401" ]; then
echo -e "${GREEN}✓ Logout endpoint exists${NC}"
else
echo -e "${YELLOW}! Logout endpoint might not be active${NC}"
fi
# Summary
echo -e "\n${BLUE}=== Summary ===${NC}"
if [ "$ENHANCED_ACTIVE" = true ]; then
echo -e "${GREEN}✅ Enhanced authentication is ACTIVE${NC}"
echo "- Account lockout protection: Working"
echo "- Login attempt tracking: Active"
echo "- Enhanced security: Enabled"
echo ""
echo -e "${YELLOW}Note: Test account might be locked for 30 minutes${NC}"
else
echo -e "${YELLOW}⚠️ Enhanced authentication is NOT ACTIVE${NC}"
echo "- Using original auth system"
echo "- No lockout protection"
echo "- No login tracking"
echo ""
echo "To activate:"
echo "1. Run migrations: docker exec wedding-photo-sharing-backend-1 npx knex migrate:latest"
echo "2. Update server.js to use auth-enhanced routes"
echo "3. Restart: docker-compose restart backend"
fi
echo ""
echo "Test complete!"
-112
View File
@@ -1,112 +0,0 @@
#!/usr/bin/env node
/**
* Test script to verify authentication security enhancements
*/
console.log('=== Testing Authentication Security Enhancements ===\n');
const {
checkAccountLockout,
getGenericAuthError,
MAX_LOGIN_ATTEMPTS,
LOCKOUT_DURATION
} = require('../src/utils/authSecurity');
let passed = 0;
let failed = 0;
function test(description, fn) {
try {
const result = fn();
if (result || result === undefined) {
console.log(`${description}`);
passed++;
} else {
console.log(`${description}`);
failed++;
}
} catch (error) {
console.log(`${description} - Error: ${error.message}`);
failed++;
}
}
// Test generic error message
console.log('Testing generic error messages:');
test('Generic error prevents user enumeration', () => {
const error = getGenericAuthError();
return error === 'Invalid credentials';
});
// Test constants
console.log('\nTesting security constants:');
test('Max login attempts is reasonable', () => MAX_LOGIN_ATTEMPTS === 5);
test('Lockout duration is 30 minutes', () => LOCKOUT_DURATION === 30 * 60 * 1000);
// Test JWT structure
console.log('\nTesting JWT token claims:');
const jwt = require('jsonwebtoken');
const testToken = jwt.sign({
id: 1,
username: 'testuser',
type: 'admin',
ip: '127.0.0.1',
loginTime: Date.now()
}, 'test-secret', {
expiresIn: '24h',
issuer: 'picpeak-auth'
});
const decoded = jwt.verify(testToken, 'test-secret', { complete: true });
test('Token has issuer claim', () => decoded.payload.iss === 'picpeak-auth');
test('Token has IP claim', () => decoded.payload.ip === '127.0.0.1');
test('Token has loginTime claim', () => typeof decoded.payload.loginTime === 'number');
test('Token expires in 24 hours', () => {
const exp = decoded.payload.exp;
const iat = decoded.payload.iat;
return (exp - iat) === 24 * 60 * 60;
});
// Test auth middleware logic
console.log('\nTesting auth middleware logic:');
test('Token type validation works', () => {
const adminToken = { type: 'admin' };
const galleryToken = { type: 'gallery' };
const invalidToken = { type: 'invalid' };
return adminToken.type === 'admin' &&
galleryToken.type === 'gallery' &&
invalidToken.type !== 'admin' &&
invalidToken.type !== 'gallery';
});
// Test IP validation logic
console.log('\nTesting IP validation:');
test('IP mismatch is detected', () => {
const tokenIp = '192.168.1.100';
const currentIp = '10.0.0.50';
return tokenIp !== currentIp;
});
// Test password change detection
console.log('\nTesting password change detection:');
test('Token issued before password change is invalid', () => {
const tokenIssuedAt = Math.floor(Date.now() / 1000) - 3600; // 1 hour ago
const passwordChangedAt = Math.floor(Date.now() / 1000) - 1800; // 30 minutes ago
return tokenIssuedAt < passwordChangedAt;
});
// 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 authentication security tests passed!');
process.exit(0);
} else {
console.log('\n❌ Some tests failed. Review the implementation.');
process.exit(1);
}
-146
View File
@@ -1,146 +0,0 @@
#!/usr/bin/env node
/**
* Test Authentication V2 Security Fixes
*/
console.log('=== Testing Authentication V2 Fixes ===\n');
const jwt = require('jsonwebtoken');
let passed = 0;
let failed = 0;
function test(description, fn) {
try {
const result = fn();
if (result || result === undefined) {
console.log(`${description}`);
passed++;
} else {
console.log(`${description}`);
failed++;
}
} catch (error) {
console.log(`${description} - Error: ${error.message}`);
failed++;
}
}
async function runTests() {
// Test 1: Rate Limiting Security
console.log('1. Testing Rate Limiting Security:');
const { hasValidAdminToken } = require('../src/utils/rateLimitSecurity');
// Mock requests
const validAdminReq = {
path: '/api/admin/events',
headers: {
authorization: 'Bearer ' + jwt.sign({ id: 1, type: 'admin' }, process.env.JWT_SECRET || 'test')
},
ip: '127.0.0.1'
};
const invalidTokenReq = {
path: '/api/admin/events',
headers: {
authorization: 'Bearer invalid.token.here'
},
ip: '127.0.0.1'
};
const galleryTokenReq = {
path: '/api/admin/events',
headers: {
authorization: 'Bearer ' + jwt.sign({ id: 1, type: 'gallery' }, process.env.JWT_SECRET || 'test')
},
ip: '127.0.0.1'
};
test('Valid admin token skips rate limit', () => hasValidAdminToken(validAdminReq) === true);
test('Invalid token applies rate limit', () => hasValidAdminToken(invalidTokenReq) === false);
test('Gallery token cannot bypass admin rate limit', () => hasValidAdminToken(galleryTokenReq) === false);
// Test 2: Password Validation
console.log('\n2. Testing Password Validation:');
const { validatePassword, validatePasswordInContext } = require('../src/utils/passwordValidation');
const weakPassword = validatePassword('weak123');
test('Weak password is rejected', () => !weakPassword.valid);
test('Weak password has errors', () => weakPassword.errors.length > 0);
const strongPassword = validatePassword('Str0ng!P@ssw0rd123');
test('Strong password is accepted', () => strongPassword.valid);
test('Strong password has good score', () => strongPassword.score >= 3);
const shortPassword = validatePassword('Short!1');
test('Short password is rejected', () => !shortPassword.valid &&
shortPassword.errors.some(e => e.includes('12 characters')));
const noSpecialChar = validatePassword('NoSpecialChar123');
test('Password without special char is rejected', () => !noSpecialChar.valid &&
noSpecialChar.errors.some(e => e.includes('special character')));
// Context validation
const adminContext = validatePasswordInContext('Admin123!Pass', 'admin', { username: 'admin' });
test('Admin password with username is rejected', () => !adminContext.valid);
const galleryContext = validatePasswordInContext('Event123!Pass', 'gallery', { eventName: 'event' });
test('Gallery password with event name is rejected', () => !galleryContext.valid);
// Test 3: Token Revocation
console.log('\n3. Testing Token Revocation:');
const { isTokenRevoked } = require('../src/utils/tokenRevocation');
const testToken = {
jti: 'test-123',
id: 1,
type: 'admin',
iat: Math.floor(Date.now() / 1000)
};
// This would need database setup to fully test
test('Token revocation check runs', async () => {
try {
await isTokenRevoked(testToken);
return true;
} catch (e) {
// Expected if tables don't exist yet
return true;
}
});
// Test 4: Bcrypt Rounds
console.log('\n4. Testing Configurable Bcrypt:');
const { getBcryptRounds, PASSWORD_CONFIG } = require('../src/utils/passwordValidation');
test('Bcrypt rounds are configurable', () => {
const rounds = getBcryptRounds();
return rounds >= 10 && rounds <= 14;
});
test('Default bcrypt rounds is 12', () => {
return PASSWORD_CONFIG.bcryptRounds === 12 ||
PASSWORD_CONFIG.bcryptRounds === parseInt(process.env.BCRYPT_ROUNDS);
});
// 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 authentication V2 tests passed!');
process.exit(0);
} else {
console.log('\n❌ Some tests failed. Review the implementation.');
process.exit(1);
}
}
// Run tests
runTests().catch(error => {
console.error('Test error:', error);
process.exit(1);
});
@@ -1,75 +0,0 @@
#!/usr/bin/env node
/**
* Test enhanced authentication features in Docker
*/
const { db } = require('../src/database/db');
const {
checkAccountLockout,
trackFailedAttempt,
trackSuccessfulLogin
} = require('../src/utils/authSecurity');
console.log('=== Testing Enhanced Auth Features ===\n');
async function testAuthFeatures() {
try {
// Test 1: Check if tables exist
console.log('1. Checking database tables...');
const loginAttempts = await db('login_attempts').count().first();
console.log('✓ login_attempts table exists');
// Test 2: Test failed attempt tracking
console.log('\n2. Testing failed attempt tracking...');
await trackFailedAttempt('test-user', '127.0.0.1', 'Test User Agent');
const attempts = await db('login_attempts')
.where('identifier', 'test-user')
.count()
.first();
console.log(`✓ Failed attempt tracked (${attempts.count} total)`);
// Test 3: Test lockout check
console.log('\n3. Testing lockout detection...');
// Add 4 more failures to trigger lockout
for (let i = 0; i < 4; i++) {
await trackFailedAttempt('test-user', '127.0.0.1', 'Test User Agent');
}
const lockoutStatus = await checkAccountLockout('test-user');
console.log(`✓ Lockout check works: ${lockoutStatus.isLocked ? 'LOCKED' : 'NOT LOCKED'}`);
if (lockoutStatus.isLocked) {
console.log(` Remaining lockout time: ${lockoutStatus.remainingTime} seconds`);
}
// Test 4: Test successful login tracking
console.log('\n4. Testing successful login tracking...');
await trackSuccessfulLogin('test-user', '127.0.0.1', 'Test User Agent');
console.log('✓ Successful login tracked');
// Test 5: Check if auth routes load
console.log('\n5. Testing enhanced auth routes...');
try {
const authRoutes = require('../src/routes/auth-enhanced');
console.log('✓ Enhanced auth routes load successfully');
} catch (e) {
console.log('✗ Error loading enhanced auth routes:', e.message);
}
// Clean up test data
await db('login_attempts').where('identifier', 'test-user').delete();
console.log('\n✓ Test data cleaned up');
console.log('\n✅ Enhanced auth features are working correctly!');
console.log('\nNext step: Update server.js to use enhanced auth routes');
} catch (error) {
console.error('\n❌ Test failed:', error);
} finally {
await db.destroy();
}
}
testAuthFeatures();
-98
View File
@@ -1,98 +0,0 @@
#!/usr/bin/env node
/**
* Test script to verify JWT_SECRET validation works correctly
* This ensures our security fix doesn't break production
*/
const { spawn } = require('child_process');
const path = require('path');
console.log('=== Testing JWT_SECRET Validation ===\n');
// Test 1: Server should fail to start without JWT_SECRET
console.log('Test 1: Starting server without JWT_SECRET...');
const test1 = spawn('node', [path.join(__dirname, '..', 'server.js')], {
env: { ...process.env, JWT_SECRET: '' },
stdio: 'pipe'
});
let test1Output = '';
test1.stderr.on('data', (data) => {
test1Output += data.toString();
});
test1.on('close', (code) => {
if (code === 1 && test1Output.includes('Missing required environment variable: JWT_SECRET')) {
console.log('✅ Test 1 PASSED: Server correctly refuses to start without JWT_SECRET\n');
runTest2();
} else {
console.log('❌ Test 1 FAILED: Server should have failed to start');
console.log('Exit code:', code);
console.log('Output:', test1Output);
process.exit(1);
}
});
// Test 2: Server should fail with insecure default value
function runTest2() {
console.log('Test 2: Starting server with insecure JWT_SECRET...');
const test2 = spawn('node', [path.join(__dirname, '..', 'server.js')], {
env: { ...process.env, JWT_SECRET: 'your-secret-key' },
stdio: 'pipe'
});
let test2Output = '';
test2.stderr.on('data', (data) => {
test2Output += data.toString();
});
test2.on('close', (code) => {
if (code === 1 && test2Output.includes('JWT_SECRET is set to the insecure default value')) {
console.log('✅ Test 2 PASSED: Server correctly refuses insecure JWT_SECRET\n');
runTest3();
} else {
console.log('❌ Test 2 FAILED: Server should have rejected insecure JWT_SECRET');
console.log('Exit code:', code);
console.log('Output:', test2Output);
process.exit(1);
}
});
}
// Test 3: Verify protectedImages functions work with valid JWT_SECRET
function runTest3() {
console.log('Test 3: Testing protectedImages functions...');
// Set a valid JWT_SECRET for this test
process.env.JWT_SECRET = 'test-secret-key-that-is-long-enough-for-security';
try {
// Load the module to test
const protectedImagesPath = path.join(__dirname, '..', 'src', 'routes', 'protectedImages.js');
delete require.cache[protectedImagesPath]; // Clear cache to ensure fresh load
// This will throw if JWT_SECRET is not available
require(protectedImagesPath);
console.log('✅ Test 3 PASSED: protectedImages module loads successfully with valid JWT_SECRET\n');
console.log('=== All Tests Passed! ===');
console.log('\nThe JWT_SECRET validation is working correctly.');
console.log('Production systems must have JWT_SECRET set to a secure value.');
process.exit(0);
} catch (error) {
console.log('❌ Test 3 FAILED: Error loading protectedImages module');
console.log('Error:', error.message);
process.exit(1);
}
}
// Give the first test some time to complete
setTimeout(() => {
if (test1.exitCode === null) {
console.log('❌ Test 1 TIMEOUT: Server did not exit as expected');
test1.kill();
process.exit(1);
}
}, 5000);
@@ -1,171 +0,0 @@
#!/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);
});
-108
View File
@@ -1,108 +0,0 @@
#!/usr/bin/env node
/**
* Test script to verify SQL security fixes work correctly
* Tests both functionality and security of the fixes
*/
const {
sanitizeDays,
escapeLikePattern,
validateSortColumn,
validateSortOrder
} = require('../src/utils/sqlSecurity');
console.log('=== Testing SQL Security Utilities ===\n');
let passed = 0;
let failed = 0;
function test(description, fn) {
try {
const result = fn();
if (result) {
console.log(`${description}`);
passed++;
} else {
console.log(`${description}`);
failed++;
}
} catch (error) {
console.log(`${description} - Error: ${error.message}`);
failed++;
}
}
// Test sanitizeDays
console.log('Testing sanitizeDays function:');
test('Valid number returns same number', () => sanitizeDays(7) === 7);
test('String number is parsed correctly', () => sanitizeDays('30') === 30);
test('Invalid input returns default 7', () => sanitizeDays('abc') === 7);
test('Negative number returns 1', () => sanitizeDays(-5) === 1);
test('Zero returns 1', () => sanitizeDays(0) === 1);
test('Large number is capped at 365', () => sanitizeDays(500) === 365);
test('NaN returns default 7', () => sanitizeDays(NaN) === 7);
test('Null returns default 7', () => sanitizeDays(null) === 7);
test('Undefined returns default 7', () => sanitizeDays(undefined) === 7);
// Test escapeLikePattern
console.log('\nTesting escapeLikePattern function:');
test('Normal text unchanged', () => escapeLikePattern('hello world') === 'hello world');
test('Percent sign escaped', () => escapeLikePattern('50%') === '50\\%');
test('Underscore escaped', () => escapeLikePattern('user_name') === 'user\\_name');
test('Backslash escaped', () => escapeLikePattern('path\\to\\file') === 'path\\\\to\\\\file');
test('Multiple special chars escaped', () => escapeLikePattern('50%_test\\') === '50\\%\\_test\\\\');
test('Empty string returns empty', () => escapeLikePattern('') === '');
test('Null returns empty string', () => escapeLikePattern(null) === '');
test('Undefined returns empty string', () => escapeLikePattern(undefined) === '');
test('Single quotes escaped', () => escapeLikePattern("O'Brien") === "O''Brien");
// Test SQL injection attempts
console.log('\nTesting SQL injection prevention:');
test('SQL injection attempt with quotes', () => {
const malicious = "'; DROP TABLE users; --";
const escaped = escapeLikePattern(malicious);
return escaped === "''; DROP TABLE users; --" && escaped.includes("''");
});
test('SQL injection with LIKE wildcards', () => {
const malicious = "%' OR 1=1 --";
const escaped = escapeLikePattern(malicious);
return escaped === "\\%'' OR 1=1 --";
});
test('Days parameter injection attempt', () => {
const malicious = "7; DROP TABLE events; --";
return sanitizeDays(malicious) === 7;
});
// Test validateSortColumn
console.log('\nTesting validateSortColumn function:');
const allowedColumns = ['name', 'date', 'size'];
test('Valid column accepted', () => validateSortColumn('name', allowedColumns, 'date') === 'name');
test('Invalid column returns default', () => validateSortColumn('price', allowedColumns, 'date') === 'date');
test('Null returns default', () => validateSortColumn(null, allowedColumns, 'date') === 'date');
test('Empty string returns default', () => validateSortColumn('', allowedColumns, 'date') === 'date');
// Test validateSortOrder
console.log('\nTesting validateSortOrder function:');
test('Valid asc accepted', () => validateSortOrder('asc') === 'asc');
test('Valid ASC accepted', () => validateSortOrder('ASC') === 'asc');
test('Valid desc accepted', () => validateSortOrder('desc') === 'desc');
test('Invalid order returns desc', () => validateSortOrder('random') === 'desc');
test('Null returns desc', () => validateSortOrder(null) === 'desc');
test('Empty returns desc', () => validateSortOrder('') === 'desc');
// 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 tests passed! SQL security utilities are working correctly.');
process.exit(0);
} else {
console.log('\n❌ Some tests failed. Please check the implementation.');
process.exit(1);
}
-59
View File
@@ -1,59 +0,0 @@
const axios = require('axios');
const FormData = require('form-data');
const fs = require('fs');
const path = require('path');
async function testUpload() {
try {
// First login
console.log('1. Logging in as admin...');
const loginResponse = await axios.post('http://localhost:3000/api/admin/auth/login', {
username: 'admin',
password: 'admin123'
});
const token = loginResponse.data.token;
console.log('✓ Login successful');
// Create a test image file
const testImagePath = path.join(__dirname, 'test-image.png');
const imageBuffer = Buffer.from('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==', 'base64');
fs.writeFileSync(testImagePath, imageBuffer);
// Test upload
console.log('\n2. Testing upload with category_id=7...');
const form = new FormData();
form.append('photos', fs.createReadStream(testImagePath), 'test-image.png');
form.append('category_id', '7');
console.log('Form data headers:', form.getHeaders());
try {
const uploadResponse = await axios.post(
'http://localhost:3000/api/admin/events/12/upload',
form,
{
headers: {
...form.getHeaders(),
'Authorization': `Bearer ${token}`
}
}
);
console.log('✓ Upload successful:', uploadResponse.data);
} catch (error) {
console.error('✗ Upload failed:', error.response?.status, error.response?.data);
if (error.response?.data) {
console.error('Error details:', JSON.stringify(error.response.data, null, 2));
}
}
// Clean up
fs.unlinkSync(testImagePath);
} catch (error) {
console.error('Error:', error.message);
}
}
testUpload();
-106
View File
@@ -1,106 +0,0 @@
#!/usr/bin/env node
/**
* Verify enhanced authentication is active and working
*/
const { db } = require('../src/database/db');
console.log('=== Verifying Enhanced Authentication Activation ===\n');
async function verifyAuth() {
const results = {
databaseTables: false,
serverConfig: false,
lockoutActive: false,
cleanupActive: false
};
try {
// 1. Check database tables
console.log('1. Checking database tables...');
const hasLoginAttempts = await db.schema.hasTable('login_attempts');
const hasSecurityColumns = await db.schema.hasColumn('admin_users', 'password_changed_at');
if (hasLoginAttempts && hasSecurityColumns) {
results.databaseTables = true;
console.log('✓ Auth tables and columns exist');
// Count attempts
const attempts = await db('login_attempts').count().first();
console.log(` Total login attempts tracked: ${attempts['count(*)'] || 0}`);
} else {
console.log('✗ Auth tables missing');
}
// 2. Check server configuration
console.log('\n2. Checking server configuration...');
const fs = require('fs');
const serverContent = fs.readFileSync('./server.js', 'utf8');
if (serverContent.includes("require('./src/routes/auth-enhanced')")) {
results.serverConfig = true;
console.log('✓ Server using enhanced auth routes');
} else {
console.log('✗ Server using original auth routes');
}
if (serverContent.includes('initializeCleanupJob')) {
results.cleanupActive = true;
console.log('✓ Cleanup job initialized');
} else {
console.log('✗ Cleanup job not initialized');
}
// 3. Test lockout functionality
console.log('\n3. Testing lockout functionality...');
const { checkAccountLockout } = require('../src/utils/authSecurity');
// Check a test account
const lockoutTest = await checkAccountLockout('lockout-test-user');
console.log(`✓ Lockout check functional: ${lockoutTest.isLocked ? 'locked' : 'not locked'}`);
results.lockoutActive = true;
// 4. Check recent activity
console.log('\n4. Recent authentication activity...');
const recentAttempts = await db('login_attempts')
.orderBy('attempt_time', 'desc')
.limit(5);
if (recentAttempts.length > 0) {
console.log('Recent login attempts:');
recentAttempts.forEach(attempt => {
const time = new Date(attempt.attempt_time).toLocaleString();
console.log(` ${time} - ${attempt.identifier} - ${attempt.success ? 'SUCCESS' : 'FAILED'}`);
});
} else {
console.log('No login attempts recorded yet');
}
// Summary
console.log('\n=== Summary ===');
const allGood = Object.values(results).every(v => v === true);
if (allGood) {
console.log('✅ Enhanced authentication is FULLY ACTIVE!');
console.log('\nFeatures enabled:');
console.log('- Account lockout protection (5 attempts)');
console.log('- Login attempt tracking');
console.log('- Enhanced token validation');
console.log('- Session management');
console.log('- Automatic cleanup of old records');
} else {
console.log('⚠️ Some features not active:');
Object.entries(results).forEach(([key, value]) => {
console.log(` ${key}: ${value ? '✓' : '✗'}`);
});
}
} catch (error) {
console.error('Verification error:', error);
} finally {
await db.destroy();
}
}
verifyAuth();
-80
View File
@@ -1,80 +0,0 @@
#!/usr/bin/env node
/**
* Verification script to check SQL queries are built correctly
* This simulates the query building without running the full app
*/
const {
sanitizeDays,
escapeLikePattern,
validateSortColumn,
validateSortOrder
} = require('../src/utils/sqlSecurity');
console.log('=== Verifying SQL Security Fixes ===\n');
// Test 1: Verify date range queries
console.log('1. Testing date range query building:');
console.log(' Input days: "7; DROP TABLE events; --"');
const safeDays = sanitizeDays("7; DROP TABLE events; --");
console.log(' Sanitized days:', safeDays);
console.log(' ✅ SQL injection attempt neutralized\n');
// Test 2: Verify LIKE pattern escaping
console.log('2. Testing LIKE pattern escaping:');
const testPatterns = [
"normal search",
"50%_wildcard",
"'; DROP TABLE users; --",
"test\\path",
"O'Brien"
];
testPatterns.forEach(pattern => {
const escaped = escapeLikePattern(pattern);
console.log(` "${pattern}" → "${escaped}"`);
});
console.log(' ✅ All patterns safely escaped\n');
// Test 3: Simulate date query building
console.log('3. Simulating safe date query:');
const days = 7;
const startDate = new Date();
startDate.setDate(startDate.getDate() - days);
console.log(` WHERE timestamp >= '${startDate.toISOString()}'`);
console.log(' ✅ Using parameterized date instead of whereRaw\n');
// Test 4: Verify sort validation
console.log('4. Testing sort column/order validation:');
const allowedColumns = ['created_at', 'event_name', 'expires_at'];
console.log(' Allowed columns:', allowedColumns);
const testSorts = [
{ column: 'created_at', order: 'desc' },
{ column: 'invalid_column', order: 'asc' },
{ column: '; DROP TABLE --', order: 'random' }
];
testSorts.forEach(({ column, order }) => {
const safeColumn = validateSortColumn(column, allowedColumns, 'created_at');
const safeOrder = validateSortOrder(order);
console.log(` "${column}" ${order} → "${safeColumn}" ${safeOrder}`);
});
console.log(' ✅ Invalid columns/orders rejected\n');
// Test 5: Show example of safe query patterns
console.log('5. Safe Query Patterns Used:');
console.log(' ❌ OLD: .whereRaw(`timestamp >= datetime("now", "-${days} days")`)')
console.log(' ✅ NEW: .where("timestamp", ">=", startDate.toISOString())\n');
console.log(' ❌ OLD: .where("event_name", "like", `%${search}%`)')
console.log(' ✅ NEW: .where("event_name", "like", `%${escapeLikePattern(search)}%`)\n');
console.log('=== Verification Complete ===');
console.log('All SQL injection vulnerabilities have been addressed.');
console.log('\nNext steps:');
console.log('1. Test in development environment');
console.log('2. Monitor logs during testing');
console.log('3. Deploy with rollback plan ready');
console.log('4. Monitor production logs after deployment');