Files

102 lines
3.1 KiB
TypeScript

// middleware.ts
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
function redirectToLogin(req: NextRequest) {
const url = req.nextUrl.clone();
url.pathname = "/login";
url.searchParams.set("next", req.nextUrl.pathname);
return NextResponse.redirect(url);
}
function isPublicPath(pathname: string) {
// Public pages
if (pathname === "/") return true;
if (pathname === "/login") return true;
if (pathname === "/register") return true;
if (pathname === "/tos") return true;
if (pathname === "/privacy") return true;
if (pathname === "/beta") return true;
if (pathname.startsWith("/beta/")) return true;
if (pathname.startsWith("/auth")) return true;
if (pathname.startsWith("/api/auth")) return true;
// Content / guides — always public, no auth required
if (pathname === "/guides") return true;
if (pathname.startsWith("/guides/")) return true;
// Changelog and feedback — always public
if (pathname === "/changelog") return true;
if (pathname === "/feedback") return true;
// Static / framework assets
if (pathname.startsWith("/_next")) return true;
if (pathname === "/favicon.ico") return true;
if (pathname === "/robots.txt") return true;
if (pathname === "/sitemap.xml") return true;
// Static images in /public
if (pathname.startsWith("/battl/")) return true;
// Health / misc APIs you may want public
if (pathname === "/api/health") return true;
return false;
}
function isProtectedLaunchPath(pathname: string) {
return (
pathname === "/builder" ||
pathname.startsWith("/builder/") ||
pathname === "/builds" ||
pathname.startsWith("/builds/") ||
pathname === "/vault" ||
pathname.startsWith("/vault/")
);
}
export function middleware(req: NextRequest) {
const { pathname } = req.nextUrl;
// Short-circuit: always let public paths through without any token checks.
if (isPublicPath(pathname)) return NextResponse.next();
const token = req.cookies.get("session_token")?.value;
const isLoggedIn = Boolean(token);
// IMPORTANT: Use a server-only env var for middleware behavior.
// Set LAUNCH_ONLY_ROOT=true in your Docker compose.
const launchOnlyRoot = process.env.LAUNCH_ONLY_ROOT === "true";
// In "launch only" mode, anonymous users are blocked only from gated app areas.
if (launchOnlyRoot && !isLoggedIn && isProtectedLaunchPath(pathname)) {
return redirectToLogin(req);
}
// --- Protected API routes ---
if (pathname.startsWith("/api/admin")) {
if (!isLoggedIn) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
}
if (pathname.startsWith("/api/builds")) {
if (!isLoggedIn) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
}
// --- Protected pages ---
if (pathname.startsWith("/admin")) {
if (!isLoggedIn) return redirectToLogin(req);
}
return NextResponse.next();
}
export const config = {
// Run middleware for all application routes, but avoid static assets.
matcher: [
"/((?!_next/static|_next/image|favicon.ico|robots.txt|sitemap.xml).*)",
],
};