Enforce maintenance mode in site layout instead of middleware fetch
CI / quality (push) Waiting to run
CI / quality (push) Waiting to run
This commit is contained in:
@@ -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 />
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user