forked from mmckeen-nv/openshell_controller
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmiddleware.ts
More file actions
134 lines (112 loc) · 4.08 KB
/
middleware.ts
File metadata and controls
134 lines (112 loc) · 4.08 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
import { NextRequest, NextResponse } from "next/server"
import { getAuthSettings, verifySessionCookieValue } from "./app/lib/controlAuth"
const PUBLIC_PATHS = [
"/login",
"/api/auth/login",
"/api/auth/logout",
"/setup-account",
"/forgot-password",
"/api/auth/setup",
"/api/auth/recover",
"/favicon.ico",
"/favicon.svg",
"/favicon-32.png",
]
const BROKER_PATHS = [
"/api/mcp/broker",
]
function isPublicPath(pathname: string) {
return PUBLIC_PATHS.some((path) => pathname === path || pathname.startsWith(`${path}/`))
}
function isBrokerPath(pathname: string) {
return BROKER_PATHS.some((path) => pathname === path || pathname.startsWith(`${path}/`))
}
function isAssetPath(pathname: string) {
return pathname.startsWith("/_next/") || pathname.startsWith("/public/")
}
function withSecurityHeaders(response: NextResponse) {
response.headers.set("x-content-type-options", "nosniff")
response.headers.set("x-frame-options", "DENY")
response.headers.set("referrer-policy", "same-origin")
response.headers.set("permissions-policy", "camera=(), microphone=(), geolocation=()")
return response
}
function isStateChangingMethod(method: string) {
return !["GET", "HEAD", "OPTIONS"].includes(method.toUpperCase())
}
function firstForwardedValue(value: string | null) {
return value?.split(",")[0]?.trim() || null
}
function originFromHost(host: string | null, protocol: string) {
if (!host) return null
try {
const normalizedProtocol = protocol.endsWith(":") ? protocol : `${protocol}:`
return new URL(`${normalizedProtocol}//${host}`).origin
} catch {
return null
}
}
function publicBaseOrigin() {
if (!process.env.PUBLIC_BASE_URL) return null
try {
return new URL(process.env.PUBLIC_BASE_URL).origin
} catch {
return null
}
}
function trustedRequestOrigins(request: NextRequest) {
const forwardedProto = firstForwardedValue(request.headers.get("x-forwarded-proto"))
const forwardedHost = firstForwardedValue(request.headers.get("x-forwarded-host"))
const host = firstForwardedValue(request.headers.get("host"))
const protocol = forwardedProto || request.nextUrl.protocol || "http:"
return new Set([
request.nextUrl.origin,
originFromHost(host, protocol),
originFromHost(forwardedHost, protocol),
publicBaseOrigin(),
].filter((origin): origin is string => Boolean(origin)))
}
function hasTrustedOrigin(request: NextRequest) {
const origin = request.headers.get("origin")
if (!origin) return true
try {
return trustedRequestOrigins(request).has(new URL(origin).origin)
} catch {
return false
}
}
export async function middleware(request: NextRequest) {
const { pathname } = request.nextUrl
const settings = getAuthSettings()
if (isStateChangingMethod(request.method) && !hasTrustedOrigin(request)) {
return withSecurityHeaders(NextResponse.json({ ok: false, error: "Untrusted request origin" }, { status: 403 }))
}
if (settings.disabled || isAssetPath(pathname)) {
if (pathname === "/login") {
return withSecurityHeaders(NextResponse.redirect(new URL("/", request.url)))
}
return withSecurityHeaders(NextResponse.next())
}
if (isBrokerPath(pathname)) {
return withSecurityHeaders(NextResponse.next())
}
if (isPublicPath(pathname)) {
if (pathname === "/login" && await verifySessionCookieValue(request.cookies.get(settings.cookieName)?.value)) {
return withSecurityHeaders(NextResponse.redirect(new URL("/", request.url)))
}
return withSecurityHeaders(NextResponse.next())
}
const authenticated = await verifySessionCookieValue(request.cookies.get(settings.cookieName)?.value)
if (authenticated) {
return withSecurityHeaders(NextResponse.next())
}
if (pathname.startsWith("/api/")) {
return withSecurityHeaders(NextResponse.json({ ok: false, error: "Authentication required" }, { status: 401 }))
}
const loginUrl = new URL("/login", request.url)
loginUrl.searchParams.set("next", `${pathname}${request.nextUrl.search}`)
return withSecurityHeaders(NextResponse.redirect(loginUrl))
}
export const config = {
matcher: ["/((?!_next/static|_next/image).*)"],
}