← back to Handbag Auth Nextjs

auction-viewer/test-security.js

331 lines

#!/usr/bin/env node

const http = require('http');
const https = require('https');

const HOST = '45.61.58.125';
const PORT = 7500;
const BASE_URL = `http://${HOST}:${PORT}`;

// Color codes for console output
const colors = {
  reset: '\x1b[0m',
  green: '\x1b[32m',
  red: '\x1b[31m',
  yellow: '\x1b[33m',
  blue: '\x1b[34m',
  cyan: '\x1b[36m'
};

let testsPassed = 0;
let testsFailed = 0;

// Test helper function
async function testEndpoint(name, options, expectedStatus = 200, expectFail = false) {
  return new Promise((resolve) => {
    const url = new URL(options.path || '/', BASE_URL);

    const reqOptions = {
      hostname: HOST,
      port: PORT,
      path: url.pathname + url.search,
      method: options.method || 'GET',
      headers: options.headers || {},
      timeout: 5000
    };

    const req = http.request(reqOptions, (res) => {
      let data = '';
      res.on('data', chunk => data += chunk);
      res.on('end', () => {
        const success = expectFail
          ? res.statusCode !== expectedStatus
          : res.statusCode === expectedStatus;

        if (success) {
          console.log(`${colors.green}✓${colors.reset} ${name}`);
          testsPassed++;
        } else {
          console.log(`${colors.red}✗${colors.reset} ${name} - Expected ${expectedStatus}, got ${res.statusCode}`);
          testsFailed++;
        }

        // Check security headers
        if (res.headers['x-content-type-options']) {
          console.log(`  ${colors.cyan}→ X-Content-Type-Options: ${res.headers['x-content-type-options']}${colors.reset}`);
        }
        if (res.headers['strict-transport-security']) {
          console.log(`  ${colors.cyan}→ HSTS: Present${colors.reset}`);
        }
        if (res.headers['x-frame-options']) {
          console.log(`  ${colors.cyan}→ X-Frame-Options: ${res.headers['x-frame-options']}${colors.reset}`);
        }

        resolve({ status: res.statusCode, headers: res.headers, data });
      });
    });

    req.on('error', (err) => {
      console.log(`${colors.red}✗${colors.reset} ${name} - ${err.message}`);
      testsFailed++;
      resolve({ error: err.message });
    });

    req.on('timeout', () => {
      req.destroy();
      console.log(`${colors.red}✗${colors.reset} ${name} - Timeout`);
      testsFailed++;
      resolve({ error: 'Timeout' });
    });

    if (options.body) {
      req.write(options.body);
    }

    req.end();
  });
}

// Test SQL Injection Prevention
async function testSQLInjection() {
  console.log(`\n${colors.blue}Testing SQL Injection Prevention:${colors.reset}`);

  await testEndpoint(
    'SQL Injection in sort parameter',
    { path: '/api/best-values?sort=percent; DROP TABLE auctions;--' },
    400,
    false
  );

  await testEndpoint(
    'SQL Injection in limit parameter',
    { path: '/api/auctions?limit=1 OR 1=1' },
    400,
    false
  );

  await testEndpoint(
    'Valid sort parameter',
    { path: '/api/best-values?sort=percent' },
    200
  );
}

// Test Path Traversal Prevention
async function testPathTraversal() {
  console.log(`\n${colors.blue}Testing Path Traversal Prevention:${colors.reset}`);

  await testEndpoint(
    'Path traversal attempt in static files',
    { path: '/../../../etc/passwd' },
    404
  );

  await testEndpoint(
    'Path traversal with encoded dots',
    { path: '/%2e%2e%2f%2e%2e%2f%2e%2e%2fetc%2fpasswd' },
    404
  );
}

// Test Rate Limiting
async function testRateLimiting() {
  console.log(`\n${colors.blue}Testing Rate Limiting:${colors.reset}`);

  // Make multiple rapid requests
  const promises = [];
  for (let i = 0; i < 25; i++) {
    promises.push(
      testEndpoint(
        `Rate limit request ${i + 1}`,
        { path: '/api/logs' },
        i < 20 ? 200 : 429,
        false
      )
    );
  }

  await Promise.all(promises);
}

