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
70 lines
2.7 KiB
TypeScript
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).*)"],
|
|
};
|