import createMiddleware from "next-intl/middleware"; import { NextResponse } from "next/server"; import type { NextRequest } from "next/server"; import { routing } from "./i18n/routing"; const intlMiddleware = createMiddleware(routing); const ADMIN_SESSION_COOKIE = "moh_admin_session"; async function isMaintenanceModeEnabled(request: NextRequest): Promise { try { const response = await fetch(`${request.nextUrl.origin}/api/maintenance`, { cache: "no-store", headers: { "x-middleware-cache": "bypass", }, }); if (!response.ok) { return false; } const data = (await response.json()) as { enabled?: boolean }; return data.enabled === true; } catch { return false; } } function getLocaleFromPath(pathname: string): (typeof routing.locales)[number] { const firstSegment = pathname.split("/").filter(Boolean)[0]; if (routing.locales.includes(firstSegment as (typeof routing.locales)[number])) { return firstSegment as (typeof routing.locales)[number]; } return routing.defaultLocale; } function isRootBasicAuthConfigured(): boolean { return Boolean(process.env.ROOT_BASIC_AUTH_USER && process.env.ROOT_BASIC_AUTH_PASS); } function isRootBasicAuthValid(request: NextRequest): boolean { if (!isRootBasicAuthConfigured()) { 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 === process.env.ROOT_BASIC_AUTH_USER && pass === process.env.ROOT_BASIC_AUTH_PASS ); } catch { return false; } } async function isSuperAdminSessionValid(request: NextRequest): Promise { const token = request.cookies.get(ADMIN_SESSION_COOKIE)?.value; const secret = process.env.ADMIN_AUTH_SECRET; if (!token || !secret) { return false; } const parts = token.split("."); if (parts.length !== 2) { return false; } const [value, signature] = parts; if (!value || !signature) { return false; } try { const key = await crypto.subtle.importKey( "raw", new TextEncoder().encode(secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign"], ); const signed = await crypto.subtle.sign( "HMAC", key, new TextEncoder().encode(value), ); const expected = Array.from(new Uint8Array(signed)) .map((byte) => byte.toString(16).padStart(2, "0")) .join(""); return signature === expected; } catch { return false; } } export default async function middleware(request: NextRequest) { const { pathname } = request.nextUrl; const locale = getLocaleFromPath(pathname); const isRootBaseRoute = pathname === "/root" || pathname.startsWith("/root/"); const isRootRoute = isRootBaseRoute; const isComingSoonRoute = pathname === "/coming-soon" || pathname.startsWith("/coming-soon/") || pathname === `/${locale}/coming-soon` || pathname.startsWith(`/${locale}/coming-soon/`); if (isRootRoute && isRootBasicAuthConfigured() && !isRootBasicAuthValid(request)) { return new NextResponse("Authentication required", { status: 401, headers: { "WWW-Authenticate": 'Basic realm="Root Area", charset="UTF-8"', }, }); } if (isRootBaseRoute) { return NextResponse.next(); } if (!isRootRoute && !isComingSoonRoute) { const maintenanceModeEnabled = await isMaintenanceModeEnabled(request); if (maintenanceModeEnabled) { return NextResponse.redirect(new URL(`/${locale}/coming-soon`, request.url)); } } return intlMiddleware(request); } export const config = { matcher: ["/((?!api|trpc|_next|_vercel|.*\\..*).*)"], };