127 lines
3.6 KiB
TypeScript
127 lines
3.6 KiB
TypeScript
import createMiddleware from "next-intl/middleware";
|
|
import { NextResponse } from "next/server";
|
|
import type { NextRequest } from "next/server";
|
|
|
|
import { routing } from "./i18n/routing";
|
|
import {
|
|
fromDevelopmentAdminPath,
|
|
getAdminBaseUrl,
|
|
getRequestHostname,
|
|
isDevelopmentAdminPath,
|
|
isAdminHost,
|
|
hasDedicatedAdminHost,
|
|
isInternalAdminPath,
|
|
isLegacyAdminPath,
|
|
toInternalAdminPath,
|
|
} from "./lib/admin-routing";
|
|
|
|
const intlMiddleware = createMiddleware(routing);
|
|
|
|
function getAdminBasicAuthUser(): string {
|
|
return process.env.ADMIN_BASIC_AUTH_USER ?? "";
|
|
}
|
|
|
|
function getAdminBasicAuthPass(): string {
|
|
return process.env.ADMIN_BASIC_AUTH_PASS ?? "";
|
|
}
|
|
|
|
function isAdminBasicAuthConfigured(): boolean {
|
|
return Boolean(getAdminBasicAuthUser() && getAdminBasicAuthPass());
|
|
}
|
|
|
|
function isAdminBasicAuthValid(request: NextRequest): boolean {
|
|
if (!isAdminBasicAuthConfigured()) {
|
|
return false;
|
|
}
|
|
|
|
const header = request.headers.get("authorization");
|
|
if (!header || !header.startsWith("Basic ")) {
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
const decoded = atob(header.slice(6));
|
|
const index = decoded.indexOf(":");
|
|
if (index === -1) {
|
|
return false;
|
|
}
|
|
|
|
const user = decoded.slice(0, index);
|
|
const pass = decoded.slice(index + 1);
|
|
|
|
return user === getAdminBasicAuthUser() && pass === getAdminBasicAuthPass();
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
export default async function middleware(request: NextRequest) {
|
|
const { pathname } = request.nextUrl;
|
|
const isDevelopmentAdminRequest =
|
|
process.env.NODE_ENV !== "production" && isDevelopmentAdminPath(pathname);
|
|
const hostname = getRequestHostname(
|
|
request.headers.get("host") ?? request.headers.get("x-forwarded-host") ?? request.nextUrl.hostname,
|
|
);
|
|
const isAdminRequest = isDevelopmentAdminRequest || isAdminHost(hostname);
|
|
const hasDedicatedAdminHostname = hasDedicatedAdminHost();
|
|
const adminRobotsHeaders = {
|
|
"X-Robots-Tag": "noindex, nofollow, noarchive, nosnippet, noimageindex",
|
|
};
|
|
|
|
if (
|
|
isDevelopmentAdminRequest &&
|
|
hasDedicatedAdminHostname &&
|
|
!isAdminHost(hostname)
|
|
) {
|
|
const redirectUrl = new URL(getAdminBaseUrl());
|
|
redirectUrl.pathname = fromDevelopmentAdminPath(pathname);
|
|
redirectUrl.search = request.nextUrl.search;
|
|
return NextResponse.redirect(redirectUrl, 308);
|
|
}
|
|
|
|
if (isLegacyAdminPath(pathname) && process.env.NODE_ENV === "production") {
|
|
return new NextResponse("Not Found", {
|
|
status: 404,
|
|
});
|
|
}
|
|
|
|
if (isInternalAdminPath(pathname) && process.env.NODE_ENV === "production" && !isAdminRequest) {
|
|
return new NextResponse("Not Found", {
|
|
status: 404,
|
|
});
|
|
}
|
|
|
|
if (isAdminRequest) {
|
|
if (isAdminBasicAuthConfigured() && !isAdminBasicAuthValid(request)) {
|
|
return new NextResponse("Authentication required", {
|
|
status: 401,
|
|
headers: {
|
|
"WWW-Authenticate": 'Basic realm="Admin Area", charset="UTF-8"',
|
|
...adminRobotsHeaders,
|
|
},
|
|
});
|
|
}
|
|
|
|
const rewriteUrl = request.nextUrl.clone();
|
|
rewriteUrl.pathname = toInternalAdminPath(
|
|
isDevelopmentAdminRequest ? fromDevelopmentAdminPath(pathname) : pathname,
|
|
);
|
|
const response = NextResponse.rewrite(rewriteUrl);
|
|
response.headers.set("X-Robots-Tag", adminRobotsHeaders["X-Robots-Tag"]);
|
|
return response;
|
|
}
|
|
|
|
if (pathname === "/de" || pathname.startsWith("/de/")) {
|
|
const redirectUrl = request.nextUrl.clone();
|
|
const nextPath = pathname.slice(3) || "/";
|
|
redirectUrl.pathname = nextPath;
|
|
return NextResponse.redirect(redirectUrl, 308);
|
|
}
|
|
|
|
return intlMiddleware(request);
|
|
}
|
|
|
|
export const config = {
|
|
matcher: ["/((?!api|trpc|_next|_vercel|.*\\..*).*)"],
|
|
};
|