fix: address multiple PR review issues

- Fixed accessibility in audit logs with keyboard navigation and ARIA attributes
- Refactored ThreatAnalysisResults interface to module level for reusability
- Added BatchOperation enum validation and proper CSV escaping in batch monitoring
- Removed unused company state causing skeleton view in dashboard overview
- Enhanced error handling with user-facing messages for metrics loading
- Replaced hardcoded timeouts with condition-based waits in E2E tests
- Removed duplicate state management in security monitoring hooks
- Fixed CSRF documentation to show proper secret fallback pattern
- Updated CSP metrics docs with GDPR Article 6(1)(f) legal basis clarification
- Fixed React hooks order to prevent conditional execution after early returns
- Added explicit button type to prevent form submission behavior
This commit is contained in:
2025-07-14 00:24:10 +02:00
parent bba79d509b
commit ef1f0769c2
9 changed files with 221 additions and 77 deletions

View File

@ -9,6 +9,26 @@ import {
import { getCircuitBreakerStatus } from "@/lib/batchProcessor";
import { getBatchSchedulerStatus } from "@/lib/batchProcessorIntegration";
// Helper function for proper CSV escaping
function escapeCSVField(field: string | number | boolean): string {
if (typeof field === "number" || typeof field === "boolean") {
return String(field);
}
const strField = String(field);
// If field contains comma, quote, or newline, wrap in quotes and escape internal quotes
if (
strField.includes(",") ||
strField.includes('"') ||
strField.includes("\n")
) {
return `"${strField.replace(/"/g, '""')}"`;
}
return strField;
}
/**
* GET /api/admin/batch-monitoring
* Get comprehensive batch processing monitoring data
@ -23,9 +43,31 @@ export async function GET(request: NextRequest) {
const url = new URL(request.url);
const companyId = url.searchParams.get("companyId");
const operation = url.searchParams.get("operation") as BatchOperation;
const operationParam = url.searchParams.get("operation");
const format = url.searchParams.get("format") || "json";
// Validate operation parameter
const isValidBatchOperation = (
value: string | null
): value is BatchOperation => {
return (
value !== null &&
Object.values(BatchOperation).includes(value as BatchOperation)
);
};
if (operationParam && !isValidBatchOperation(operationParam)) {
return NextResponse.json(
{
error: "Invalid operation parameter",
validOperations: Object.values(BatchOperation),
},
{ status: 400 }
);
}
const operation = operationParam as BatchOperation | null;
// Get batch processing metrics
const metrics = batchLogger.getMetrics(companyId || undefined);
@ -75,15 +117,15 @@ export async function GET(request: NextRequest) {
const rows = Object.entries(metrics).map(([companyId, metric]) =>
[
companyId,
new Date(metric.operationStartTime).toISOString(),
metric.requestCount,
metric.successCount,
metric.failureCount,
metric.retryCount,
metric.totalCost.toFixed(4),
metric.averageLatency.toFixed(2),
metric.circuitBreakerTrips,
escapeCSVField(companyId),
escapeCSVField(new Date(metric.operationStartTime).toISOString()),
escapeCSVField(metric.requestCount),
escapeCSVField(metric.successCount),
escapeCSVField(metric.failureCount),
escapeCSVField(metric.retryCount),
escapeCSVField(metric.totalCost.toFixed(4)),
escapeCSVField(metric.averageLatency.toFixed(2)),
escapeCSVField(metric.circuitBreakerTrips),
].join(",")
);
@ -132,10 +174,55 @@ export async function POST(request: NextRequest) {
end: new Date(endDate),
};
const exportData = batchLogger.exportLogs(timeRange);
const exportDataJson = batchLogger.exportLogs(timeRange);
if (format === "csv") {
return new NextResponse(exportData, {
// Convert JSON to CSV format
const data = JSON.parse(exportDataJson);
// Flatten the data structure for CSV
const csvRows: string[] = [];
// Add headers
csvRows.push(
"Metric,Company ID,Operation,Batch ID,Request Count,Success Count,Failure Count,Average Latency,Last Updated"
);
// Add metrics data
if (data.metrics) {
interface MetricData {
companyId?: string;
operation?: string;
batchId?: string;
requestCount?: number;
successCount?: number;
failureCount?: number;
averageLatency?: number;
lastUpdated?: string;
}
Object.entries(data.metrics).forEach(
([key, metric]: [string, MetricData]) => {
csvRows.push(
[
escapeCSVField(key),
escapeCSVField(metric.companyId || ""),
escapeCSVField(metric.operation || ""),
escapeCSVField(metric.batchId || ""),
escapeCSVField(metric.requestCount || 0),
escapeCSVField(metric.successCount || 0),
escapeCSVField(metric.failureCount || 0),
escapeCSVField(metric.averageLatency || 0),
escapeCSVField(metric.lastUpdated || ""),
].join(",")
);
}
);
}
const csvContent = csvRows.join("\n");
return new NextResponse(csvContent, {
headers: {
"Content-Type": "text/csv",
"Content-Disposition": `attachment; filename="batch-logs-${startDate}-${endDate}.csv"`,
@ -143,7 +230,7 @@ export async function POST(request: NextRequest) {
});
}
return new NextResponse(exportData, {
return new NextResponse(exportDataJson, {
headers: {
"Content-Type": "application/json",
"Content-Disposition": `attachment; filename="batch-logs-${startDate}-${endDate}.json"`,

View File

@ -14,6 +14,31 @@ import {
type ThreatLevel,
} from "@/lib/securityMonitoring";
interface ThreatAnalysisResults {
ipThreatAnalysis?: {
ipAddress: string;
threatLevel: ThreatLevel;
isBlacklisted: boolean;
riskFactors: string[];
recommendations: string[];
};
timeRangeAnalysis?: {
timeRange: { start: Date; end: Date };
securityScore: number;
threatLevel: string;
topThreats: Array<{ type: AlertType; count: number }>;
geoDistribution: Record<string, number>;
riskUsers: Array<{ userId: string; email: string; riskScore: number }>;
};
overallThreatLandscape?: {
currentThreatLevel: string;
securityScore: number;
activeAlerts: number;
criticalEvents: number;
recommendations: string[];
};
}
const threatAnalysisSchema = z.object({
ipAddress: z.string().optional(),
userId: z.string().uuid().optional(),
@ -37,31 +62,6 @@ export async function POST(request: NextRequest) {
const analysis = threatAnalysisSchema.parse(body);
const context = await createAuditContext(request, session);
interface ThreatAnalysisResults {
ipThreatAnalysis?: {
ipAddress: string;
threatLevel: ThreatLevel;
isBlacklisted: boolean;
riskFactors: string[];
recommendations: string[];
};
timeRangeAnalysis?: {
timeRange: { start: Date; end: Date };
securityScore: number;
threatLevel: string;
topThreats: Array<{ type: AlertType; count: number }>;
geoDistribution: Record<string, number>;
riskUsers: Array<{ userId: string; email: string; riskScore: number }>;
};
overallThreatLandscape?: {
currentThreatLevel: string;
securityScore: number;
activeAlerts: number;
criticalEvents: number;
recommendations: string[];
};
}
const results: ThreatAnalysisResults = {};
// IP threat analysis