import createMiddleware from "next-intl/middleware"; import { NextResponse } from "next/server"; import type { NextRequest } from "next/server"; import { appLocales, createI18nRouting } from "./i18n/routing"; import { fromDevelopmentAdminPath, getAdminBaseUrl, getRequestHostname, isDevelopmentAdminPath, isAdminHost, hasDedicatedAdminHost, isInternalAdminPath, isLegacyAdminPath, toInternalAdminPath, } from "./lib/admin-routing"; import { FALLBACK_LOCALE, getLocalizedPathWithDefault, isSupportedLocale, stripLocalePrefix, } from "./lib/locale"; const ADMIN_SESSION_COOKIE = "moh_admin_session"; type SiteRuntimeState = { defaultLocale: (typeof appLocales)[number]; maintenanceEnabled: boolean; }; function getPathLocale(pathname: string, fallbackLocale: (typeof appLocales)[number]) { const locale = pathname.split("/")[1]; return isSupportedLocale(locale) ? locale : fallbackLocale; } function isComingSoonPath(pathname: string) { return stripLocalePrefix(pathname) === "/coming-soon"; } async function getSiteRuntimeState(request: NextRequest): Promise { try { const runtimeStateUrl = new URL("/api/site/default-locale", request.url); const response = await fetch(new URL("/api/site/default-locale", request.url), { headers: { "x-middleware-request": "1", }, cache: "no-store", }); if (!response.ok) { console.warn("middleware:getSiteRuntimeState:non-ok", { url: runtimeStateUrl.toString(), status: response.status, }); return { defaultLocale: FALLBACK_LOCALE, maintenanceEnabled: false, }; } const data = await response.json() as { defaultLocale?: string; maintenanceEnabled?: boolean; }; console.info("middleware:getSiteRuntimeState:resolved", { url: runtimeStateUrl.toString(), defaultLocale: data.defaultLocale, maintenanceEnabled: data.maintenanceEnabled, }); return { defaultLocale: isSupportedLocale(data.defaultLocale) ? data.defaultLocale : FALLBACK_LOCALE, maintenanceEnabled: data.maintenanceEnabled === true, }; } catch { console.error("middleware:getSiteRuntimeState:failed"); return { defaultLocale: FALLBACK_LOCALE, maintenanceEnabled: false, }; } } function getAdminBasicAuthUser(): string { return process.env.ADMIN_BASIC_AUTH_USER ?? ""; } function getAdminBasicAuthPass(): string { return process.env.ADMIN_BASIC_AUTH_PASS ?? ""; } function isAdminBasicAuthConfigured(): boolean { return Boolean(getAdminBasicAuthUser() && getAdminBasicAuthPass()); } function isAdminBasicAuthValid(request: NextRequest): boolean { if (!isAdminBasicAuthConfigured()) { return false; } const header = request.headers.get("authorization"); if (!header || !header.startsWith("Basic ")) { return false; } try { const decoded = atob(header.slice(6)); const index = decoded.indexOf(":"); if (index === -1) { return false; } const user = decoded.slice(0, index); const pass = decoded.slice(index + 1); return user === getAdminBasicAuthUser() && pass === getAdminBasicAuthPass(); } catch { return false; } } export default async function middleware(request: NextRequest) { const { pathname } = request.nextUrl; const isDevelopmentAdminRequest = process.env.NODE_ENV !== "production" && isDevelopmentAdminPath(pathname); const hostname = getRequestHostname( request.headers.get("x-forwarded-host"), request.headers.get("host"), request.nextUrl.hostname, ); const isAdminRequest = isDevelopmentAdminRequest || isAdminHost(hostname); const hasDedicatedAdminHostname = hasDedicatedAdminHost(); const adminRobotsHeaders = { "X-Robots-Tag": "noindex, nofollow, noarchive, nosnippet, noimageindex", }; if ( isDevelopmentAdminRequest && hasDedicatedAdminHostname && !isAdminHost(hostname) ) { const redirectUrl = new URL(getAdminBaseUrl()); redirectUrl.pathname = fromDevelopmentAdminPath(pathname); redirectUrl.search = request.nextUrl.search; return NextResponse.redirect(redirectUrl, 308); } if (isLegacyAdminPath(pathname) && process.env.NODE_ENV === "production") { return new NextResponse("Not Found", { status: 404, }); } if (isInternalAdminPath(pathname) && process.env.NODE_ENV === "production" && !isAdminRequest) { return new NextResponse("Not Found", { status: 404, }); } if (isAdminRequest) { if (isAdminBasicAuthConfigured() && !isAdminBasicAuthValid(request)) { return new NextResponse("Authentication required", { status: 401, headers: { "WWW-Authenticate": 'Basic realm="Admin Area", charset="UTF-8"', ...adminRobotsHeaders, }, }); } const rewriteUrl = request.nextUrl.clone(); rewriteUrl.pathname = toInternalAdminPath( isDevelopmentAdminRequest ? fromDevelopmentAdminPath(pathname) : pathname, ); const response = NextResponse.rewrite(rewriteUrl); response.headers.set("X-Robots-Tag", adminRobotsHeaders["X-Robots-Tag"]); return response; } const siteRuntimeState = await getSiteRuntimeState(request); const configuredDefaultLocale = siteRuntimeState.defaultLocale; const intlMiddleware = createMiddleware(createI18nRouting(configuredDefaultLocale)); if ( siteRuntimeState.maintenanceEnabled && !request.cookies.has(ADMIN_SESSION_COOKIE) && !isComingSoonPath(pathname) ) { const locale = getPathLocale(pathname, configuredDefaultLocale); const redirectUrl = request.nextUrl.clone(); redirectUrl.pathname = getLocalizedPathWithDefault(locale, "/coming-soon", configuredDefaultLocale); return NextResponse.redirect(redirectUrl, 307); } return intlMiddleware(request); } export const config = { matcher: ["/((?!api|trpc|_next|_vercel|.*\\..*).*)"], };