mirror of
https://github.com/kjanat/livedash-node.git
synced 2026-01-16 17:12:10 +01:00
feat: enhance security, performance, and stability
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.
This commit is contained in:
@ -45,10 +45,18 @@ export async function GET(request: NextRequest) {
|
||||
const context = await createAuditContext(request, session);
|
||||
|
||||
// Get alerts based on filters
|
||||
const alerts = securityMonitoring.getActiveAlerts(
|
||||
let alerts = securityMonitoring.getActiveAlerts(
|
||||
query.severity as AlertSeverity
|
||||
);
|
||||
|
||||
// Apply acknowledged filter if provided
|
||||
if (query.acknowledged !== undefined) {
|
||||
const showAcknowledged = query.acknowledged === "true";
|
||||
alerts = alerts.filter((alert) =>
|
||||
showAcknowledged ? alert.acknowledged : !alert.acknowledged
|
||||
);
|
||||
}
|
||||
|
||||
// Apply pagination
|
||||
const limit = query.limit || 50;
|
||||
const offset = query.offset || 0;
|
||||
|
||||
@ -1,12 +1,19 @@
|
||||
import { type NextRequest, NextResponse } from "next/server";
|
||||
import { getServerSession } from "next-auth";
|
||||
import { authOptions } from "@/lib/auth";
|
||||
import { cspMonitoring } from "@/lib/csp-monitoring";
|
||||
import { rateLimiter } from "@/lib/rateLimiter";
|
||||
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 =
|
||||
request.ip || request.headers.get("x-forwarded-for") || "unknown";
|
||||
const ip = extractClientIP(request);
|
||||
const rateLimitResult = await rateLimiter.check(
|
||||
`csp-metrics:${ip}`,
|
||||
30, // 30 requests
|
||||
@ -102,9 +109,11 @@ export async function OPTIONS() {
|
||||
return new NextResponse(null, {
|
||||
status: 200,
|
||||
headers: {
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
"Access-Control-Allow-Origin":
|
||||
process.env.ALLOWED_ORIGINS || "https://livedash.notso.ai",
|
||||
"Access-Control-Allow-Methods": "GET, OPTIONS",
|
||||
"Access-Control-Allow-Headers": "Content-Type",
|
||||
"Access-Control-Allow-Headers": "Content-Type, Authorization",
|
||||
"Access-Control-Allow-Credentials": "true",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@ -51,11 +51,11 @@ function mapPrismaSessionToChatSession(prismaSession: {
|
||||
country: prismaSession.country ?? null,
|
||||
ipAddress: prismaSession.ipAddress ?? null,
|
||||
sentiment: prismaSession.sentiment ?? null,
|
||||
messagesSent: prismaSession.messagesSent ?? undefined, // Use undefined if ChatSession expects number | undefined
|
||||
messagesSent: prismaSession.messagesSent ?? null, // Maintain consistency with other nullable fields
|
||||
avgResponseTime: prismaSession.avgResponseTime ?? null,
|
||||
escalated: prismaSession.escalated ?? undefined,
|
||||
forwardedHr: prismaSession.forwardedHr ?? undefined,
|
||||
initialMsg: prismaSession.initialMsg ?? undefined,
|
||||
escalated: prismaSession.escalated,
|
||||
forwardedHr: prismaSession.forwardedHr,
|
||||
initialMsg: prismaSession.initialMsg ?? null,
|
||||
fullTranscriptUrl: prismaSession.fullTranscriptUrl ?? null,
|
||||
summary: prismaSession.summary ?? null, // New field
|
||||
transcriptContent: null, // Not available in Session model
|
||||
|
||||
@ -25,6 +25,8 @@ function usePlatformSession() {
|
||||
name?: string;
|
||||
role: string;
|
||||
companyId?: string;
|
||||
isPlatformUser?: boolean;
|
||||
platformRole?: string;
|
||||
};
|
||||
} | null>(null);
|
||||
const [status, setStatus] = useState<
|
||||
@ -32,26 +34,47 @@ function usePlatformSession() {
|
||||
>("loading");
|
||||
|
||||
useEffect(() => {
|
||||
const abortController = new AbortController();
|
||||
|
||||
const handleAuthSuccess = (sessionData: any) => {
|
||||
if (sessionData?.user?.isPlatformUser) {
|
||||
setSession(sessionData);
|
||||
setStatus("authenticated");
|
||||
} else {
|
||||
handleAuthFailure();
|
||||
}
|
||||
};
|
||||
|
||||
const handleAuthFailure = (error?: unknown) => {
|
||||
if (error instanceof Error && error.name === "AbortError") return;
|
||||
if (error) console.error("Platform session fetch error:", error);
|
||||
setSession(null);
|
||||
setStatus("unauthenticated");
|
||||
};
|
||||
|
||||
const fetchSession = async () => {
|
||||
try {
|
||||
const response = await fetch("/api/platform/auth/session");
|
||||
const sessionData = await response.json();
|
||||
const response = await fetch("/api/platform/auth/session", {
|
||||
signal: abortController.signal,
|
||||
});
|
||||
|
||||
if (sessionData?.user?.isPlatformUser) {
|
||||
setSession(sessionData);
|
||||
setStatus("authenticated");
|
||||
} else {
|
||||
setSession(null);
|
||||
setStatus("unauthenticated");
|
||||
if (!response.ok) {
|
||||
if (response.status === 401) return handleAuthFailure();
|
||||
throw new Error(`Failed to fetch session: ${response.status}`);
|
||||
}
|
||||
|
||||
const sessionData = await response.json();
|
||||
handleAuthSuccess(sessionData);
|
||||
} catch (error) {
|
||||
console.error("Platform session fetch error:", error);
|
||||
setSession(null);
|
||||
setStatus("unauthenticated");
|
||||
handleAuthFailure(error);
|
||||
}
|
||||
};
|
||||
|
||||
fetchSession();
|
||||
|
||||
return () => {
|
||||
abortController.abort();
|
||||
};
|
||||
}, []);
|
||||
|
||||
return { data: session, status };
|
||||
|
||||
Reference in New Issue
Block a user