diff --git a/app/api/maintenance/route.ts b/app/api/maintenance/route.ts deleted file mode 100644 index cfb15db..0000000 --- a/app/api/maintenance/route.ts +++ /dev/null @@ -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 }, - ); - } -} diff --git a/lib/app-config.ts b/lib/app-config.ts index ca67864..8eee6f7 100644 --- a/lib/app-config.ts +++ b/lib/app-config.ts @@ -3,15 +3,12 @@ import { prisma } from "@/lib/prisma"; export const MAINTENANCE_MODE_KEY = "maintenance_mode"; export async function getMaintenanceMode(): Promise { - 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 { diff --git a/middleware.ts b/middleware.ts index 091c250..0092bf1 100644 --- a/middleware.ts +++ b/middleware.ts @@ -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 { - 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 { - 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 { - 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(); }