mirror of
https://github.com/kjanat/livedash-node.git
synced 2026-01-16 10:52:08 +01:00
refactor: fix biome linting issues and update project documentation
- Fix 36+ biome linting issues reducing errors/warnings from 227 to 191 - Replace explicit 'any' types with proper TypeScript interfaces - Fix React hooks dependencies and useCallback patterns - Resolve unused variables and parameter assignment issues - Improve accessibility with proper label associations - Add comprehensive API documentation for admin and security features - Update README.md with accurate PostgreSQL setup and current tech stack - Create complete documentation for audit logging, CSP monitoring, and batch processing - Fix outdated project information and missing developer workflows
This commit is contained in:
126
middleware.ts
126
middleware.ts
@ -1,36 +1,116 @@
|
||||
import type { NextRequest } from "next/server";
|
||||
import { NextResponse } from "next/server";
|
||||
import { authRateLimitMiddleware } from "./middleware/authRateLimit";
|
||||
import { csrfProtectionMiddleware, csrfTokenMiddleware } from "./middleware/csrfProtection";
|
||||
import { type NextRequest, NextResponse } from "next/server";
|
||||
import { buildCSP, generateNonce } from "@/lib/csp";
|
||||
|
||||
export async function middleware(request: NextRequest) {
|
||||
// Handle CSRF token requests first
|
||||
const csrfTokenResponse = csrfTokenMiddleware(request);
|
||||
if (csrfTokenResponse) {
|
||||
return csrfTokenResponse;
|
||||
export function middleware(request: NextRequest) {
|
||||
// Skip CSP for API routes (except CSP report endpoint)
|
||||
if (
|
||||
request.nextUrl.pathname.startsWith("/api") &&
|
||||
!request.nextUrl.pathname.startsWith("/api/csp-report")
|
||||
) {
|
||||
return NextResponse.next();
|
||||
}
|
||||
|
||||
// Apply auth rate limiting
|
||||
const authRateLimitResponse = authRateLimitMiddleware(request);
|
||||
if (authRateLimitResponse.status === 429) {
|
||||
return authRateLimitResponse;
|
||||
// Skip CSP for static assets
|
||||
if (
|
||||
request.nextUrl.pathname.startsWith("/_next") ||
|
||||
request.nextUrl.pathname.startsWith("/favicon") ||
|
||||
request.nextUrl.pathname.includes(".")
|
||||
) {
|
||||
return NextResponse.next();
|
||||
}
|
||||
|
||||
// Apply CSRF protection
|
||||
const csrfResponse = await csrfProtectionMiddleware(request);
|
||||
if (csrfResponse.status === 403) {
|
||||
return csrfResponse;
|
||||
const response = NextResponse.next();
|
||||
const nonce = generateNonce();
|
||||
const isDevelopment = process.env.NODE_ENV === "development";
|
||||
|
||||
// Build CSP with nonce and report URI
|
||||
const csp = buildCSP({
|
||||
nonce,
|
||||
isDevelopment,
|
||||
reportUri: "/api/csp-report",
|
||||
enforceMode: true,
|
||||
strictMode: !isDevelopment, // Enable strict mode in production
|
||||
reportingLevel: "violations",
|
||||
});
|
||||
|
||||
// Set enhanced security headers
|
||||
response.headers.set("Content-Security-Policy", csp);
|
||||
|
||||
// Modern CSP violation reporting
|
||||
response.headers.set(
|
||||
"Report-To",
|
||||
JSON.stringify({
|
||||
group: "csp-endpoint",
|
||||
max_age: 86400,
|
||||
endpoints: [{ url: "/api/csp-report" }],
|
||||
include_subdomains: true,
|
||||
})
|
||||
);
|
||||
|
||||
// Store nonce for components to use
|
||||
response.headers.set("X-Nonce", nonce);
|
||||
|
||||
// Enhanced security headers
|
||||
response.headers.set("X-Content-Type-Options", "nosniff");
|
||||
response.headers.set("X-Frame-Options", "DENY");
|
||||
response.headers.set("X-XSS-Protection", "1; mode=block");
|
||||
response.headers.set("Referrer-Policy", "strict-origin-when-cross-origin");
|
||||
response.headers.set("X-DNS-Prefetch-Control", "off");
|
||||
|
||||
// Permissions Policy - more restrictive than before
|
||||
response.headers.set(
|
||||
"Permissions-Policy",
|
||||
[
|
||||
"camera=()",
|
||||
"microphone=()",
|
||||
"geolocation=()",
|
||||
"interest-cohort=()",
|
||||
"browsing-topics=()",
|
||||
"display-capture=()",
|
||||
"fullscreen=(self)",
|
||||
"web-share=(self)",
|
||||
"clipboard-read=()",
|
||||
"clipboard-write=(self)",
|
||||
"payment=()",
|
||||
"usb=()",
|
||||
"bluetooth=()",
|
||||
"midi=()",
|
||||
"accelerometer=()",
|
||||
"gyroscope=()",
|
||||
"magnetometer=()",
|
||||
"ambient-light-sensor=()",
|
||||
"encrypted-media=()",
|
||||
"autoplay=(self)",
|
||||
].join(", ")
|
||||
);
|
||||
|
||||
// HSTS only in production
|
||||
if (process.env.NODE_ENV === "production") {
|
||||
response.headers.set(
|
||||
"Strict-Transport-Security",
|
||||
"max-age=31536000; includeSubDomains; preload"
|
||||
);
|
||||
}
|
||||
|
||||
return NextResponse.next();
|
||||
// Additional security headers
|
||||
response.headers.set("X-Permitted-Cross-Domain-Policies", "none");
|
||||
response.headers.set("Cross-Origin-Embedder-Policy", "require-corp");
|
||||
response.headers.set("Cross-Origin-Opener-Policy", "same-origin");
|
||||
response.headers.set("Cross-Origin-Resource-Policy", "same-origin");
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
// Configure which routes the middleware runs on
|
||||
export const config = {
|
||||
matcher: [
|
||||
// Apply to API routes
|
||||
"/api/:path*",
|
||||
// Exclude static files and images
|
||||
"/((?!_next/static|_next/image|favicon.ico).*)",
|
||||
/*
|
||||
* Match all request paths except for the ones starting with:
|
||||
* - api (API routes, handled separately)
|
||||
* - _next/static (static files)
|
||||
* - _next/image (image optimization files)
|
||||
* - favicon.ico (favicon file)
|
||||
* - public folder files
|
||||
*/
|
||||
"/((?!api|_next/static|_next/image|favicon.ico|.*\\..*|public).*)",
|
||||
],
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user