#!/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();