diff --git a/.env.production b/.env.production index bddee24..6d207b4 100644 --- a/.env.production +++ b/.env.production @@ -2,7 +2,7 @@ NEXT_PUBLIC_API_BASE_URL=http://battlbuilder-api:8080 # Middleware to limited site to only root page -NEXT_PUBLIC_LAUNCH_ONLY_ROOT=true +LAUNCH_ONLY_ROOT=true NEXT_PUBLIC_SHORTLINK_BASE_URL=https://battl.build diff --git a/middleware.ts b/middleware.ts index 4b371e2..5a185d7 100644 --- a/middleware.ts +++ b/middleware.ts @@ -2,42 +2,83 @@ 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.startsWith("/auth")) 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; + + // Health / misc APIs you may want public + if (pathname === "/api/health") return true; + + return false; +} + export function middleware(req: NextRequest) { const { pathname } = req.nextUrl; const token = req.cookies.get("session_token")?.value; + const isLoggedIn = Boolean(token); - // Protect admin API routes + // 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"; + + // If we're in "launch only" mode, anonymous users can ONLY see public paths. + // Everything else (including /builder) gets bounced. + if (launchOnlyRoot && !isLoggedIn && !isPublicPath(pathname)) { + // For API requests, return 401 instead of redirecting + if (pathname.startsWith("/api/")) { + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + } + + // For pages, redirect to home (keeps the landing page experience) + const url = req.nextUrl.clone(); + url.pathname = "/"; + return NextResponse.redirect(url); + } + + // --- Protected API routes --- if (pathname.startsWith("/api/admin")) { - if (!token) { + if (!isLoggedIn) { return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); } } - // Protect builds API routes if (pathname.startsWith("/api/builds")) { - if (!token) { + if (!isLoggedIn) { return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); } } - // Protect admin pages + // --- Protected pages --- if (pathname.startsWith("/admin")) { - if (!token) { - const url = req.nextUrl.clone(); - url.pathname = "/login"; - url.searchParams.set("next", pathname); - return NextResponse.redirect(url); - } + if (!isLoggedIn) return redirectToLogin(req); + } + + // Protect the builder UI + if (pathname === "/builder" || pathname.startsWith("/builder/")) { + if (!isLoggedIn) return redirectToLogin(req); } return NextResponse.next(); } export const config = { + // Run middleware for all application routes, but avoid static assets. matcher: [ - "/api/admin/:path*", - "/api/builds", - "/api/builds/:path+", - "/admin/:path*", + "/((?!_next/static|_next/image|favicon.ico|robots.txt|sitemap.xml).*)", ], }; \ No newline at end of file