Fix maintenance redirects and admin host detection
This commit is contained in:
@@ -1,15 +1,19 @@
|
|||||||
import { NextResponse } from "next/server";
|
import { NextResponse } from "next/server";
|
||||||
|
|
||||||
import { getSiteSettings } from "@/lib/app-config";
|
import { getMaintenanceMode, getSiteSettings } from "@/lib/app-config";
|
||||||
|
|
||||||
export const dynamic = "force-dynamic";
|
export const dynamic = "force-dynamic";
|
||||||
|
|
||||||
export async function GET() {
|
export async function GET() {
|
||||||
const settings = await getSiteSettings();
|
const [settings, maintenanceEnabled] = await Promise.all([
|
||||||
|
getSiteSettings(),
|
||||||
|
getMaintenanceMode(),
|
||||||
|
]);
|
||||||
|
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{
|
{
|
||||||
defaultLocale: settings.defaultLocale,
|
defaultLocale: settings.defaultLocale,
|
||||||
|
maintenanceEnabled,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
|
|||||||
+14
-6
@@ -43,12 +43,20 @@ export function getSiteHost(): string {
|
|||||||
return parseHostname(process.env.NEXT_PUBLIC_SITE_URL ?? DEFAULT_SITE_URL) ?? "localhost";
|
return parseHostname(process.env.NEXT_PUBLIC_SITE_URL ?? DEFAULT_SITE_URL) ?? "localhost";
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getRequestHostname(hostHeader?: string | null): string {
|
export function getRequestHostname(...hostHeaders: Array<string | null | undefined>): string {
|
||||||
return (hostHeader ?? "")
|
for (const hostHeader of hostHeaders) {
|
||||||
.split(",")[0]
|
const normalizedHostname = (hostHeader ?? "")
|
||||||
?.trim()
|
.split(",")[0]
|
||||||
.toLowerCase()
|
?.trim()
|
||||||
.replace(/:\d+$/, "") ?? "";
|
.toLowerCase()
|
||||||
|
.replace(/:\d+$/, "") ?? "";
|
||||||
|
|
||||||
|
if (normalizedHostname) {
|
||||||
|
return normalizedHostname;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isAdminHost(hostname: string): boolean {
|
export function isAdminHost(hostname: string): boolean {
|
||||||
|
|||||||
+54
-10
@@ -14,11 +14,31 @@ import {
|
|||||||
isLegacyAdminPath,
|
isLegacyAdminPath,
|
||||||
toInternalAdminPath,
|
toInternalAdminPath,
|
||||||
} from "./lib/admin-routing";
|
} from "./lib/admin-routing";
|
||||||
import { getLocalizedPathWithDefault } from "./lib/locale";
|
import { getLocalizedPathWithDefault, stripLocalePrefix } from "./lib/locale";
|
||||||
|
|
||||||
const intlMiddleware = createMiddleware(routing);
|
const intlMiddleware = createMiddleware(routing);
|
||||||
|
const ADMIN_SESSION_COOKIE = "moh_admin_session";
|
||||||
|
|
||||||
async function getConfiguredDefaultLocale(request: NextRequest) {
|
type SiteRuntimeState = {
|
||||||
|
defaultLocale: (typeof routing.locales)[number];
|
||||||
|
maintenanceEnabled: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
function isSupportedLocale(locale: string | undefined): locale is (typeof routing.locales)[number] {
|
||||||
|
return locale === "ar" || locale === "en" || locale === "de";
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPathLocale(pathname: string, fallbackLocale: (typeof routing.locales)[number]) {
|
||||||
|
const locale = pathname.split("/")[1];
|
||||||
|
|
||||||
|
return isSupportedLocale(locale) ? locale : fallbackLocale;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isComingSoonPath(pathname: string) {
|
||||||
|
return stripLocalePrefix(pathname) === "/coming-soon";
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getSiteRuntimeState(request: NextRequest): Promise<SiteRuntimeState> {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(new URL("/api/site/default-locale", request.url), {
|
const response = await fetch(new URL("/api/site/default-locale", request.url), {
|
||||||
headers: {
|
headers: {
|
||||||
@@ -28,16 +48,26 @@ async function getConfiguredDefaultLocale(request: NextRequest) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
return routing.defaultLocale;
|
return {
|
||||||
|
defaultLocale: routing.defaultLocale,
|
||||||
|
maintenanceEnabled: false,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await response.json() as { defaultLocale?: string };
|
const data = await response.json() as {
|
||||||
|
defaultLocale?: string;
|
||||||
|
maintenanceEnabled?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
return data.defaultLocale === "ar" || data.defaultLocale === "en" || data.defaultLocale === "de"
|
return {
|
||||||
? data.defaultLocale
|
defaultLocale: isSupportedLocale(data.defaultLocale) ? data.defaultLocale : routing.defaultLocale,
|
||||||
: routing.defaultLocale;
|
maintenanceEnabled: data.maintenanceEnabled === true,
|
||||||
|
};
|
||||||
} catch {
|
} catch {
|
||||||
return routing.defaultLocale;
|
return {
|
||||||
|
defaultLocale: routing.defaultLocale,
|
||||||
|
maintenanceEnabled: false,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,7 +118,9 @@ export default async function middleware(request: NextRequest) {
|
|||||||
const isDevelopmentAdminRequest =
|
const isDevelopmentAdminRequest =
|
||||||
process.env.NODE_ENV !== "production" && isDevelopmentAdminPath(pathname);
|
process.env.NODE_ENV !== "production" && isDevelopmentAdminPath(pathname);
|
||||||
const hostname = getRequestHostname(
|
const hostname = getRequestHostname(
|
||||||
request.headers.get("host") ?? request.headers.get("x-forwarded-host") ?? request.nextUrl.hostname,
|
request.headers.get("x-forwarded-host"),
|
||||||
|
request.headers.get("host"),
|
||||||
|
request.nextUrl.hostname,
|
||||||
);
|
);
|
||||||
const isAdminRequest = isDevelopmentAdminRequest || isAdminHost(hostname);
|
const isAdminRequest = isDevelopmentAdminRequest || isAdminHost(hostname);
|
||||||
const hasDedicatedAdminHostname = hasDedicatedAdminHost();
|
const hasDedicatedAdminHostname = hasDedicatedAdminHost();
|
||||||
@@ -139,7 +171,19 @@ export default async function middleware(request: NextRequest) {
|
|||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
const configuredDefaultLocale = await getConfiguredDefaultLocale(request);
|
const siteRuntimeState = await getSiteRuntimeState(request);
|
||||||
|
const configuredDefaultLocale = siteRuntimeState.defaultLocale;
|
||||||
|
|
||||||
|
if (
|
||||||
|
siteRuntimeState.maintenanceEnabled &&
|
||||||
|
!request.cookies.has(ADMIN_SESSION_COOKIE) &&
|
||||||
|
!isComingSoonPath(pathname)
|
||||||
|
) {
|
||||||
|
const locale = getPathLocale(pathname, configuredDefaultLocale);
|
||||||
|
const redirectUrl = request.nextUrl.clone();
|
||||||
|
redirectUrl.pathname = getLocalizedPathWithDefault(locale, "/coming-soon", configuredDefaultLocale);
|
||||||
|
return NextResponse.redirect(redirectUrl, 307);
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
configuredDefaultLocale !== routing.defaultLocale &&
|
configuredDefaultLocale !== routing.defaultLocale &&
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
|
||||||
|
import { getRequestHostname } from "../lib/admin-routing";
|
||||||
|
|
||||||
|
describe("admin routing helpers", () => {
|
||||||
|
it("prefers the first non-empty forwarded hostname", () => {
|
||||||
|
expect(getRequestHostname("root.mohfarawati.de", "internal-service")).toBe("root.mohfarawati.de");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("normalizes ports and comma-separated proxy values", () => {
|
||||||
|
expect(getRequestHostname(undefined, "root.mohfarawati.de:443, proxy")).toBe("root.mohfarawati.de");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("falls back to an empty string when no hostname exists", () => {
|
||||||
|
expect(getRequestHostname(undefined, null, "")).toBe("");
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user