preserve auth headers in json proxies
This commit is contained in:
@@ -2,6 +2,7 @@ import { NextRequest, NextResponse } from "next/server";
|
||||
import {
|
||||
applyAuthResponseCookies,
|
||||
buildBackendRequestHeaders,
|
||||
buildJsonBackendRequestHeaders,
|
||||
} from "@/lib/backend-auth";
|
||||
import {
|
||||
buildProxyExceptionResponse,
|
||||
@@ -22,10 +23,7 @@ export async function POST(req: NextRequest) {
|
||||
const body = await req.text();
|
||||
const res = await fetch(`${API_BASE}/api/auth/referral/apply`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
...auth.headers,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
headers: buildJsonBackendRequestHeaders(auth.headers),
|
||||
body,
|
||||
cache: "no-store",
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { applyAuthResponseCookies, buildBackendRequestHeaders } from "@/lib/backend-auth";
|
||||
import { applyAuthResponseCookies, buildBackendRequestHeaders, buildJsonBackendRequestHeaders } from "@/lib/backend-auth";
|
||||
import { buildProxyExceptionResponse } from "@/lib/api-proxy";
|
||||
import { requireOpsProxyAuth } from "@/lib/ops-proxy-auth";
|
||||
|
||||
@@ -28,7 +28,7 @@ export async function PUT(req: NextRequest) {
|
||||
if (authError) return authError;
|
||||
|
||||
const body = await req.text();
|
||||
const res = await fetch(BACKEND, { method: "PUT", headers: { ...auth.headers, "Content-Type": "application/json" }, body, cache: "no-store" });
|
||||
const res = await fetch(BACKEND, { method: "PUT", headers: buildJsonBackendRequestHeaders(auth.headers), body, cache: "no-store" });
|
||||
const raw = await res.text();
|
||||
const response = new NextResponse(raw, { status: res.status, headers: { "Content-Type": "application/json", "Cache-Control": "no-store" } });
|
||||
return applyAuthResponseCookies(response, auth.response);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { applyAuthResponseCookies, buildBackendRequestHeaders } from "@/lib/backend-auth";
|
||||
import { applyAuthResponseCookies, buildBackendRequestHeaders, buildJsonBackendRequestHeaders } from "@/lib/backend-auth";
|
||||
import { buildProxyExceptionResponse } from "@/lib/api-proxy";
|
||||
import { requireOpsProxyAuth } from "@/lib/ops-proxy-auth";
|
||||
|
||||
@@ -28,7 +28,7 @@ export async function PUT(req: NextRequest) {
|
||||
if (authError) return authError;
|
||||
|
||||
const body = await req.text();
|
||||
const res = await fetch(BACKEND, { method: "PUT", headers: { ...auth.headers, "Content-Type": "application/json" }, body, cache: "no-store" });
|
||||
const res = await fetch(BACKEND, { method: "PUT", headers: buildJsonBackendRequestHeaders(auth.headers), body, cache: "no-store" });
|
||||
const raw = await res.text();
|
||||
const response = new NextResponse(raw, { status: res.status, headers: { "Content-Type": "application/json", "Cache-Control": "no-store" } });
|
||||
return applyAuthResponseCookies(response, auth.response);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { applyAuthResponseCookies, buildBackendRequestHeaders } from "@/lib/backend-auth";
|
||||
import { applyAuthResponseCookies, buildBackendRequestHeaders, buildJsonBackendRequestHeaders } from "@/lib/backend-auth";
|
||||
import { buildProxyExceptionResponse } from "@/lib/api-proxy";
|
||||
import { requireOpsProxyAuth } from "@/lib/ops-proxy-auth";
|
||||
|
||||
@@ -14,9 +14,9 @@ export async function POST(req: NextRequest) {
|
||||
if (authError) return authError;
|
||||
|
||||
const body = await req.text();
|
||||
const headers: Record<string, string> = { ...auth.headers as Record<string, string>, "Content-Type": "application/json" };
|
||||
const headers = buildJsonBackendRequestHeaders(auth.headers);
|
||||
if (ENTITLEMENT_TOKEN) {
|
||||
headers.Authorization = `Bearer ${ENTITLEMENT_TOKEN}`;
|
||||
headers.set("Authorization", `Bearer ${ENTITLEMENT_TOKEN}`);
|
||||
}
|
||||
const res = await fetch(`${API_BASE}/api/ops/subscriptions/extend`, {
|
||||
method: "POST", headers, body, cache: "no-store",
|
||||
|
||||
@@ -2,6 +2,7 @@ import { NextRequest, NextResponse } from "next/server";
|
||||
import {
|
||||
applyAuthResponseCookies,
|
||||
buildBackendRequestHeaders,
|
||||
buildJsonBackendRequestHeaders,
|
||||
} from "@/lib/backend-auth";
|
||||
import { buildProxyExceptionResponse } from "@/lib/api-proxy";
|
||||
import { requireOpsProxyAuth } from "@/lib/ops-proxy-auth";
|
||||
@@ -166,7 +167,7 @@ export async function POST(req: NextRequest) {
|
||||
|
||||
const res = await fetch(`${API_BASE}/api/ops/subscriptions/grant`, {
|
||||
method: "POST",
|
||||
headers: { ...(auth.headers as Record<string, string>), "Content-Type": "application/json" },
|
||||
headers: buildJsonBackendRequestHeaders(auth.headers),
|
||||
body,
|
||||
cache: "no-store",
|
||||
});
|
||||
|
||||
@@ -2,6 +2,7 @@ import { NextRequest, NextResponse } from "next/server";
|
||||
import {
|
||||
applyAuthResponseCookies,
|
||||
buildBackendRequestHeaders,
|
||||
buildJsonBackendRequestHeaders,
|
||||
} from "@/lib/backend-auth";
|
||||
import { buildProxyExceptionResponse } from "@/lib/api-proxy";
|
||||
import { requireOpsProxyAuth } from "@/lib/ops-proxy-auth";
|
||||
@@ -24,10 +25,7 @@ export async function POST(req: NextRequest) {
|
||||
const body = await req.text();
|
||||
const res = await fetch(`${API_BASE}/api/ops/users/grant-points`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
...auth.headers,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
headers: buildJsonBackendRequestHeaders(auth.headers),
|
||||
body,
|
||||
cache: "no-store",
|
||||
});
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
|
||||
function assert(condition: unknown, message: string): asserts condition {
|
||||
if (!condition) throw new Error(message);
|
||||
}
|
||||
|
||||
function collectRouteFiles(dir: string): string[] {
|
||||
return fs.readdirSync(dir, { withFileTypes: true }).flatMap((entry) => {
|
||||
const fullPath = path.join(dir, entry.name);
|
||||
if (entry.isDirectory()) return collectRouteFiles(fullPath);
|
||||
return entry.name === "route.ts" ? [fullPath] : [];
|
||||
});
|
||||
}
|
||||
|
||||
export function runTests() {
|
||||
const projectRoot = process.cwd();
|
||||
const apiRoot = path.join(projectRoot, "app", "api");
|
||||
const unsafeRoutes = collectRouteFiles(apiRoot)
|
||||
.filter((routePath) =>
|
||||
/\.\.\.\s*(?:\(\s*)?auth\.headers/.test(fs.readFileSync(routePath, "utf8")),
|
||||
)
|
||||
.map((routePath) => path.relative(projectRoot, routePath));
|
||||
|
||||
assert(
|
||||
unsafeRoutes.length === 0,
|
||||
`backend auth Headers must not be object-spread because that drops authorization headers: ${unsafeRoutes.join(", ")}`,
|
||||
);
|
||||
|
||||
const helperSource = fs.readFileSync(
|
||||
path.join(projectRoot, "lib", "backend-auth.ts"),
|
||||
"utf8",
|
||||
);
|
||||
assert(
|
||||
helperSource.includes("buildJsonBackendRequestHeaders") &&
|
||||
helperSource.includes("new Headers(headers)") &&
|
||||
helperSource.includes('result.set("Content-Type", "application/json")'),
|
||||
"backend auth must expose a safe JSON header builder",
|
||||
);
|
||||
}
|
||||
@@ -125,6 +125,12 @@ export function applyAuthResponseCookies(
|
||||
return target;
|
||||
}
|
||||
|
||||
export function buildJsonBackendRequestHeaders(headers: HeadersInit) {
|
||||
const result = new Headers(headers);
|
||||
result.set("Content-Type", "application/json");
|
||||
return result;
|
||||
}
|
||||
|
||||
export function requireBackendAuthUser(auth: BackendHeaderBuildResult) {
|
||||
if (auth.authUserId) return null;
|
||||
return applyAuthResponseCookies(
|
||||
|
||||
Reference in New Issue
Block a user