feat: implement comprehensive CSRF protection

This commit is contained in:
2025-07-11 18:06:51 +02:00
committed by Kaj Kowalski
parent e7818f5e4f
commit 3e9e75e854
44 changed files with 14964 additions and 6413 deletions

View File

@ -0,0 +1,526 @@
/**
* tRPC Endpoint Validation Tests
*
* Comprehensive tests to validate tRPC endpoints are working correctly
* after deployment of the new architecture.
*/
import { migrationLogger } from "./migration-logger";
interface EndpointTest {
name: string;
path: string;
method: string;
payload?: unknown;
expectedStatuses: number[];
timeout: number;
critical: boolean;
}
interface TestResult {
name: string;
success: boolean;
status: number;
duration: number;
response?: unknown;
error?: Error;
}
interface TRPCTestResult {
success: boolean;
tests: TestResult[];
totalDuration: number;
passedTests: number;
failedTests: number;
criticalFailures: number;
}
export class TRPCEndpointTester {
private baseUrl: string;
private timeout: number;
constructor(baseUrl?: string, timeout: number = 30000) {
this.baseUrl = baseUrl || process.env.NEXTAUTH_URL || "http://localhost:3000";
this.timeout = timeout;
}
/**
* Run comprehensive tRPC endpoint tests
*/
async runEndpointTests(): Promise<TRPCTestResult> {
const startTime = Date.now();
const tests: TestResult[] = [];
try {
migrationLogger.startStep("TRPC_TESTS", "Running tRPC endpoint validation tests");
// Define test suite
const endpointTests: EndpointTest[] = [
// Authentication endpoints
{
name: "Auth - Get Session",
path: "/api/trpc/auth.getSession",
method: "POST",
payload: { json: null },
expectedStatuses: [200, 401], // 401 is OK for unauthenticated requests
timeout: 5000,
critical: true,
},
// Dashboard endpoints
{
name: "Dashboard - Get Metrics",
path: "/api/trpc/dashboard.getMetrics",
method: "POST",
payload: { json: { dateRange: "7d" } },
expectedStatuses: [200, 401, 403],
timeout: 10000,
critical: true,
},
{
name: "Dashboard - Get Sessions",
path: "/api/trpc/dashboard.getSessions",
method: "POST",
payload: {
json: {
page: 1,
pageSize: 10,
filters: {}
}
},
expectedStatuses: [200, 401, 403],
timeout: 10000,
critical: true,
},
{
name: "Dashboard - Get Session Filter Options",
path: "/api/trpc/dashboard.getSessionFilterOptions",
method: "POST",
payload: { json: null },
expectedStatuses: [200, 401, 403],
timeout: 5000,
critical: false,
},
// Admin endpoints
{
name: "Admin - Get System Health",
path: "/api/trpc/admin.getSystemHealth",
method: "POST",
payload: { json: null },
expectedStatuses: [200, 401, 403],
timeout: 15000,
critical: false,
},
{
name: "Admin - Get Processing Status",
path: "/api/trpc/admin.getProcessingStatus",
method: "POST",
payload: { json: null },
expectedStatuses: [200, 401, 403],
timeout: 10000,
critical: false,
},
// Batch request endpoints (if available)
{
name: "Admin - Get Batch Requests",
path: "/api/trpc/admin.getBatchRequests",
method: "POST",
payload: { json: { page: 1, pageSize: 10 } },
expectedStatuses: [200, 401, 403, 404], // 404 OK if endpoint doesn't exist yet
timeout: 10000,
critical: false,
},
// Test invalid endpoint (should return 404)
{
name: "Invalid Endpoint Test",
path: "/api/trpc/nonexistent.invalidMethod",
method: "POST",
payload: { json: null },
expectedStatuses: [404, 400],
timeout: 5000,
critical: false,
},
];
// Run all tests
for (const test of endpointTests) {
const result = await this.runSingleTest(test);
tests.push(result);
}
const totalDuration = Date.now() - startTime;
const passedTests = tests.filter(t => t.success).length;
const failedTests = tests.filter(t => !t.success).length;
const criticalFailures = tests.filter(t => !t.success && endpointTests.find(et => et.name === t.name)?.critical).length;
const result: TRPCTestResult = {
success: criticalFailures === 0,
tests,
totalDuration,
passedTests,
failedTests,
criticalFailures,
};
if (result.success) {
migrationLogger.completeStep("TRPC_TESTS");
} else {
migrationLogger.failStep("TRPC_TESTS", new Error(`${criticalFailures} critical tRPC tests failed`));
}
return result;
} catch (error) {
migrationLogger.error("TRPC_TESTS", "tRPC test suite failed", error as Error);
throw error;
}
}
private async runSingleTest(test: EndpointTest): Promise<TestResult> {
const startTime = Date.now();
try {
migrationLogger.debug("TRPC_TEST", `Testing: ${test.name}`);
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), test.timeout);
const url = `${this.baseUrl}${test.path}`;
const response = await fetch(url, {
method: test.method,
headers: {
"Content-Type": "application/json",
},
body: test.payload ? JSON.stringify(test.payload) : null,
signal: controller.signal,
});
clearTimeout(timeoutId);
const duration = Date.now() - startTime;
// Check if status is expected
const success = test.expectedStatuses.includes(response.status);
let responseData: unknown;
try {
responseData = await response.json();
} catch {
// Response might not be JSON, that's OK
responseData = await response.text();
}
const result: TestResult = {
name: test.name,
success,
status: response.status,
duration,
response: responseData,
};
if (success) {
migrationLogger.debug("TRPC_TEST", `${test.name} passed`, {
status: response.status,
duration
});
} else {
migrationLogger.warn("TRPC_TEST", `${test.name} failed`, {
status: response.status,
expected: test.expectedStatuses,
duration
});
}
return result;
} catch (error) {
const duration = Date.now() - startTime;
migrationLogger.error("TRPC_TEST", `💥 ${test.name} crashed`, error as Error, { duration });
return {
name: test.name,
success: false,
status: 0,
duration,
error: error as Error,
};
}
}
/**
* Test tRPC batch requests
*/
async testBatchRequests(): Promise<TestResult> {
const startTime = Date.now();
try {
migrationLogger.info("TRPC_BATCH", "Testing tRPC batch requests");
// Create a batch request with multiple calls
const batchPayload = [
{
id: 1,
jsonrpc: "2.0",
method: "query",
params: {
path: "auth.getSession",
input: { json: null },
},
},
{
id: 2,
jsonrpc: "2.0",
method: "query",
params: {
path: "dashboard.getMetrics",
input: { json: { dateRange: "7d" } },
},
},
];
const response = await fetch(`${this.baseUrl}/api/trpc`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(batchPayload),
});
const duration = Date.now() - startTime;
const responseData = await response.json();
// Batch requests should return an array of responses
const success = response.ok && Array.isArray(responseData) && responseData.length === 2;
return {
name: "tRPC Batch Requests",
success,
status: response.status,
duration,
response: responseData,
};
} catch (error) {
const duration = Date.now() - startTime;
return {
name: "tRPC Batch Requests",
success: false,
status: 0,
duration,
error: error as Error,
};
}
}
/**
* Test tRPC subscription endpoints (if available)
*/
async testSubscriptions(): Promise<TestResult> {
const startTime = Date.now();
try {
migrationLogger.info("TRPC_SUBSCRIPTIONS", "Testing tRPC subscriptions");
// Test if WebSocket connection is available for subscriptions
const wsUrl = this.baseUrl.replace(/^https?/, "ws") + "/api/trpc";
return new Promise<TestResult>((resolve) => {
try {
const ws = new WebSocket(wsUrl);
const timeout = setTimeout(() => {
ws.close();
resolve({
name: "tRPC Subscriptions",
success: false,
status: 0,
duration: Date.now() - startTime,
error: new Error("WebSocket connection timeout"),
});
}, 5000);
ws.onopen = () => {
clearTimeout(timeout);
ws.close();
resolve({
name: "tRPC Subscriptions",
success: true,
status: 200,
duration: Date.now() - startTime,
});
};
ws.onerror = (error) => {
clearTimeout(timeout);
resolve({
name: "tRPC Subscriptions",
success: false,
status: 0,
duration: Date.now() - startTime,
error: new Error("WebSocket connection failed"),
});
};
} catch (error) {
resolve({
name: "tRPC Subscriptions",
success: false,
status: 0,
duration: Date.now() - startTime,
error: error as Error,
});
}
});
} catch (error) {
const duration = Date.now() - startTime;
return {
name: "tRPC Subscriptions",
success: false,
status: 0,
duration,
error: error as Error,
};
}
}
/**
* Generate test report
*/
generateTestReport(result: TRPCTestResult): string {
const report = `
# tRPC Endpoint Test Report
**Overall Status**: ${result.success ? '✅ All Critical Tests Passed' : '❌ Critical Tests Failed'}
**Total Duration**: ${result.totalDuration}ms
**Passed Tests**: ${result.passedTests}/${result.tests.length}
**Failed Tests**: ${result.failedTests}/${result.tests.length}
**Critical Failures**: ${result.criticalFailures}
## Test Results
${result.tests.map(test => `
### ${test.name}
- **Status**: ${test.success ? '✅ Pass' : '❌ Fail'}
- **HTTP Status**: ${test.status}
- **Duration**: ${test.duration}ms
${test.error ? `- **Error**: ${test.error.message}` : ''}
${test.response && typeof test.response === 'object' ? `- **Response**: \`\`\`json\n${JSON.stringify(test.response, null, 2)}\n\`\`\`` : ''}
`).join('')}
## Summary
${result.success ?
'🎉 All critical tRPC endpoints are working correctly!' :
`⚠️ ${result.criticalFailures} critical endpoint(s) failed. Please review and fix the issues above.`
}
## Recommendations
${result.failedTests > 0 ? `
### Failed Tests Analysis
${result.tests.filter(t => !t.success).map(test => `
- **${test.name}**: ${test.error?.message || `HTTP ${test.status}`}
`).join('')}
### Next Steps
1. Check server logs for detailed error information
2. Verify tRPC router configuration
3. Ensure all required dependencies are installed
4. Validate environment configuration
5. Test endpoints manually if needed
` : `
### Optimization Opportunities
1. Monitor response times for performance optimization
2. Consider implementing caching for frequently accessed endpoints
3. Add monitoring and alerting for endpoint health
4. Implement rate limiting if not already in place
`}
---
*Generated at ${new Date().toISOString()}*
`;
return report;
}
}
// CLI interface
if (import.meta.url === `file://${process.argv[1]}`) {
const baseUrl = process.argv[2];
const tester = new TRPCEndpointTester(baseUrl);
const generateReport = process.argv.includes("--report");
const testBatch = process.argv.includes("--batch");
const testSubscriptions = process.argv.includes("--subscriptions");
async function runTests() {
// Run main endpoint tests
const result = await tester.runEndpointTests();
// Run additional tests if requested
if (testBatch) {
const batchResult = await tester.testBatchRequests();
result.tests.push(batchResult);
if (!batchResult.success) {
result.failedTests++;
} else {
result.passedTests++;
}
}
if (testSubscriptions) {
const subscriptionResult = await tester.testSubscriptions();
result.tests.push(subscriptionResult);
if (!subscriptionResult.success) {
result.failedTests++;
} else {
result.passedTests++;
}
}
return result;
}
runTests()
.then((result) => {
console.log('\n=== tRPC ENDPOINT TEST RESULTS ===');
console.log(`Overall Success: ${result.success ? '✅' : '❌'}`);
console.log(`Total Duration: ${result.totalDuration}ms`);
console.log(`Passed Tests: ${result.passedTests}/${result.tests.length}`);
console.log(`Failed Tests: ${result.failedTests}/${result.tests.length}`);
console.log(`Critical Failures: ${result.criticalFailures}`);
console.log('\n=== INDIVIDUAL TEST RESULTS ===');
for (const test of result.tests) {
const status = test.success ? '✅' : '❌';
console.log(`${status} ${test.name} (HTTP ${test.status}, ${test.duration}ms)`);
if (test.error) {
console.log(` Error: ${test.error.message}`);
}
}
if (generateReport) {
const report = tester.generateTestReport(result);
const fs = require("node:fs");
const reportPath = `trpc-test-report-${Date.now()}.md`;
fs.writeFileSync(reportPath, report);
console.log(`\n📋 Test report saved to: ${reportPath}`);
}
process.exit(result.success ? 0 : 1);
})
.catch((error) => {
console.error('tRPC endpoint tests failed:', error);
process.exit(1);
});
}