This commit is contained in:
@@ -1,25 +0,0 @@
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
import { getMaintenanceMode } from "@/lib/app-config";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
const enabled = await getMaintenanceMode();
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
enabled,
|
||||
},
|
||||
{ status: 200 },
|
||||
);
|
||||
} catch {
|
||||
return NextResponse.json(
|
||||
{
|
||||
enabled: false,
|
||||
},
|
||||
{ status: 200 },
|
||||
);
|
||||
}
|
||||
}
|
||||
+5
-8
@@ -3,15 +3,12 @@ import { prisma } from "@/lib/prisma";
|
||||
export const MAINTENANCE_MODE_KEY = "maintenance_mode";
|
||||
|
||||
export async function getMaintenanceMode(): Promise<boolean> {
|
||||
try {
|
||||
const config = await prisma.appConfig.findUnique({
|
||||
where: { key: MAINTENANCE_MODE_KEY },
|
||||
});
|
||||
const config = await prisma.appConfig.findUnique({
|
||||
where: { key: MAINTENANCE_MODE_KEY },
|
||||
select: { value: true },
|
||||
});
|
||||
|
||||
return config?.value === "true";
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
return config?.value === "true";
|
||||
}
|
||||
|
||||
export async function setMaintenanceMode(enabled: boolean): Promise<void> {
|
||||
|
||||
-119
@@ -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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user