78 lines
2.6 KiB
TypeScript
78 lines
2.6 KiB
TypeScript
import { NextRequest, NextResponse } from "next/server"
|
|
import { verifySession } from "@/lib/auth"
|
|
|
|
// Rutas públicas EXACTAS (coincidencia por segmento, no por prefijo).
|
|
const publicExact = new Set([
|
|
"/login",
|
|
"/api/auth/login",
|
|
"/api/auth/logout",
|
|
"/api/health",
|
|
"/api/proxy/health",
|
|
"/setup",
|
|
])
|
|
|
|
// Prefijos públicos: /api/cover (imágenes de portada para el sitio público),
|
|
// assets de Next y fuentes. Coinciden a nivel de segmento: "/api/cover" NO
|
|
// destapa "/api/cover/evil" ni otras rutas.
|
|
const publicPrefixes = [
|
|
"/api/cover/",
|
|
"/_next/",
|
|
"/fonts/",
|
|
]
|
|
|
|
// Páginas públicas del sitio (server-side, sin login).
|
|
// "/" es MATCH EXACTO (startsWith("/") matchearía TODO).
|
|
// "/gallery/" incluye el lector: cualquiera puede LEER (GET); las acciones
|
|
// del panel (eliminar, etc.) quedan protegidas por el middleware vía método.
|
|
const publicPages = ["/p/", "/tag/", "/gallery/", "/buscar"]
|
|
|
|
// GET de lectura del proxy hacia el pipeline: solo las rutas que el LECTOR
|
|
// público necesita (páginas, summary, artifacts, cover). /status y /queue
|
|
// exponen info operativa interna -> requieren sesión.
|
|
function isPublicProxyRead(pathname: string, method: string): boolean {
|
|
if (method !== "GET" && method !== "HEAD") return false
|
|
return pathname.startsWith("/api/proxy/galleries/")
|
|
}
|
|
|
|
function isPublicPath(pathname: string, method: string): boolean {
|
|
if (pathname === "/") return true
|
|
if (publicExact.has(pathname)) return true
|
|
if (publicPrefixes.some((p) => pathname.startsWith(p))) return true
|
|
if (publicPages.some((p) => pathname.startsWith(p))) return true
|
|
if (isPublicProxyRead(pathname, method)) return true
|
|
|
|
// /api/posts: GET de listado público; mutaciones requieren sesión.
|
|
if (pathname === "/api/posts" && method === "GET") return true
|
|
|
|
// /api/setup: GET (chequeo de config) público; POST (cambiar secrets) NO.
|
|
if (pathname === "/api/setup" && method === "GET") return true
|
|
|
|
return false
|
|
}
|
|
|
|
export async function middleware(req: NextRequest) {
|
|
const { pathname } = req.nextUrl
|
|
const method = req.method
|
|
|
|
if (isPublicPath(pathname, method)) return NextResponse.next()
|
|
|
|
const webPassword = process.env.WEB_PASSWORD
|
|
if (!webPassword) return NextResponse.next()
|
|
|
|
const session = req.cookies.get("session")?.value
|
|
if (!session) {
|
|
return NextResponse.redirect(new URL("/login", req.url))
|
|
}
|
|
|
|
const payload = await verifySession(session)
|
|
if (!payload || !payload.authenticated) {
|
|
return NextResponse.redirect(new URL("/login", req.url))
|
|
}
|
|
|
|
return NextResponse.next()
|
|
}
|
|
|
|
export const config = {
|
|
matcher: ["/((?!_next/static|_next/image|favicon.ico).*)"],
|
|
}
|