2 files changed
+11
-40
No files matched your search
@@ -2,16 +2,6 @@ import { afterEach, describe, expect, it, vi } from "vitest";
|
|||||||
import { NextRequest } from "next/server";
|
import { NextRequest } from "next/server";
|
||||||
|
|
||||||
const isInstalledForRequest = vi.fn();
|
const isInstalledForRequest = vi.fn();
|
||||||
// Keep the Auth.js wrapper observable. Manager requests must not construct or
|
|
||||||
// invoke it; business requests still must use it for req.auth handling.
|
|
||||||
const { nextAuth, handleProxyRequestForTest } = vi.hoisted(() => {
|
|
||||||
const handleProxyRequestForTest = vi.fn(async (_request: NextRequest) => new Response(null, { status: 200 }));
|
|
||||||
const nextAuth = vi.fn(() => ({
|
|
||||||
auth: (request: NextRequest) => handleProxyRequestForTest(request),
|
|
||||||
}));
|
|
||||||
|
|
||||||
return { nextAuth, handleProxyRequestForTest };
|
|
||||||
});
|
|
||||||
|
|
||||||
// If proxy.ts or its Manager request path statically imports Prisma, this
|
// If proxy.ts or its Manager request path statically imports Prisma, this
|
||||||
// mock throws while the test module is loading. Manager mode must not reach it.
|
// mock throws while the test module is loading. Manager mode must not reach it.
|
||||||
@@ -21,9 +11,7 @@ vi.mock("@/lib/db/prisma", () => {
|
|||||||
|
|
||||||
vi.mock("@/lib/installation/installation-state", () => ({ isInstalledForRequest }));
|
vi.mock("@/lib/installation/installation-state", () => ({ isInstalledForRequest }));
|
||||||
|
|
||||||
vi.mock("next-auth", () => ({ default: nextAuth }));
|
import { handleProxyRequest } from "@/proxy";
|
||||||
|
|
||||||
import proxy, { handleProxyRequest } from "@/proxy";
|
|
||||||
|
|
||||||
function request(pathname: string) {
|
function request(pathname: string) {
|
||||||
return new NextRequest(`https://manager.example.com${pathname}`);
|
return new NextRequest(`https://manager.example.com${pathname}`);
|
||||||
@@ -36,26 +24,23 @@ describe("proxy deployment separation", () => {
|
|||||||
if (originalManagerMode === undefined) delete process.env.MANAGER_MODE;
|
if (originalManagerMode === undefined) delete process.env.MANAGER_MODE;
|
||||||
else process.env.MANAGER_MODE = originalManagerMode;
|
else process.env.MANAGER_MODE = originalManagerMode;
|
||||||
isInstalledForRequest.mockReset();
|
isInstalledForRequest.mockReset();
|
||||||
nextAuth.mockClear();
|
|
||||||
handleProxyRequestForTest.mockClear();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("routes the Manager root before loading or checking business installation state", async () => {
|
it("routes the Manager root before loading or checking business installation state", async () => {
|
||||||
process.env.MANAGER_MODE = "true";
|
process.env.MANAGER_MODE = "true";
|
||||||
|
|
||||||
const response = await proxy(request("/"));
|
const response = await handleProxyRequest(request("/"));
|
||||||
|
|
||||||
expect(response.status).toBe(307);
|
expect(response.status).toBe(307);
|
||||||
expect(response.headers.get("location")).toBe("https://manager.example.com/manager");
|
expect(response.headers.get("location")).toBe("https://manager.example.com/manager");
|
||||||
expect(response.headers.get("content-security-policy")).toContain("default-src 'self'");
|
expect(response.headers.get("content-security-policy")).toContain("default-src 'self'");
|
||||||
expect(isInstalledForRequest).not.toHaveBeenCalled();
|
expect(isInstalledForRequest).not.toHaveBeenCalled();
|
||||||
expect(nextAuth).not.toHaveBeenCalled();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("serves Manager routes without business installation state", async () => {
|
it("serves Manager routes without business installation state", async () => {
|
||||||
process.env.MANAGER_MODE = "true";
|
process.env.MANAGER_MODE = "true";
|
||||||
|
|
||||||
const response = await proxy(request("/manager"));
|
const response = await handleProxyRequest(request("/manager"));
|
||||||
|
|
||||||
expect(response.status).toBe(200);
|
expect(response.status).toBe(200);
|
||||||
expect(isInstalledForRequest).not.toHaveBeenCalled();
|
expect(isInstalledForRequest).not.toHaveBeenCalled();
|
||||||
@@ -72,15 +57,6 @@ describe("proxy deployment separation", () => {
|
|||||||
expect(isInstalledForRequest).toHaveBeenCalledOnce();
|
expect(isInstalledForRequest).toHaveBeenCalledOnce();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("uses Auth.js for normal business requests", async () => {
|
|
||||||
process.env.MANAGER_MODE = "false";
|
|
||||||
|
|
||||||
await proxy(request("/"));
|
|
||||||
|
|
||||||
expect(nextAuth).toHaveBeenCalledOnce();
|
|
||||||
expect(handleProxyRequestForTest).toHaveBeenCalledOnce();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("preserves protected business route redirects after installation", async () => {
|
it("preserves protected business route redirects after installation", async () => {
|
||||||
process.env.MANAGER_MODE = "false";
|
process.env.MANAGER_MODE = "false";
|
||||||
isInstalledForRequest.mockResolvedValue(true);
|
isInstalledForRequest.mockResolvedValue(true);
|
||||||
|
|||||||
+8
-13
@@ -29,15 +29,13 @@ const INSTALL_EXEMPT_PREFIXES = [
|
|||||||
"/sw.js",
|
"/sw.js",
|
||||||
];
|
];
|
||||||
|
|
||||||
// Build the business Auth.js middleware only when a business request needs
|
// Lightweight instance built from the DB-free config — see auth.config.ts.
|
||||||
// it. Manager requests must not construct or invoke this wrapper at all.
|
// Next.js 16 renamed this file convention from `middleware` to `proxy`
|
||||||
type BusinessProxy = (req: NextRequest) => Response | Promise<Response>;
|
// (https://nextjs.org/docs/messages/middleware-to-proxy); it now always
|
||||||
let authenticatedProxy: BusinessProxy | undefined;
|
// runs on the Node.js runtime rather than Edge, but this stays split from
|
||||||
|
// the full `@/auth` regardless, so checking "is there a session" doesn't
|
||||||
function getAuthenticatedProxy() {
|
// spin up a Postgres connection pool on every request.
|
||||||
authenticatedProxy ??= NextAuth(authConfig).auth as unknown as BusinessProxy;
|
const { auth } = NextAuth(authConfig);
|
||||||
return authenticatedProxy;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Nonce-gated 'strict-dynamic' script-src (Next's own documented pattern)
|
* Nonce-gated 'strict-dynamic' script-src (Next's own documented pattern)
|
||||||
@@ -141,10 +139,7 @@ export async function handleProxyRequest(req: AuthenticatedNextRequest) {
|
|||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function proxy(req: NextRequest) {
|
export default auth(handleProxyRequest);
|
||||||
if (process.env.MANAGER_MODE === "true") return handleProxyRequest(req);
|
|
||||||
return getAuthenticatedProxy()(req);
|
|
||||||
}
|
|
||||||
|
|
||||||
export const config = {
|
export const config = {
|
||||||
matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],
|
matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],
|
||||||
|
|||||||
Reference in new issue
Block a user