Spaces:
Sleeping
Sleeping
Download proxy.ts from Leon4gr45/nodepad-hypotheses: direct link, hf CLI and curl.
- Browser
- Download file 3.37 kB
-
https://huggingface.co/spaces/Leon4gr45/nodepad-hypotheses/resolve/main/proxy.ts
- Command line
-
hf download hf://spaces/Leon4gr45/nodepad-hypotheses/proxy.ts
-
curl -L -o proxy.ts https://huggingface.co/spaces/Leon4gr45/nodepad-hypotheses/resolve/main/proxy.ts
3.37 kB
| import { NextRequest, NextResponse } from "next/server" | |
| import { isAuthenticatedProxy } from "@/lib/auth/proxy" | |
| // ββ Sliding-window rate limiter βββββββββββββββββββββββββββββββββββββββββββββββ | |
| // Guards /api/fetch-url from being hammered as a public CORS proxy. | |
| // On serverless (Vercel) this is best-effort per-instance; on persistent | |
| // hosts (Render) it is reliable across requests. | |
| const store = new Map<string, number[]>() | |
| function isRateLimited(ip: string, path: string): boolean { | |
| const now = Date.now() | |
| const key = `${ip}:${path}` | |
| const hits = (store.get(key) ?? []).filter(t => now - t < 60_000) | |
| if (hits.length >= 30) return true // 30 URL fetches/min per IP | |
| hits.push(now) | |
| store.set(key, hits) | |
| return false | |
| } | |
| export function proxy(req: NextRequest) { | |
| const { pathname } = req.nextUrl | |
| const publicPaths = new Set(["/login", "/api/auth/login", "/api/auth/logout", "/api/auth/status", "/health", "/api-docs"]) | |
| if (publicPaths.has(pathname)) return NextResponse.next() | |
| if (pathname.startsWith("/api/")) { | |
| // API endpoints handle their own deep authentication via requireAuth. | |
| // The proxy only does a light pass to ensure unauthenticated requests | |
| // don't waste downstream resources, but relies on API route handlers | |
| // to correctly reject unauthorized access. | |
| if (!isAuthenticatedProxy(req)) { | |
| return NextResponse.json({ error: "Unauthorized" }, { status: 401, headers: { "Cache-Control": "no-store" } }) | |
| } | |
| } else if (!isAuthenticatedProxy(req)) { | |
| return NextResponse.redirect(new URL("/login", req.url)) | |
| } | |
| if (!pathname.startsWith("/api/fetch-url")) { | |
| return NextResponse.next({ headers: { "Cache-Control": "private, no-store" } }) | |
| } | |
| // Origin check β block requests from other origins. | |
| // Use strict URL parsing rather than substring matching to prevent | |
| // bypasses like https://evil.com?nodepad.space passing the check. | |
| const origin = req.headers.get("origin") ?? "" | |
| const referer = req.headers.get("referer") ?? "" | |
| const host = (req.headers.get("host") ?? "").split(":")[0] // strip port | |
| function strictHostMatch(headerValue: string): boolean { | |
| try { return new URL(headerValue).hostname === host } catch { return false } | |
| } | |
| const isLocalhost = strictHostMatch(origin || referer) | |
| ? ["localhost", "127.0.0.1"].includes(new URL(origin || referer).hostname) | |
| : (origin + referer).includes("localhost") | |
| const isSameOrigin = strictHostMatch(origin) || strictHostMatch(referer) | |
| if (origin && !isLocalhost && !isSameOrigin) { | |
| return new NextResponse(JSON.stringify({ error: "Forbidden" }), { | |
| status: 403, | |
| headers: { "Content-Type": "application/json" }, | |
| }) | |
| } | |
| const ip = req.headers.get("x-forwarded-for")?.split(",")[0].trim() | |
| ?? req.headers.get("x-real-ip") | |
| ?? "unknown" | |
| if (isRateLimited(ip, pathname)) { | |
| return new NextResponse(JSON.stringify({ error: "Rate limit exceeded." }), { | |
| status: 429, | |
| headers: { "Content-Type": "application/json", "Retry-After": "60" }, | |
| }) | |
| } | |
| return NextResponse.next({ headers: { "Cache-Control": "private, no-store" } }) | |
| } | |
| export const config = { | |
| matcher: ["/((?!_next/static|_next/image|icon.svg|favicon.ico|apple-icon.png).*)"], | |
| } | |