Files
wehub-resource-sync e4dcfc49aa
Tests / Lint and Format (push) Waiting to run
Tests / Web Node Tests (push) Waiting to run
Tests / Import Check (Python 3.11) (push) Waiting to run
Tests / Import Check (Python 3.12) (push) Waiting to run
Tests / Import Check (Python 3.13) (push) Waiting to run
Tests / Import Check (Python 3.14) (push) Waiting to run
Tests / Python Tests (Python 3.11) (push) Blocked by required conditions
Tests / Python Tests (Python 3.12) (push) Blocked by required conditions
Tests / Python Tests (Python 3.13) (push) Blocked by required conditions
Tests / Python Tests (Python 3.14) (push) Blocked by required conditions
Tests / Test Summary (push) Blocked by required conditions
chore: import upstream snapshot with attribution
2026-07-13 13:00:43 +08:00

70 lines
2.7 KiB
TypeScript

import { NextRequest, NextResponse } from "next/server";
import { parseAuthEnabled } from "./lib/api";
import {
COOKIE_NAME,
LOGIN_PATH,
classifyToken,
isAuthExempt,
isBackendPath,
} from "./lib/proxy-policy";
// Backend base URL for `/api/*` and `/ws/*` rewrites. The container entrypoint
// exports `DEEPTUTOR_API_BASE_URL` from `data/user/settings/system.json`
// (preferring `next_public_api_base`, then `next_public_api_base_external`,
// then `http://localhost:${BACKEND_PORT}`). In dev (`deeptutor start`) it
// defaults to `http://localhost:8001`.
const API_BASE_URL =
process.env.DEEPTUTOR_API_BASE_URL ?? "http://localhost:8001";
const AUTH_ENABLED = parseAuthEnabled(process.env.DEEPTUTOR_AUTH_ENABLED);
// Redirect to the login page, preserving the intended destination in `next`.
// A present-but-invalid cookie is cleared so the browser stops resending it;
// when no cookie was sent there is nothing to clear.
function redirectToLogin(
req: NextRequest,
{ clearCookie }: { clearCookie: boolean },
): NextResponse {
const loginUrl = req.nextUrl.clone();
loginUrl.pathname = LOGIN_PATH;
loginUrl.searchParams.set("next", req.nextUrl.pathname);
const response = NextResponse.redirect(loginUrl);
if (clearCookie) response.cookies.delete(COOKIE_NAME);
return response;
}
export function proxy(req: NextRequest): NextResponse {
const { pathname, search } = req.nextUrl;
// 1. Bridge the origin gap: forward backend-relative paths to the API server.
// This keeps the URL knowledge in one place (the entrypoint + system.json)
// rather than baked into the frontend bundle.
if (isBackendPath(pathname)) {
return NextResponse.rewrite(new URL(pathname + search, API_BASE_URL));
}
// 2. Auth gate — multi-user mode only. Disabled by default, and never blocks
// auth pages, Next.js internals, or public static assets (see
// isAuthExempt: that exemption is what keeps the logo/banner images
// loading once login is enabled — issue #599).
if (!AUTH_ENABLED || isAuthExempt(pathname)) {
return NextResponse.next();
}
const token = req.cookies.get(COOKIE_NAME)?.value;
if (classifyToken(token, Date.now()) !== "valid") {
return redirectToLogin(req, { clearCookie: Boolean(token) });
}
return NextResponse.next();
}
export const config = {
// Run on every request except Next.js internals and the favicon. The /api/*
// and /ws/* paths are explicitly handled above (rewritten to the backend);
// the browser's /_next/image optimizer requests are excluded here, while the
// optimizer's loopback fetch for the source image (e.g. /logo.png) is let
// through the auth gate by isAuthExempt.
matcher: ["/((?!_next/static|_next/image|favicon.ico).*)"],
};