refactor maintenance mode enforcement
CI / quality (push) Waiting to run

This commit is contained in:
MOH
2026-03-07 14:30:09 +01:00
parent 7f4277d1d7
commit 917be15dd5
3 changed files with 5 additions and 152 deletions
-119
View File
@@ -3,11 +3,8 @@ import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
import { routing } from "./i18n/routing";
import { getLocalizedPath } from "./lib/locale";
const intlMiddleware = createMiddleware(routing);
const ADMIN_SESSION_COOKIE = "moh_admin_session";
const ADMIN_SESSION_VALUE = "superadmin";
function isRootBasicAuthConfigured(): boolean {
return Boolean(process.env.ROOT_BASIC_AUTH_USER && process.env.ROOT_BASIC_AUTH_PASS);
@@ -42,99 +39,6 @@ function isRootBasicAuthValid(request: NextRequest): boolean {
}
}
function getLocaleFromPathname(pathname: string): string | null {
for (const locale of routing.locales) {
if (pathname === `/${locale}` || pathname.startsWith(`/${locale}/`)) {
return locale;
}
}
return null;
}
function timingSafeEqualString(a: string, b: string): boolean {
if (a.length !== b.length) {
return false;
}
let mismatch = 0;
for (let index = 0; index < a.length; index += 1) {
mismatch |= a.charCodeAt(index) ^ b.charCodeAt(index);
}
return mismatch === 0;
}
function toHex(buffer: ArrayBuffer): string {
return Array.from(new Uint8Array(buffer))
.map((value) => value.toString(16).padStart(2, "0"))
.join("");
}
async function signAdminValue(value: string): Promise<string | null> {
const secret = process.env.ADMIN_AUTH_SECRET ?? "";
if (!secret) {
return null;
}
const encoder = new TextEncoder();
const key = await crypto.subtle.importKey(
"raw",
encoder.encode(secret),
{ name: "HMAC", hash: "SHA-256" },
false,
["sign"],
);
const signature = await crypto.subtle.sign("HMAC", key, encoder.encode(value));
return toHex(signature);
}
async function isAdminSessionValid(request: NextRequest): Promise<boolean> {
const token = request.cookies.get(ADMIN_SESSION_COOKIE)?.value;
if (!token) {
return false;
}
const parts = token.split(".");
if (parts.length !== 2) {
return false;
}
const [value, signature] = parts;
if (value !== ADMIN_SESSION_VALUE) {
return false;
}
const expected = await signAdminValue(value);
if (!expected) {
return false;
}
return timingSafeEqualString(signature, expected);
}
async function getMaintenanceModeFromApi(request: NextRequest): Promise<boolean> {
try {
const response = await fetch(new URL("/api/maintenance", request.nextUrl.origin), {
cache: "no-store",
headers: {
"x-middleware-check": "1",
},
});
if (!response.ok) {
return false;
}
const data = (await response.json()) as { enabled?: boolean };
return data.enabled === true;
} catch {
return false;
}
}
export default async function middleware(request: NextRequest) {
const { pathname } = request.nextUrl;
const isRootBaseRoute = pathname === "/root" || pathname.startsWith("/root/");
@@ -156,29 +60,6 @@ export default async function middleware(request: NextRequest) {
});
}
const locale = getLocaleFromPathname(pathname);
const isComingSoonRoute =
locale !== null &&
(pathname === `/${locale}/coming-soon` || pathname.startsWith(`/${locale}/coming-soon/`));
const isLocalizedSiteRoute = locale !== null && !isComingSoonRoute;
const isTopLevelRootRoute = pathname === "/";
if (!isRootBaseRoute && (isLocalizedSiteRoute || isTopLevelRootRoute)) {
const maintenanceEnabled = await getMaintenanceModeFromApi(request);
if (maintenanceEnabled) {
const isAdminAuthenticated = await isAdminSessionValid(request);
if (!isAdminAuthenticated) {
const targetLocale = locale ?? routing.defaultLocale;
const redirectUrl = request.nextUrl.clone();
redirectUrl.pathname = getLocalizedPath(targetLocale, "/coming-soon");
redirectUrl.search = "";
return NextResponse.redirect(redirectUrl);
}
}
}
if (isRootBaseRoute) {
return NextResponse.next();
}