Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 | 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 57x 57x 57x 57x 57x 57x 57x 57x 57x 57x 57x 57x 57x 57x 57x 57x 57x 57x 57x 57x 57x 57x 57x 57x 57x 57x 57x 57x 57x 57x 57x 57x 57x 57x 57x 57x 57x 57x 57x 57x 57x 57x 57x 57x 57x 57x 57x 57x 57x 57x 57x 57x 57x 57x 57x 57x 3x 3x 3x 3x 3x 57x 57x 57x 1x 1x 57x 57x 26x 26x 26x 26x 26x 14x 14x 12x 12x 26x 26x 6x 6x 6x 26x 37x 37x 37x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x | import { NextRequest, NextResponse } from "next/server";
import { auth } from "@/lib/auth/auth";
interface SessionUser {
id?: number;
email?: string;
name?: string;
role?: string;
}
/**
* Add security headers to the response
*/
function addSecurityHeaders(response: NextResponse): NextResponse {
// Content Security Policy
// Allows Stripe, inline styles (required by many React libraries), and self
const cspDirectives = [
"default-src 'self'",
// Scripts: self, Stripe, and inline (needed for Next.js)
"script-src 'self' 'unsafe-inline' 'unsafe-eval' https://js.stripe.com",
// Styles: self and inline (needed for Tailwind and component libraries)
"style-src 'self' 'unsafe-inline'",
// Images: self, data URIs, blobs, and HTTPS sources
"img-src 'self' blob: data: https:",
// Fonts: self
"font-src 'self' data:",
// Connect: self, Stripe API, EmailJS
"connect-src 'self' https://api.stripe.com https://api.emailjs.com",
// Frames: Stripe (for payment elements)
"frame-src https://js.stripe.com https://hooks.stripe.com",
// Object: none (security best practice)
"object-src 'none'",
// Base URI: self only
"base-uri 'self'",
// Form actions: self only
"form-action 'self'",
// Prevent framing by other sites
"frame-ancestors 'none'",
// Upgrade insecure requests in production
process.env.NODE_ENV === "production" ? "upgrade-insecure-requests" : "",
]
.filter(Boolean)
.join("; ");
response.headers.set("Content-Security-Policy", cspDirectives);
// Prevent MIME type sniffing
response.headers.set("X-Content-Type-Options", "nosniff");
// Prevent clickjacking
response.headers.set("X-Frame-Options", "DENY");
// Enable XSS protection (for older browsers)
response.headers.set("X-XSS-Protection", "1; mode=block");
// Control referrer information
response.headers.set("Referrer-Policy", "strict-origin-when-cross-origin");
// Disable browser features we don't use
response.headers.set(
"Permissions-Policy",
"camera=(), microphone=(), geolocation=(), payment=(self)"
);
// Prevent cross-domain policies
response.headers.set("X-Permitted-Cross-Domain-Policies", "none");
// HSTS in production
if (process.env.NODE_ENV === "production") {
response.headers.set(
"Strict-Transport-Security",
"max-age=31536000; includeSubDomains; preload"
);
}
return response;
}
export async function proxy(request: NextRequest) {
// Check if request is to admin routes
if (request.nextUrl.pathname.startsWith("/admin")) {
// Get the session
const session = await auth();
// If not authenticated, redirect to login
if (!session) {
return addSecurityHeaders(NextResponse.redirect(new URL("/signin", request.url)));
}
// Check user role
const userRole = (session.user as SessionUser)?.role;
if (userRole !== "ADMIN") {
// Redirect non-admin users to home page
return addSecurityHeaders(NextResponse.redirect(new URL("/", request.url)));
}
}
return addSecurityHeaders(NextResponse.next());
}
export const config = {
matcher: [
/*
* Match all request paths except for:
* - _next/static (static files)
* - _next/image (image optimization files)
* - favicon.ico (favicon file)
* - public folder
* - API webhooks (they need raw body for signature verification)
*/
"/((?!_next/static|_next/image|favicon.ico|.*\\..*|api/webhooks).*)",
]};
|