Repository navigation
Expand file tree
/
Copy pathproxy.ts
More file actions
56 lines (53 loc) · 2.84 KB
/
Copy pathproxy.ts
File metadata and controls
56 lines (53 loc) · 2.84 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
import { NextResponse, type NextRequest } from "next/server";
import { isApiRequestOriginAllowed, shouldCheckApiRequestOrigin } from "@/lib/request-security";
import {
isTrustedProxyRequest,
isValidWebSession,
isWebPasswordEnabled,
webPasswordConfigurationProblem,
OMP_WEB_SESSION_COOKIE,
OMP_WEB_TRUSTED_HEADER,
} from "@/lib/web-auth";
export function proxy(request: NextRequest) {
if (request.nextUrl.pathname.startsWith("/api/") && shouldCheckApiRequestOrigin(request) && !isApiRequestOriginAllowed(request)) {
return NextResponse.json({ error: "Cross-origin API requests are not allowed" }, { status: 403 });
}
// A configured-but-unusable password (plaintext, or a truncated hash) must
// never degrade into an open server: refuse every request with the fix. The
// problem is also reported once at startup (instrumentation.node.ts), so this
// deliberately logs nothing — a doomed request loop must not flood the log.
const passwordProblem = webPasswordConfigurationProblem();
if (passwordProblem) {
return new NextResponse(`omp-web password configuration error\n\n${passwordProblem}\n`, {
status: 503,
headers: { "content-type": "text/plain; charset=utf-8", "cache-control": "no-store" },
});
}
if (!isWebPasswordEnabled()) {
return request.nextUrl.pathname === "/login"
? NextResponse.redirect(new URL("/", request.url))
: NextResponse.next();
}
const { pathname } = request.nextUrl;
const hasSession = isTrustedProxyRequest(request.headers.get(OMP_WEB_TRUSTED_HEADER))
|| isValidWebSession(request.cookies.get(OMP_WEB_SESSION_COOKIE)?.value);
if (pathname === "/login") {
return hasSession ? NextResponse.redirect(new URL("/", request.url)) : NextResponse.next();
}
if (pathname === "/api/web-auth/session") return NextResponse.next();
if (hasSession) return NextResponse.next();
if (pathname.startsWith("/api/")) {
return NextResponse.json({ error: "Password required", code: "password_required" }, { status: 401 });
}
const login = new URL("/login", request.url);
// Keep a deep link to a session (e.g. a notification click) across sign-in.
if (pathname === "/" && request.nextUrl.search) login.searchParams.set("next", `/${request.nextUrl.search}`);
return NextResponse.redirect(login);
}
// The sign-in screen still needs its Next.js JavaScript and CSS before a
// session exists; these and the ordinary browser icons are public assets,
// not workspace data. /api/manifest stays authenticated: its link includes
// credentials and its embedded installation icons need no separate request.
// The service worker script holds no data either, and the browser re-fetches
// it for update checks even after the sign-in cookie expired.
export const config = { matcher: "/((?!_next/static|_next/image|favicon\\.ico|icon\\.svg|icon\\.png|icon-192\\.png|badge-96\\.png|sw\\.js).*)" };