/** * Rate Limiting Functionality Test * * Tests that rate limiting is working correctly on authentication endpoints. * Verifies that requests are blocked after exceeding limits and that proper * headers are returned. * * Prerequisites: Web server running on http://127.0.0.1:5000 * * Usage: node tests/ui_tests/test_rate_limiting_settings.js */ const puppeteer = require('puppeteer'); const { getPuppeteerLaunchOptions } = require('./puppeteer_config'); async function testRateLimiting() { // Skip this test in CI unless ENABLE_RATE_LIMITING=true // Rate limiting is disabled in CI by default, so this test would fail if (process.env.CI && process.env.ENABLE_RATE_LIMITING !== 'true') { console.log('āš ļø Skipping rate limiting test in CI (requires ENABLE_RATE_LIMITING=true)'); console.log('ā„¹ļø This test needs rate limiting enabled, but CI disables it by default'); return; // Caller awaits but doesn't use return value } const browser = await puppeteer.launch(getPuppeteerLaunchOptions()); const page = await browser.newPage(); const baseUrl = 'http://127.0.0.1:5000'; try { console.log('šŸš€ Starting rate limiting functionality test...'); // Test 1: Test login rate limiting (5 per 15 minutes) console.log('\nšŸ” Test 1: Login endpoint should be rate limited (5 attempts per 15 min)'); await page.goto(`${baseUrl}/auth/login`, { waitUntil: 'domcontentloaded' }); let loginAttempts = 0; let rateLimitTriggered = false; // Make login attempts until rate limited for (let i = 0; i < 10; i++) { const response = await page.evaluate(async (url) => { const res = await fetch(`${url}/api/v1/auth/login`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ username: 'testuser_nonexistent', password: 'wrongpassword' // gitleaks:allow }) }); return { status: res.status, headers: { ratelimitLimit: res.headers.get('X-RateLimit-Limit'), ratelimitRemaining: res.headers.get('X-RateLimit-Remaining'), ratelimitReset: res.headers.get('X-RateLimit-Reset') } }; }, baseUrl); loginAttempts++; if (response.status === 429) { console.log(`āœ… Rate limit triggered after ${loginAttempts} login attempts`); console.log(`šŸ“Š Rate limit headers:`, response.headers); rateLimitTriggered = true; break; } else { console.log(`šŸ“ Login attempt ${loginAttempts}: status ${response.status}, remaining: ${response.headers.ratelimitRemaining}`); } // Small delay between requests await new Promise(resolve => setTimeout(resolve, 100)); } if (!rateLimitTriggered) { console.log('āŒ Rate limit was not triggered after 10 login attempts'); throw new Error('Login rate limiting is not working'); } if (loginAttempts > 6) { console.log(`āš ļø Rate limit triggered later than expected (after ${loginAttempts} attempts, expected ~5)`); } // Test 2: Test registration rate limiting (3 per hour) console.log('\nšŸ” Test 2: Registration endpoint should be rate limited (3 attempts per hour)'); // Use a different page context to reset cookies/session const page2 = await browser.newPage(); await page2.goto(`${baseUrl}/auth/register`, { waitUntil: 'domcontentloaded' }); let registrationAttempts = 0; let regRateLimitTriggered = false; // Make registration attempts until rate limited for (let i = 0; i < 10; i++) { const response = await page2.evaluate(async (url, attemptNum) => { const res = await fetch(`${url}/api/v1/auth/register`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ username: `testuser_ratelimit_${Date.now()}_${attemptNum}`, password: 'testpassword123' // gitleaks:allow }) }); return { status: res.status, headers: { ratelimitLimit: res.headers.get('X-RateLimit-Limit'), ratelimitRemaining: res.headers.get('X-RateLimit-Remaining'), ratelimitReset: res.headers.get('X-RateLimit-Reset') } }; }, baseUrl, i); registrationAttempts++; if (response.status === 429) { console.log(`āœ… Rate limit triggered after ${registrationAttempts} registration attempts`); console.log(`šŸ“Š Rate limit headers:`, response.headers); regRateLimitTriggered = true; break; } else { console.log(`šŸ“ Registration attempt ${registrationAttempts}: status ${response.status}, remaining: ${response.headers.ratelimitRemaining}`); } // Small delay between requests await new Promise(resolve => setTimeout(resolve, 100)); } if (!regRateLimitTriggered) { console.log('āŒ Rate limit was not triggered after 10 registration attempts'); throw new Error('Registration rate limiting is not working'); } if (registrationAttempts > 4) { console.log(`āš ļø Rate limit triggered later than expected (after ${registrationAttempts} attempts, expected ~3)`); } await page2.close(); // Test 3: Verify 429 error response format console.log('\nšŸ” Test 3: Verify 429 error response format'); const errorResponse = await page.evaluate(async (url) => { const res = await fetch(`${url}/api/v1/auth/login`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ username: 'testuser', password: 'testpass' // gitleaks:allow }) }); const body = await res.json(); return { status: res.status, body }; }, baseUrl); if (errorResponse.status === 429) { console.log('āœ… 429 status code returned correctly'); console.log('šŸ“Š Error response body:', errorResponse.body); if (errorResponse.body.error && errorResponse.body.message) { console.log('āœ… Error response has correct format (error and message fields)'); } else { console.log('āš ļø Error response format unexpected:', errorResponse.body); } } else { console.log(`ā„¹ļø Rate limit may have reset, got status ${errorResponse.status}`); } // Summary console.log('\nšŸ“Š Test Summary:'); console.log(`āœ… Login rate limiting (5 per 15 min): PASS (triggered after ${loginAttempts} attempts)`); console.log(`āœ… Registration rate limiting (3 per hour): PASS (triggered after ${registrationAttempts} attempts)`); console.log(`āœ… Error response format: PASS`); console.log('\nšŸŽ‰ Rate limiting functionality test completed successfully'); } catch (error) { console.error('āŒ Error during rate limiting test:', error); throw error; } finally { await browser.close(); } } // Run the test if (require.main === module) { testRateLimiting() .then(() => { console.log('āœ… All rate limiting tests completed'); process.exit(0); }) .catch(error => { console.error('āŒ Rate limiting tests failed:', error); process.exit(1); }); } module.exports = { testRateLimiting };