// Test Input Validation
async function testInputValidation() {
  console.log(`\n${colors.blue}Testing Input Validation:${colors.reset}`);

  await testEndpoint(
    'Invalid limit parameter (negative)',
    { path: '/api/auctions?limit=-1' },
    400
  );

  await testEndpoint(
    'Invalid limit parameter (too large)',
    { path: '/api/auctions?limit=10000' },
    400
  );

  await testEndpoint(
    'Invalid sort parameter',
    { path: '/api/best-values?sort=invalid' },
    400
  );

  await testEndpoint(
    'Valid parameters',
    { path: '/api/auctions?limit=10&offset=0' },
    200
  );
}

// Test Security Headers
async function testSecurityHeaders() {
  console.log(`\n${colors.blue}Testing Security Headers:${colors.reset}`);

  const result = await testEndpoint(
    'Security headers on main page',
    { path: '/' },
    200
  );

  if (result.headers) {
    const requiredHeaders = [
      'x-content-type-options',
      'x-frame-options',
      'x-xss-protection',
      'strict-transport-security',
      'content-security-policy'
    ];

    requiredHeaders.forEach(header => {
      if (result.headers[header]) {
        console.log(`${colors.green}✓${colors.reset} Header ${header} is present`);
        testsPassed++;
      } else {
        console.log(`${colors.yellow}⚠${colors.reset} Header ${header} is missing`);
      }
    });
  }
}

// Test CORS Restrictions
async function testCORS() {
  console.log(`\n${colors.blue}Testing CORS Restrictions:${colors.reset}`);

  await testEndpoint(
    'CORS with invalid origin',
    {
      path: '/api/auctions',
      headers: { 'Origin': 'http://evil-site.com' }
    },
    500,
    false
  );

  await testEndpoint(
    'CORS with valid origin',
    {
      path: '/api/auctions',
      headers: { 'Origin': `http://${HOST}:${PORT}` }
    },
    200
  );
}

// Test Error Handling
async function testErrorHandling() {
  console.log(`\n${colors.blue}Testing Error Handling:${colors.reset}`);

  const result = await testEndpoint(
    'Non-existent endpoint',
    { path: '/api/nonexistent' },
    404
  );

  if (result.data) {
    try {
      const json = JSON.parse(result.data);
      if (json.error && !json.error.includes('stack')) {
        console.log(`${colors.green}✓${colors.reset} Error messages don't expose stack traces`);
        testsPassed++;
      }
    } catch (e) {
      // Invalid JSON
    }
  }
}

// Test File Size Limits
async function testFileSizeLimits() {
  console.log(`\n${colors.blue}Testing File Size Limits:${colors.reset}`);

  // Test large body rejection
  const largeBody = 'x'.repeat(11 * 1024 * 1024); // 11MB

  await testEndpoint(
    'Large request body rejection',
    {
      method: 'POST',
      path: '/api/auctions',
      headers: {
        'Content-Type': 'application/json',
        'Content-Length': largeBody.length
      },
      body: largeBody
    },
    413,
    false
  );
}

// Test Health Endpoints
async function testHealthEndpoints() {
  console.log(`\n${colors.blue}Testing Health Endpoints:${colors.reset}`);

  await testEndpoint(
    'Health check endpoint',
    { path: '/health' },
    200
  );

  await testEndpoint(
    'Status endpoint',
    { path: '/api/status' },
    200
  );
}

// Main test runner
async function runSecurityTests() {
  console.log(`
╔═══════════════════════════════════════════════════════╗
║           LUXVAULT SECURITY TEST SUITE                ║
╠═══════════════════════════════════════════════════════╣
║  Testing: http://${HOST}:${PORT}                      ║
╚═══════════════════════════════════════════════════════╝
  `);

  await testSQLInjection();
  await testPathTraversal();
  await testInputValidation();
  await testSecurityHeaders();
  await testCORS();
  await testErrorHandling();
  await testHealthEndpoints();
  await testRateLimiting(); // Run rate limiting last

  // Summary
  console.log(`
╔═══════════════════════════════════════════════════════╗
║                    TEST SUMMARY                       ║
╠═══════════════════════════════════════════════════════╣
║  ${colors.green}Passed: ${testsPassed}${colors.reset}                                            ║
║  ${colors.red}Failed: ${testsFailed}${colors.reset}                                             ║
║  Total: ${testsPassed + testsFailed}                                            ║
╚═══════════════════════════════════════════════════════╝
  `);

  process.exit(testsFailed > 0 ? 1 : 0);
}

// Run tests
runSecurityTests().catch(console.error);