-
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathproxy.ts
More file actions
110 lines (90 loc) · 3.06 KB
/
Copy pathproxy.ts
File metadata and controls
110 lines (90 loc) · 3.06 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
import { NextResponse, type NextRequest } from "next/server";
const sessionCookieName = "prismatica_session";
const unsafeMethods = new Set(["DELETE", "PATCH", "POST", "PUT"]);
export function proxy(request: NextRequest) {
const pathname = request.nextUrl.pathname;
if (pathname.startsWith("/api/") && unsafeMethods.has(request.method.toUpperCase())) {
const response = enforceSameOriginMutation(request);
if (response) {
return response;
}
}
if (pathname === "/projects/new" || !pathname.startsWith("/projects/")) {
return NextResponse.next();
}
if (request.cookies.has(sessionCookieName)) {
return NextResponse.next();
}
const signInUrl = request.nextUrl.clone();
signInUrl.pathname = "/sign-in";
signInUrl.search = "";
signInUrl.searchParams.set("redirect", `${request.nextUrl.pathname}${request.nextUrl.search}`);
return NextResponse.redirect(signInUrl);
}
function enforceSameOriginMutation(request: NextRequest) {
const sourceOrigin = getSourceOrigin(request);
if (!sourceOrigin || !getAllowedOrigins(request).has(sourceOrigin)) {
return NextResponse.json(
{ error: "Cross-site request blocked." },
{
status: 403,
headers: {
"X-Content-Type-Options": "nosniff"
}
}
);
}
return null;
}
function getSourceOrigin(request: NextRequest) {
const origin = normalizeOrigin(request.headers.get("origin") ?? "");
if (origin) {
return origin;
}
return normalizeOrigin(request.headers.get("referer") ?? "");
}
function getAllowedOrigins(request: NextRequest) {
const origins = new Set<string>();
addOrigin(origins, request.nextUrl.origin);
addOriginFromParts(origins, request.nextUrl.protocol.replace(/:$/, ""), request.headers.get("host") ?? "");
const forwardedProto = firstForwardedHeaderValue(request.headers.get("x-forwarded-proto")) || request.nextUrl.protocol.replace(/:$/, "");
const forwardedHost = firstForwardedHeaderValue(request.headers.get("x-forwarded-host"));
if (forwardedHost) {
addOriginFromParts(origins, forwardedProto, forwardedHost);
}
for (const configuredOrigin of (process.env.PRISMATICA_ALLOWED_ORIGINS ?? "").split(",")) {
addOrigin(origins, configuredOrigin);
}
return origins;
}
function addOrigin(origins: Set<string>, value: string) {
const origin = normalizeOrigin(value);
if (origin) {
origins.add(origin);
}
}
function addOriginFromParts(origins: Set<string>, protocol: string, host: string) {
const normalizedProtocol = protocol.trim().replace(/:$/, "") || "https";
const normalizedHost = firstForwardedHeaderValue(host);
if (!normalizedHost) {
return;
}
addOrigin(origins, `${normalizedProtocol}://${normalizedHost}`);
}
function normalizeOrigin(value: string) {
const trimmed = value.trim();
if (!trimmed) {
return "";
}
try {
return new URL(trimmed).origin.toLowerCase();
} catch {
return "";
}
}
function firstForwardedHeaderValue(value: string | null) {
return value?.split(",")[0]?.trim() ?? "";
}
export const config = {
matcher: ["/api/:path*", "/projects/:path*"]
};