Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// app/layout.tsx - Updated version
import "./globals.css";
import type { Metadata } from "next";
import { SavedProvider } from "./saved/SavedContext";
Expand All @@ -9,6 +10,7 @@ import { Toaster } from "@/components/ui/toaster";
import { SpeedInsights } from "@vercel/speed-insights/next";
import DiscordPromo from "@/components/DiscordPromo";
import ProjectUpdaterWorker from "@/components/ProjectUpdaterWorker";
import { headers } from "next/headers";

export const metadata: Metadata = {
title: "Code Gems - Discover Remarkable GitHub Projects",
Expand Down Expand Up @@ -70,15 +72,27 @@ export const metadata: Metadata = {
},
};

export default function RootLayout({
export default async function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
// Get the CSP nonce from headers
const headersList = await headers();
const nonce = headersList.get('X-CSP-Nonce') || undefined;

return (
<html lang="en">
<head>
<link rel="canonical" href="https://codegems.xyz" />
{nonce && (
<script
nonce={nonce}
dangerouslySetInnerHTML={{
__html: `window.__CSP_NONCE__ = '${nonce}';`
}}
/>
)}
</head>
<body>
<AuthProvider>
Expand Down
26 changes: 15 additions & 11 deletions lib/security/security-headers.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// lib/security-headers.ts
// lib/security/security-headers.ts - Updated version
import { NextRequest, NextResponse } from 'next/server';
import React from 'react';

Expand All @@ -18,23 +18,27 @@ export function applySecurityHeaders(
// Generate CSP nonce if not provided
const cspNonce = nonce || generateNonce();

// Content Security Policy - Strict
// Content Security Policy - More lenient for development
const isDevelopment = process.env.NODE_ENV === 'development';

const csp = [
"default-src 'self'",
`script-src 'self' 'nonce-${cspNonce}' https://cdnjs.cloudflare.com https://va.vercel-scripts.com`,
"style-src 'self' 'unsafe-inline'", // Unfortunately needed for Tailwind
isDevelopment
? `script-src 'self' 'unsafe-inline' 'unsafe-eval' 'nonce-${cspNonce}' https://cdnjs.cloudflare.com https://va.vercel-scripts.com https://vercel.live`
: `script-src 'self' 'nonce-${cspNonce}' https://cdnjs.cloudflare.com https://va.vercel-scripts.com`,
"style-src 'self' 'unsafe-inline' https://fonts.googleapis.com",
"img-src 'self' data: https: blob:",
"font-src 'self' data:",
"connect-src 'self' https://api.github.com https://*.supabase.co wss://*.supabase.co https://vitals.vercel-insights.com",
"font-src 'self' data: https://fonts.gstatic.com",
"connect-src 'self' https://api.github.com https://*.supabase.co wss://*.supabase.co https://vitals.vercel-insights.com https://vercel.live",
"frame-src 'none'",
"object-src 'none'",
"base-uri 'self'",
"form-action 'self'",
"frame-ancestors 'none'",
"upgrade-insecure-requests",
isDevelopment ? "" : "upgrade-insecure-requests",
"block-all-mixed-content",
"manifest-src 'self'"
].join('; ');
].filter(Boolean).join('; ');

response.headers.set('Content-Security-Policy', csp);

Expand All @@ -47,8 +51,8 @@ export function applySecurityHeaders(
response.headers.set('X-Download-Options', 'noopen');
response.headers.set('X-Permitted-Cross-Domain-Policies', 'none');

// Strict Transport Security (HSTS)
if (process.env.NODE_ENV === 'production') {
// Strict Transport Security (HSTS) - only in production
if (!isDevelopment) {
response.headers.set(
'Strict-Transport-Security',
'max-age=31536000; includeSubDomains; preload'
Expand Down Expand Up @@ -102,7 +106,7 @@ export function getSecurityHeadersForRoute(pathname: string): Record<string, str
return baseHeaders;
}

// React component to inject CSP nonce
// React component to inject CSP nonce - Updated
export function SecurityHeaders({ nonce }: { nonce: string }): React.ReactElement {
return React.createElement('script', {
nonce: nonce,
Expand Down
18 changes: 10 additions & 8 deletions middleware.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
// middleware.ts
// middleware.ts - Updated version
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import { securityMiddleware } from './lib/security/security-headers';
import { generateNonce, applySecurityHeaders } from './lib/security/security-headers';
import { rateLimit, createRateLimitHeaders } from './lib/security/rate-limiter-config';

export async function middleware(request: NextRequest) {
// Apply security headers first
let response = securityMiddleware(request);
// Generate a nonce for this request
const nonce = generateNonce();

// Create response with security headers and nonce
let response = NextResponse.next();
response = applySecurityHeaders(response, request, nonce);

// Apply rate limiting to API routes
if (request.nextUrl.pathname.startsWith('/api/')) {
Expand Down Expand Up @@ -106,10 +110,8 @@ export async function middleware(request: NextRequest) {
response.headers.set('X-Robots-Tag', 'noindex, nofollow, noarchive, nosnippet');
}

// Add security headers for all responses
response.headers.set('X-Content-Type-Options', 'nosniff');
response.headers.set('X-Frame-Options', 'DENY');
response.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin');
// Ensure the nonce is available for the layout
response.headers.set('X-CSP-Nonce', nonce);

return response;
}
Expand Down