mirror of
https://github.com/gethomepage/homepage.git
synced 2026-09-12 23:25:48 -07:00
62 lines
2.5 KiB
JavaScript
62 lines
2.5 KiB
JavaScript
import { getToken } from "next-auth/jwt";
|
|
import { NextResponse } from "next/server";
|
|
|
|
import { isAuthEnabled } from "utils/env";
|
|
|
|
const authEnabled = isAuthEnabled();
|
|
const authSecret = process.env.NEXTAUTH_SECRET || process.env.HOMEPAGE_AUTH_SECRET;
|
|
|
|
// Prerendered pages carry `s-maxage`, and the dashboard HTML embeds the service and
|
|
// bookmark inventory. Without this, a CDN or caching reverse proxy in front of Homepage
|
|
// would store an authenticated response and serve it to anonymous visitors.
|
|
function withPrivateCache(res) {
|
|
if (authEnabled) {
|
|
res.headers.set("Cache-Control", "private, no-store");
|
|
}
|
|
return res;
|
|
}
|
|
|
|
export async function middleware(req) {
|
|
// Check the Host header, if HOMEPAGE_ALLOWED_HOSTS is set
|
|
const host = req.headers.get("host");
|
|
const port = process.env.PORT || 3000;
|
|
let allowedHosts = [`localhost:${port}`, `127.0.0.1:${port}`, `[::1]:${port}`];
|
|
const allowAll = process.env.HOMEPAGE_ALLOWED_HOSTS === "*";
|
|
if (process.env.HOMEPAGE_ALLOWED_HOSTS) {
|
|
allowedHosts = allowedHosts.concat(process.env.HOMEPAGE_ALLOWED_HOSTS.split(","));
|
|
}
|
|
if (!allowAll && (!host || !allowedHosts.includes(host))) {
|
|
console.error(
|
|
`Host validation failed for: ${host}. Hint: Set the HOMEPAGE_ALLOWED_HOSTS environment variable to allow requests from this host / port.`,
|
|
);
|
|
return NextResponse.json({ error: "Host validation failed. See logs for more details." }, { status: 400 });
|
|
}
|
|
|
|
const { pathname, search } = new URL(req.url);
|
|
const isPublicAuthPath = pathname.startsWith("/api/healthcheck") || pathname === "/api/config/custom.css";
|
|
if (authEnabled && !isPublicAuthPath) {
|
|
// The MCP API handler authorizes both bearer tokens and Homepage sessions.
|
|
if (pathname === "/api/mcp") {
|
|
return withPrivateCache(NextResponse.next());
|
|
}
|
|
|
|
const token = await getToken({ req, secret: authSecret });
|
|
if (!token) {
|
|
const signInUrl = new URL("/auth/signin", req.url);
|
|
// Same-origin by construction, so this cannot be used as an open redirect
|
|
signInUrl.searchParams.set("callbackUrl", `${pathname}${search}`);
|
|
return withPrivateCache(NextResponse.redirect(signInUrl));
|
|
}
|
|
}
|
|
|
|
return withPrivateCache(NextResponse.next());
|
|
}
|
|
|
|
export const config = {
|
|
// Protect all app and API routes; allow Next.js internals, public assets, auth pages, and NextAuth endpoints.
|
|
matcher: [
|
|
"/",
|
|
"/((?!_next/static|_next/image|favicon.ico|robots.txt|manifest.json|sitemap.xml|icons/|api/auth|auth/).*)",
|
|
],
|
|
};
|