2026-06-07 03:58:32 -04:00
|
|
|
import { NextRequest, NextResponse } from "next/server";
|
|
|
|
|
|
|
|
|
|
// Better Auth's session cookie name (default prefix "better-auth"); the
|
|
|
|
|
// "__Secure-" variant is used when cookies are served over HTTPS in production.
|
|
|
|
|
const SESSION_COOKIES = ["better-auth.session_token", "__Secure-better-auth.session_token"];
|
|
|
|
|
|
2026-06-20 20:59:03 -04:00
|
|
|
// Authed surfaces that require an optimistic session-cookie check. Anonymous users
|
|
|
|
|
// hitting these are redirected to /sign-in. Public/marketing/auth routes are NOT
|
|
|
|
|
// listed here, so they are never redirected (CSP still applies to them, below).
|
|
|
|
|
const AUTHED_PREFIXES = [
|
|
|
|
|
"/dashboard",
|
|
|
|
|
"/episodes",
|
|
|
|
|
"/series",
|
|
|
|
|
"/usage",
|
|
|
|
|
"/billing",
|
|
|
|
|
"/team",
|
|
|
|
|
"/api-keys",
|
|
|
|
|
"/settings",
|
|
|
|
|
"/admin",
|
|
|
|
|
];
|
|
|
|
|
|
2026-06-07 03:58:32 -04:00
|
|
|
/**
|
2026-06-20 20:59:03 -04:00
|
|
|
* Runs on every request (see matcher). Two responsibilities:
|
|
|
|
|
*
|
|
|
|
|
* 1. CSP/nonce (all routes): generate a per-request base64 nonce with the Web Crypto
|
|
|
|
|
* API (Edge-safe — no node:crypto), expose it on the inbound `x-nonce` request
|
|
|
|
|
* header, and set a nonce-based Content-Security-Policy response header. Next.js
|
|
|
|
|
* auto-applies this nonce to its own framework <script> tags when the `x-nonce`
|
|
|
|
|
* request header is present; the root layout may also read it via `headers()` to
|
|
|
|
|
* nonce any manual inline scripts.
|
|
|
|
|
*
|
|
|
|
|
* 2. Optimistic edge gate (authed prefixes only): redirect anonymous users away from
|
|
|
|
|
* authed surfaces. Only checks for the *presence* of a session cookie — real
|
|
|
|
|
* session validation (and admin/role checks) happen in the route-group layouts.
|
2026-06-07 03:58:32 -04:00
|
|
|
*/
|
|
|
|
|
export function middleware(req: NextRequest) {
|
|
|
|
|
const { pathname, search } = req.nextUrl;
|
|
|
|
|
|
2026-06-20 20:59:03 -04:00
|
|
|
// Per-request nonce (base64). randomUUID is Edge-runtime safe and unguessable.
|
|
|
|
|
const nonce = Buffer.from(crypto.randomUUID()).toString("base64");
|
|
|
|
|
const csp = [
|
|
|
|
|
"default-src 'self'",
|
|
|
|
|
`script-src 'self' 'nonce-${nonce}' 'strict-dynamic'`,
|
|
|
|
|
"style-src 'self' 'unsafe-inline'",
|
|
|
|
|
"img-src 'self' data: https://oaidalleapiprodscus.blob.core.windows.net https://images.unsplash.com",
|
|
|
|
|
"media-src 'self'",
|
|
|
|
|
"connect-src 'self'",
|
|
|
|
|
"frame-ancestors 'none'",
|
|
|
|
|
"base-uri 'self'",
|
|
|
|
|
"form-action 'self'",
|
|
|
|
|
].join("; ");
|
|
|
|
|
|
|
|
|
|
// Optimistic auth gate for the previously-matched authed prefixes only.
|
|
|
|
|
const isAuthedPath = AUTHED_PREFIXES.some(
|
|
|
|
|
(p) => pathname === p || pathname.startsWith(p + "/")
|
|
|
|
|
);
|
|
|
|
|
if (isAuthedPath) {
|
|
|
|
|
const hasSession = SESSION_COOKIES.some((name) => req.cookies.has(name));
|
|
|
|
|
if (!hasSession) {
|
|
|
|
|
const signIn = new URL("/sign-in", req.url);
|
|
|
|
|
signIn.searchParams.set("redirect", pathname + search);
|
|
|
|
|
return NextResponse.redirect(signIn);
|
|
|
|
|
}
|
2026-06-07 03:58:32 -04:00
|
|
|
}
|
|
|
|
|
|
2026-06-20 20:59:03 -04:00
|
|
|
// Forward the nonce to the app via a request header, and set the CSP on the response.
|
|
|
|
|
const requestHeaders = new Headers(req.headers);
|
|
|
|
|
requestHeaders.set("x-nonce", nonce);
|
|
|
|
|
requestHeaders.set("Content-Security-Policy", csp);
|
|
|
|
|
|
|
|
|
|
const res = NextResponse.next({ request: { headers: requestHeaders } });
|
|
|
|
|
res.headers.set("Content-Security-Policy", csp);
|
|
|
|
|
return res;
|
2026-06-07 03:58:32 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export const config = {
|
|
|
|
|
matcher: [
|
2026-06-20 20:59:03 -04:00
|
|
|
// Run on every request EXCEPT static assets so CSP applies app-wide while
|
|
|
|
|
// avoiding unnecessary work on prefetched/static files.
|
|
|
|
|
{
|
|
|
|
|
source: "/((?!_next/static|_next/image|favicon.ico).*)",
|
|
|
|
|
missing: [
|
|
|
|
|
{ type: "header", key: "next-router-prefetch" },
|
|
|
|
|
{ type: "header", key: "purpose", value: "prefetch" },
|
|
|
|
|
],
|
|
|
|
|
},
|
2026-06-07 03:58:32 -04:00
|
|
|
],
|
|
|
|
|
};
|