Enforce maintenance mode in site layout instead of middleware fetch
CI / quality (push) Waiting to run

This commit is contained in:
MOH
2026-03-06 15:34:51 +01:00
parent 90ca6d11ff
commit 59bc4a6563
2 changed files with 14 additions and 87 deletions
+14 -1
View File
@@ -1,13 +1,26 @@
import type { ReactNode } from "react"; import type { ReactNode } from "react";
import { redirect } from "next/navigation";
import { Footer } from "@/components/footer"; import { Footer } from "@/components/footer";
import { Navbar } from "@/components/navbar"; import { Navbar } from "@/components/navbar";
import { getMaintenanceMode } from "@/lib/app-config";
import { resolveLocale } from "@/lib/site-data";
type SiteLayoutProps = { type SiteLayoutProps = {
children: ReactNode; children: ReactNode;
params: {
locale: string;
};
}; };
export default function SiteLayout({ children }: SiteLayoutProps) { export default async function SiteLayout({ children, params: { locale } }: SiteLayoutProps) {
const localeKey = resolveLocale(locale);
const maintenanceEnabled = await getMaintenanceMode();
if (maintenanceEnabled) {
redirect(`/${localeKey}/coming-soon`);
}
return ( return (
<div className="flex min-h-screen flex-col"> <div className="flex min-h-screen flex-col">
<Navbar /> <Navbar />
-86
View File
@@ -5,37 +5,6 @@ import type { NextRequest } from "next/server";
import { routing } from "./i18n/routing"; import { routing } from "./i18n/routing";
const intlMiddleware = createMiddleware(routing); const intlMiddleware = createMiddleware(routing);
const ADMIN_SESSION_COOKIE = "moh_admin_session";
async function isMaintenanceModeEnabled(request: NextRequest): Promise<boolean> {
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 { function isRootBasicAuthConfigured(): boolean {
return Boolean(process.env.ROOT_BASIC_AUTH_USER && process.env.ROOT_BASIC_AUTH_PASS); return Boolean(process.env.ROOT_BASIC_AUTH_USER && process.env.ROOT_BASIC_AUTH_PASS);
@@ -70,58 +39,11 @@ function isRootBasicAuthValid(request: NextRequest): boolean {
} }
} }
async function isSuperAdminSessionValid(request: NextRequest): Promise<boolean> {
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) { export default async function middleware(request: NextRequest) {
const { pathname } = request.nextUrl; const { pathname } = request.nextUrl;
const locale = getLocaleFromPath(pathname);
const isRootBaseRoute = pathname === "/root" || pathname.startsWith("/root/"); const isRootBaseRoute = pathname === "/root" || pathname.startsWith("/root/");
const isRootRoute = isRootBaseRoute; 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)) { if (isRootRoute && isRootBasicAuthConfigured() && !isRootBasicAuthValid(request)) {
return new NextResponse("Authentication required", { return new NextResponse("Authentication required", {
@@ -136,14 +58,6 @@ export default async function middleware(request: NextRequest) {
return NextResponse.next(); 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); return intlMiddleware(request);
} }