proxy.ts
Rama main · réplica de lectura del repositorio original.
import { NextResponse } from "next/server";
import { auth } from "@/auth";
import { contentSecurityPolicy, createNonce } from "@/lib/security/csp";
// Paths anyone may open without signing in: auth/page-flow routes, the
// code-generated icon/apple-icon routes, top-level files under public/
// (matched generically by file extension so a new static asset doesn't
// silently get auth-gated too), and the webhook ingestion endpoint — that
// one is called by Radarr/Sonarr directly (no session cookie) and
// authenticates itself via a per-user secret in the URL instead. /api/v1 is
// the native-app JSON API: its routes authenticate bearer tokens themselves
// and must answer 401 JSON, never a redirect to the HTML login page.
// /api-docs is the public, read-only table of those endpoints (the same as
// the public /api/v1/openapi.json).
//
// Each exclusion is anchored (a following "/" or the end of the path): a
// bare prefix like "login" would also let "/login-history" through, and the
// file-extension rule is limited to a single path segment so
// "/title/tv/1399.js" can't slip past the gate and reach that page's server
// actions.
const PUBLIC_PATH =
/^\/(?:api\/auth(?:\/|$)|api\/webhooks(?:\/|$)|api\/v1(?:\/|$)|api-docs(?:\/|$)|login(?:\/|$)|setup(?:\/|$)|_next\/static(?:\/|$)|_next\/image(?:\/|$)|favicon\.ico$|icon$|apple-icon$|[^/]+\.(?:png|jpg|jpeg|gif|webp|svg|ico|css|js|txt|xml|json|webmanifest|woff|woff2)$)/;
/** Whether this path needs a signed-in session (exported for its tests). */
export function needsSignIn(pathname: string): boolean {
// The homepage stays public so visitors can see what Marquee is before
// creating an account — everything else requires signing in.
return pathname !== "/" && !PUBLIC_PATH.test(pathname);
}
export default auth((req) => {
if (needsSignIn(req.nextUrl.pathname) && !req.auth) {
return NextResponse.redirect(new URL("/login", req.nextUrl));
}
// Every page gets the Content-Security-Policy (lib/security/csp.ts) with
// a nonce of its own. Next.js reads it off the request to put the nonce on
// its scripts; the root layout reads x-nonce for the theme-init script.
// Every page is rendered per request already (the root layout reads
// cookies), which nonces need.
const nonce = createNonce();
const policy = contentSecurityPolicy(nonce);
const requestHeaders = new Headers(req.headers);
requestHeaders.set("x-nonce", nonce);
requestHeaders.set("Content-Security-Policy", policy);
const response = NextResponse.next({ request: { headers: requestHeaders } });
response.headers.set("Content-Security-Policy", policy);
return response;
});
export const config = {
matcher: [
// Everything but what neither needs a session nor is a page: Next.js's
// static files and image optimizer, the generated icons, files under
// public/, and the API routes that authenticate themselves (sign-in,
// webhooks, /api/v1). The pages among the public paths above (login,
// setup, /api-docs, the homepage) run through here for their
// Content-Security-Policy, and needsSignIn lets them through.
"/((?!api/auth(?:/|$)|api/webhooks(?:/|$)|api/v1(?:/|$)|_next/static(?:/|$)|_next/image(?:/|$)|favicon\\.ico$|icon$|apple-icon$|[^/]+\\.(?:png|jpg|jpeg|gif|webp|svg|ico|css|js|txt|xml|json|webmanifest|woff|woff2)$).*)",
],
};