mirror of
https://github.com/kjanat/livedash-node.git
synced 2026-01-16 18:52:08 +01:00
This commit introduces a range of improvements across the application: - **Security:** - Adds authentication to the CSP metrics endpoint. - Hardens CSP bypass detection regex to prevent ReDoS attacks. - Improves CORS headers for the CSP metrics API. - Adds filtering for acknowledged alerts in security monitoring. - **Performance:** - Optimizes database connection pooling for NeonDB. - Improves session fetching with abort controller. - **Stability:** - Adds error handling to the tRPC demo component. - Fixes type inconsistencies in session data mapping. - **Docs & DX:** - Ignores files in git. - Fixes a token placeholder in the documentation.
120 lines
3.6 KiB
TypeScript
120 lines
3.6 KiB
TypeScript
import { type NextRequest, NextResponse } from "next/server";
|
|
import { getServerSession } from "next-auth";
|
|
import { authOptions } from "@/lib/auth";
|
|
import { cspMonitoring } from "@/lib/csp-monitoring";
|
|
import { extractClientIP, rateLimiter } from "@/lib/rateLimiter";
|
|
|
|
export async function GET(request: NextRequest) {
|
|
try {
|
|
// Authentication check for security metrics endpoint
|
|
const session = await getServerSession(authOptions);
|
|
|
|
if (!session?.user || !session.user.isPlatformUser) {
|
|
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
|
}
|
|
// Rate limiting for metrics endpoint
|
|
const ip = extractClientIP(request);
|
|
const rateLimitResult = await rateLimiter.check(
|
|
`csp-metrics:${ip}`,
|
|
30, // 30 requests
|
|
60 * 1000 // per minute
|
|
);
|
|
|
|
if (!rateLimitResult.success) {
|
|
return NextResponse.json({ error: "Too many requests" }, { status: 429 });
|
|
}
|
|
|
|
// Parse query parameters
|
|
const url = new URL(request.url);
|
|
const timeRange = url.searchParams.get("range") || "24h";
|
|
const format = url.searchParams.get("format") || "json";
|
|
|
|
// Calculate time range
|
|
const now = new Date();
|
|
let start: Date;
|
|
|
|
switch (timeRange) {
|
|
case "1h":
|
|
start = new Date(now.getTime() - 60 * 60 * 1000);
|
|
break;
|
|
case "6h":
|
|
start = new Date(now.getTime() - 6 * 60 * 60 * 1000);
|
|
break;
|
|
case "24h":
|
|
start = new Date(now.getTime() - 24 * 60 * 60 * 1000);
|
|
break;
|
|
case "7d":
|
|
start = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
|
|
break;
|
|
case "30d":
|
|
start = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000);
|
|
break;
|
|
default:
|
|
start = new Date(now.getTime() - 24 * 60 * 60 * 1000);
|
|
}
|
|
|
|
// Get metrics from monitoring service
|
|
const metrics = cspMonitoring.getMetrics({ start, end: now });
|
|
|
|
// Get policy recommendations
|
|
const recommendations = cspMonitoring.generatePolicyRecommendations({
|
|
start,
|
|
end: now,
|
|
});
|
|
|
|
const response = {
|
|
timeRange: {
|
|
start: start.toISOString(),
|
|
end: now.toISOString(),
|
|
range: timeRange,
|
|
},
|
|
summary: {
|
|
totalViolations: metrics.totalViolations,
|
|
criticalViolations: metrics.criticalViolations,
|
|
bypassAttempts: metrics.bypassAttempts,
|
|
violationRate:
|
|
metrics.totalViolations /
|
|
((now.getTime() - start.getTime()) / (60 * 60 * 1000)), // per hour
|
|
},
|
|
topViolatedDirectives: metrics.topViolatedDirectives,
|
|
topBlockedUris: metrics.topBlockedUris,
|
|
violationTrends: metrics.violationTrends,
|
|
recommendations: recommendations,
|
|
lastUpdated: now.toISOString(),
|
|
};
|
|
|
|
// Export format handling
|
|
if (format === "csv") {
|
|
const csv = cspMonitoring.exportViolations("csv");
|
|
return new NextResponse(csv, {
|
|
headers: {
|
|
"Content-Type": "text/csv",
|
|
"Content-Disposition": `attachment; filename="csp-violations-${timeRange}.csv"`,
|
|
},
|
|
});
|
|
}
|
|
|
|
return NextResponse.json(response);
|
|
} catch (error) {
|
|
console.error("Error fetching CSP metrics:", error);
|
|
return NextResponse.json(
|
|
{ error: "Failed to fetch metrics" },
|
|
{ status: 500 }
|
|
);
|
|
}
|
|
}
|
|
|
|
// Handle preflight requests
|
|
export async function OPTIONS() {
|
|
return new NextResponse(null, {
|
|
status: 200,
|
|
headers: {
|
|
"Access-Control-Allow-Origin":
|
|
process.env.ALLOWED_ORIGINS || "https://livedash.notso.ai",
|
|
"Access-Control-Allow-Methods": "GET, OPTIONS",
|
|
"Access-Control-Allow-Headers": "Content-Type, Authorization",
|
|
"Access-Control-Allow-Credentials": "true",
|
|
},
|
|
});
|
|
}
|