Files
wehub-resource-sync e4dcfc49aa
Tests / Import Check (Python 3.13) (push) Has been cancelled
Tests / Import Check (Python 3.14) (push) Has been cancelled
Tests / Python Tests (Python 3.11) (push) Has been cancelled
Tests / Python Tests (Python 3.12) (push) Has been cancelled
Tests / Python Tests (Python 3.14) (push) Has been cancelled
Tests / Test Summary (push) Has been cancelled
Tests / Lint and Format (push) Has been cancelled
Tests / Web Node Tests (push) Has been cancelled
Tests / Import Check (Python 3.11) (push) Has been cancelled
Tests / Import Check (Python 3.12) (push) Has been cancelled
Tests / Python Tests (Python 3.13) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:00:43 +08:00

69 lines
2.7 KiB
TypeScript

// Pure request-routing policy for the Next.js middleware (web/proxy.ts).
//
// This module deliberately carries NO dependency on `next/server`: it answers
// "what should happen to this request?" as plain, side-effect-free functions,
// while proxy.ts stays a thin adapter that turns those answers into
// NextResponse objects. Keeping the policy pure means the routing/auth rules
// can be unit-tested in the node harness without booting the Next runtime.
export const LOGIN_PATH = "/login";
export const COOKIE_NAME = "dt_token";
// Paths whose responses come from the backend, not the Next app. The middleware
// rewrites these to DEEPTUTOR_API_BASE_URL so the browser can use frontend-
// relative URLs (e.g. `:3782/api/v1/...` or `.../ws`) and let the rewrite
// bridge the origin gap.
export function isBackendPath(pathname: string): boolean {
return pathname.startsWith("/api/") || pathname.startsWith("/ws/");
}
// Static assets served straight out of `web/public` (logos, favicons, fonts,
// provider icons, …). These must bypass the auth gate even in multi-user mode:
// the Next image optimizer re-fetches a referenced public image over a
// server-side loopback request that carries NO auth cookie, so gating the path
// bounces that fetch to /login and the `<Image>` renders as a broken icon
// (issue #599 — broken logo/banner after login). Public assets are
// non-sensitive by design, so allowing them through is safe.
const STATIC_ASSET =
/\.(?:png|jpe?g|gif|svg|ico|webp|avif|woff2?|ttf|otf|txt|json|map|css|js)$/i;
// Paths the auth gate must never block: the auth pages themselves, Next.js
// internals, and public static assets (see STATIC_ASSET above).
export function isAuthExempt(pathname: string): boolean {
return (
pathname.startsWith(LOGIN_PATH) ||
pathname.startsWith("/register") ||
pathname.startsWith("/_next") ||
pathname.startsWith("/favicon") ||
STATIC_ASSET.test(pathname)
);
}
export type TokenState = "missing" | "malformed" | "expired" | "valid";
// Classify the auth cookie WITHOUT trusting its signature — the middleware is a
// cheap front-line gate, not the authority (the backend does real verification
// on every API call). `nowMs` is injected rather than read from the clock so
// the classifier stays pure and testable.
export function classifyToken(
token: string | undefined,
nowMs: number,
): TokenState {
if (!token) return "missing";
// Expect a JWT: header.payload.signature
const parts = token.split(".");
if (parts.length !== 3) return "malformed";
try {
const payload = JSON.parse(
Buffer.from(parts[1], "base64url").toString("utf-8"),
);
if (payload.exp && nowMs >= payload.exp * 1000) return "expired";
} catch {
return "malformed";
}
return "valid";
}