mirror of
https://github.com/foomo/next-proxy-middleware.git
synced 2025-10-16 12:35:36 +00:00
132 lines
3.6 KiB
TypeScript
132 lines
3.6 KiB
TypeScript
import type { NextRequest } from "next/server";
|
|
import { NextResponse } from "next/server";
|
|
|
|
export type DevProxyConfig = {
|
|
debug?: boolean;
|
|
disable?: boolean;
|
|
// Remote server to be used
|
|
remoteUrl: string | ((request: NextRequest) => string);
|
|
|
|
// if set to true accept-encoding header will be forwarded
|
|
// this could cause encoding issues
|
|
allowResponseCompression?: boolean;
|
|
|
|
overrideHostHeader?: boolean;
|
|
overrideCookieDomain?: false | string;
|
|
|
|
// Basic Auth
|
|
basicAuth?: {
|
|
// will be used as the "Authorization" header
|
|
authHeader: string;
|
|
};
|
|
// Cloudflare Access Token Authorization
|
|
cfTokenAuth?: {
|
|
clientId: string;
|
|
clientSecret: string;
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Place this snippet inside your `middleware.ts` file:
|
|
*
|
|
* export function middleware(request: NextRequest) {
|
|
* if (request.nextUrl.pathname.match('^/(services|webhooks)/')) {
|
|
* return proxyMiddleware(request);
|
|
* }
|
|
* return request
|
|
* }
|
|
*
|
|
* export const config = {
|
|
* matcher: ['/services/:path*'],
|
|
* };
|
|
*
|
|
* @param request
|
|
*/
|
|
export const createProxyMiddleware = (config: DevProxyConfig) => {
|
|
if (config.debug) {
|
|
console.debug("[PROXY]", "starting proxy with config", config);
|
|
}
|
|
|
|
if (config.remoteUrl === undefined) {
|
|
throw new Error("remoteUrl is required");
|
|
}
|
|
|
|
return async (request: NextRequest) => {
|
|
if (config.disable) {
|
|
return request;
|
|
}
|
|
|
|
const remoteUrl = new URL(
|
|
typeof config.remoteUrl === "function"
|
|
? config.remoteUrl(request)
|
|
: config.remoteUrl,
|
|
);
|
|
remoteUrl.pathname = request.nextUrl.pathname;
|
|
remoteUrl.search = request.nextUrl.search;
|
|
|
|
const remoteHeaders = new Headers(request.headers);
|
|
remoteHeaders.set("host", remoteUrl.host);
|
|
|
|
if (config.basicAuth) {
|
|
remoteHeaders.set("Authorization", config.basicAuth.authHeader);
|
|
}
|
|
|
|
// disable compression for proxy
|
|
remoteHeaders.delete("accept-encoding");
|
|
|
|
console.log("[PROXY]", `${request.nextUrl.href} => ${remoteUrl.href}`);
|
|
// check if we have cloudflare headers
|
|
if (config.cfTokenAuth) {
|
|
remoteHeaders.set("CF-Access-Client-Id", config.cfTokenAuth.clientId);
|
|
remoteHeaders.set(
|
|
"CF-Access-Client-Secret",
|
|
config.cfTokenAuth.clientSecret,
|
|
);
|
|
}
|
|
|
|
// Fetch the response from the backend
|
|
const backendResponse = await fetch(remoteUrl.href, {
|
|
method: request.method,
|
|
headers: remoteHeaders,
|
|
body: request.body,
|
|
});
|
|
|
|
if (config.debug) {
|
|
console.debug("[PROXY]", "received response from remote", {
|
|
// biome-ignore lint/suspicious/noExplicitAny: inconsistency in TS
|
|
headers: Object.fromEntries(backendResponse.headers as any),
|
|
});
|
|
}
|
|
|
|
const responseHeaders = new Headers(backendResponse.headers);
|
|
if (config.overrideCookieDomain) {
|
|
const setCookieHeaders = backendResponse.headers.get("set-cookie");
|
|
if (setCookieHeaders) {
|
|
try {
|
|
if (config.debug) {
|
|
console.debug("[PROXY]", "setCookieHeaders", setCookieHeaders);
|
|
}
|
|
// const origin = new URL(request.headers.get("host") ?? "");
|
|
const rewrittenCookies = setCookieHeaders.split(",").map((cookie) => {
|
|
const [cookiePair] = cookie.split(";").map((part) => part.trim());
|
|
return `${cookiePair}; Path=/; SameSite=None; Secure; Domain=${config.overrideCookieDomain}`;
|
|
});
|
|
|
|
if (config.debug) {
|
|
console.debug("[PROXY]", "rewrittenCookies", rewrittenCookies);
|
|
}
|
|
remoteHeaders.set("set-cookie", rewrittenCookies.join(","));
|
|
} catch (e) {
|
|
console.error("Error setting cookies", e);
|
|
}
|
|
}
|
|
}
|
|
|
|
return new NextResponse(backendResponse.body, {
|
|
...backendResponse,
|
|
headers: responseHeaders,
|
|
status: backendResponse.status,
|
|
});
|
|
};
|
|
};
|