-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathproxy.ts
More file actions
80 lines (74 loc) · 3.47 KB
/
Copy pathproxy.ts
File metadata and controls
80 lines (74 loc) · 3.47 KB
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
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
// Per-request Content-Security-Policy with a nonce.
//
// script-src uses a fresh 'nonce-<value>' + 'strict-dynamic' so we can drop
// 'unsafe-inline' entirely from scripts (the directive securityheaders.com
// flags as dangerous). Next.js reads the nonce from the request's CSP header
// during SSR and stamps it onto its framework/runtime/inline scripts; those
// scripts then transitively trust the chunks they load via 'strict-dynamic'.
//
// style-src keeps 'unsafe-inline' on purpose: React renders `style={{}}` as
// inline style attributes and a nonce cannot authorize style attributes, so
// there is no nonce-based path for them. (style-src 'unsafe-inline' is not the
// directive flagged as dangerous.)
//
// Because the nonce is generated per request, pages that render HTML must be
// dynamically rendered (see `export const dynamic = "force-dynamic"` on the
// otherwise-static routes).
export function proxy(request: NextRequest) {
const nonce = Buffer.from(crypto.randomUUID()).toString("base64");
const isDev = process.env.NODE_ENV === "development";
// Allow the Supabase Storage origin (receipt images) to load inline. Derived
// from the configured URL so it tracks the project without hardcoding a host.
let supabaseOrigin = "";
try {
if (process.env.NEXT_PUBLIC_SUPABASE_URL) {
supabaseOrigin = new URL(process.env.NEXT_PUBLIC_SUPABASE_URL).origin;
}
} catch {
supabaseOrigin = "";
}
const csp = [
"default-src 'self'",
"base-uri 'self'",
"object-src 'none'",
"frame-ancestors 'none'",
// auth.privy.io is where Privy's embedded wallet lives — the key is held in a
// cross-origin iframe this page cannot read, which is the point of it. Without
// this the frame is blocked and every Privy sign-in and confirmation hangs
// with nothing on screen to say why. Its CAPTCHA is the Cloudflare frame that
// was already allowed.
"frame-src https://challenges.cloudflare.com https://auth.privy.io",
"form-action 'self'",
`img-src 'self' data: blob: https://pbs.twimg.com https://abs.twimg.com https://unavatar.io https://cdn.discordapp.com https://lh3.googleusercontent.com https://auth.privy.io${supabaseOrigin ? ` ${supabaseOrigin}` : ""}`,
"font-src 'self' data:",
"style-src 'self' 'unsafe-inline'",
`script-src 'self' 'nonce-${nonce}' 'strict-dynamic'${isDev ? " 'unsafe-eval'" : ""}`,
"connect-src 'self' https: wss:",
"worker-src 'self' blob:",
"upgrade-insecure-requests",
].join("; ");
const requestHeaders = new Headers(request.headers);
requestHeaders.set("x-nonce", nonce);
requestHeaders.set("Content-Security-Policy", csp);
const response = NextResponse.next({ request: { headers: requestHeaders } });
response.headers.set("Content-Security-Policy", csp);
return response;
}
export const config = {
matcher: [
// Run on document requests only. Skip API routes, Next internals, the
// favicon, and the static trust files — they don't need a per-request CSP
// and shouldn't be pushed into dynamic rendering. Also skip next/link
// prefetches so prefetching stays cacheable.
{
source:
"/((?!api|_next/static|_next/image|favicon.ico|robots.txt|sitemap.xml|site.webmanifest|security.txt|.well-known).*)",
missing: [
{ type: "header", key: "next-router-prefetch" },
{ type: "header", key: "purpose", value: "prefetch" },
],
},
],
};