-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathmiddleware.ts
More file actions
144 lines (126 loc) · 4.5 KB
/
Copy pathmiddleware.ts
File metadata and controls
144 lines (126 loc) · 4.5 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
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
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
import {
applyHostPrefix,
resolveHeadProbePath,
resolveHostPrefix,
} from "@/lib/runtime/host-routing";
import { resolveRequestHostname } from "@/lib/runtime/request-host";
import {
buildSecurityHeaders,
resolveAdditionalConnectSrc,
} from "@/lib/runtime/security-headers";
// Regex to detect public files that should skip rewriting.
// This is safer than checking for dots, which might exist in valid URL slugs.
const PUBLIC_FILE =
/\.(?:png|jpg|jpeg|gif|svg|ico|webp|css|js|map|txt|xml|webmanifest|woff|woff2|ttf|eot)$/i;
const IS_PRODUCTION = process.env.NODE_ENV === "production";
const IS_DEVELOPMENT = !IS_PRODUCTION;
const STRICT_CSP_PATHS = ["/styfi", "/veyfi", "/yeth", "/ybc"];
const ADDITIONAL_CONNECT_SRC = resolveAdditionalConnectSrc();
function isStrictCspPath(pathname: string, hostPrefix: string | null) {
if (hostPrefix) return true;
return STRICT_CSP_PATHS.some((prefix) => pathname === prefix || pathname.startsWith(`${prefix}/`));
}
function isGovernanceAppRequest(pathname: string, hostPrefix: string | null) {
if (hostPrefix) return true;
return STRICT_CSP_PATHS.some((prefix) => pathname === prefix || pathname.startsWith(`${prefix}/`));
}
function withSecurityHeaders(
response: NextResponse,
nonce: string,
options: {
allowUnsafeInlineScripts: boolean;
allowSafeFrameEmbedding: boolean;
}
) {
const securityHeaders = buildSecurityHeaders({
nonce,
isDevelopment: IS_DEVELOPMENT,
isProduction: IS_PRODUCTION,
allowUnsafeInlineScripts: options.allowUnsafeInlineScripts,
allowSafeFrameEmbedding: options.allowSafeFrameEmbedding,
additionalConnectSrc: ADDITIONAL_CONNECT_SRC,
});
for (const [key, value] of Object.entries(securityHeaders)) {
response.headers.set(key, value);
}
if (options.allowSafeFrameEmbedding) {
response.headers.delete("X-Frame-Options");
} else {
response.headers.set("X-Frame-Options", "DENY");
}
response.headers.set("x-nonce", nonce);
return response;
}
export function middleware(request: NextRequest) {
const url = request.nextUrl;
const requestHostname = resolveRequestHostname(request.headers, url.hostname);
const nonce = crypto.randomUUID().replace(/-/g, "");
const requestHeaders = new Headers(request.headers);
requestHeaders.set("x-nonce", nonce);
// 1. Determine the intended application based on the Host header
const prefix = resolveHostPrefix(requestHostname);
const shouldUseStrictCsp = isStrictCspPath(url.pathname, prefix);
const allowSafeFrameEmbedding = isGovernanceAppRequest(url.pathname, prefix);
const securityOptions = {
allowUnsafeInlineScripts: !shouldUseStrictCsp,
allowSafeFrameEmbedding,
};
const isHeadRequest = request.method === "HEAD";
// If the hostname isn't in our map (e.g. app.dao-ops.com or localhost),
// pass through to the default root page (the Launcher).
// 2. Safety Checks: Skip rewriting for internals and static assets
const isSkippablePath =
url.pathname.startsWith("/_next") ||
url.pathname.startsWith("/api") ||
url.pathname.startsWith("/fonts") ||
url.pathname.startsWith("/.well-known") ||
url.pathname === "/manifest.json" ||
PUBLIC_FILE.test(url.pathname);
if (isHeadRequest && !isSkippablePath) {
const headUrl = url.clone();
const probePath = resolveHeadProbePath(headUrl.pathname, prefix);
headUrl.pathname = applyHostPrefix(probePath, prefix);
return withSecurityHeaders(
NextResponse.rewrite(headUrl, {
request: { headers: requestHeaders },
}),
nonce,
securityOptions
);
}
if (!prefix) {
return withSecurityHeaders(
NextResponse.next({
request: { headers: requestHeaders },
}),
nonce,
securityOptions
);
}
if (isSkippablePath) {
return withSecurityHeaders(
NextResponse.next({
request: { headers: requestHeaders },
}),
nonce,
securityOptions
);
}
// 3. Double-Prefix Guard
// If the user visits styfi.yearn.fi/styfi/dashboard, we don't want /styfi/styfi/dashboard.
// We only apply the prefix if it's not already there.
url.pathname = applyHostPrefix(url.pathname, prefix);
return withSecurityHeaders(
NextResponse.rewrite(url, {
request: { headers: requestHeaders },
}),
nonce,
securityOptions
);
}
export const config = {
// Match all paths except explicit Next.js internals to be efficient
matcher: ["/((?!_next/static|_next/image|favicon.ico).*)"],
};