-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathproxy.ts
More file actions
59 lines (52 loc) · 2.32 KB
/
Copy pathproxy.ts
File metadata and controls
59 lines (52 loc) · 2.32 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
import { NextRequest, NextResponse } from "next/server";
import { AUTH_COOKIE } from "@/lib/auth-cookie";
// Public-exposure auth gate (Next's "proxy" convention — formerly "middleware").
// trellis can spawn the claude/codex CLI with full permission (workspace/
// project/enhanced modes) — i.e. arbitrary code execution on the host. When
// served over the public tunnel (trellis.smokingmouse.cc) that endpoint MUST
// sit behind auth.
//
// Cookie session (not Basic Auth): /login posts the password to /api/login,
// which sets an httpOnly cookie = TRELLIS_AUTH_TOKEN on success. Here we just
// compare the cookie to the token. Page navigations without a valid cookie are
// redirected to the themed /login; API/asset requests get a plain 401.
//
// When TRELLIS_AUTH_PASS is unset the gate is OFF — local dev / Tailscale-
// private use stays frictionless; only the password-configured public deploy
// is protected.
const PASS = process.env.TRELLIS_AUTH_PASS;
const TOKEN = process.env.TRELLIS_AUTH_TOKEN;
// Always reachable without a session: the login page itself, the login API,
// and public/static assets the login page (and PWA shell) need.
function isPublicPath(pathname: string): boolean {
return (
pathname === "/login" ||
pathname === "/api/login" ||
pathname.startsWith("/_next/") ||
pathname === "/favicon.ico" ||
pathname === "/icon.svg" ||
pathname === "/apple-icon" ||
pathname === "/manifest.json"
);
}
export function proxy(req: NextRequest) {
if (!PASS || !TOKEN) return NextResponse.next(); // gate disabled
const { pathname } = req.nextUrl;
if (isPublicPath(pathname)) return NextResponse.next();
if (req.cookies.get(AUTH_COOKIE)?.value === TOKEN) {
return NextResponse.next();
}
// Page navigation → send to the themed login page (remember where to return).
const accept = req.headers.get("accept") || "";
if (req.method === "GET" && accept.includes("text/html")) {
const url = req.nextUrl.clone();
url.pathname = "/login";
url.search = pathname === "/" ? "" : `?from=${encodeURIComponent(pathname)}`;
return NextResponse.redirect(url);
}
// API / fetch / asset → opaque 401 (the SPA shell is already gated above).
return new NextResponse("Unauthorized", { status: 401 });
}
export const config = {
matcher: ["/((?!_next/static|_next/image).*)"],
};