preserve auth headers in json proxies
This commit is contained in:
@@ -2,6 +2,7 @@ import { NextRequest, NextResponse } from "next/server";
|
|||||||
import {
|
import {
|
||||||
applyAuthResponseCookies,
|
applyAuthResponseCookies,
|
||||||
buildBackendRequestHeaders,
|
buildBackendRequestHeaders,
|
||||||
|
buildJsonBackendRequestHeaders,
|
||||||
} from "@/lib/backend-auth";
|
} from "@/lib/backend-auth";
|
||||||
import {
|
import {
|
||||||
buildProxyExceptionResponse,
|
buildProxyExceptionResponse,
|
||||||
@@ -22,10 +23,7 @@ export async function POST(req: NextRequest) {
|
|||||||
const body = await req.text();
|
const body = await req.text();
|
||||||
const res = await fetch(`${API_BASE}/api/auth/referral/apply`, {
|
const res = await fetch(`${API_BASE}/api/auth/referral/apply`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: buildJsonBackendRequestHeaders(auth.headers),
|
||||||
...auth.headers,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body,
|
body,
|
||||||
cache: "no-store",
|
cache: "no-store",
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { NextRequest, NextResponse } from "next/server";
|
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 { buildProxyExceptionResponse } from "@/lib/api-proxy";
|
||||||
import { requireOpsProxyAuth } from "@/lib/ops-proxy-auth";
|
import { requireOpsProxyAuth } from "@/lib/ops-proxy-auth";
|
||||||
|
|
||||||
@@ -28,7 +28,7 @@ export async function PUT(req: NextRequest) {
|
|||||||
if (authError) return authError;
|
if (authError) return authError;
|
||||||
|
|
||||||
const body = await req.text();
|
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 raw = await res.text();
|
||||||
const response = new NextResponse(raw, { status: res.status, headers: { "Content-Type": "application/json", "Cache-Control": "no-store" } });
|
const response = new NextResponse(raw, { status: res.status, headers: { "Content-Type": "application/json", "Cache-Control": "no-store" } });
|
||||||
return applyAuthResponseCookies(response, auth.response);
|
return applyAuthResponseCookies(response, auth.response);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { NextRequest, NextResponse } from "next/server";
|
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 { buildProxyExceptionResponse } from "@/lib/api-proxy";
|
||||||
import { requireOpsProxyAuth } from "@/lib/ops-proxy-auth";
|
import { requireOpsProxyAuth } from "@/lib/ops-proxy-auth";
|
||||||
|
|
||||||
@@ -28,7 +28,7 @@ export async function PUT(req: NextRequest) {
|
|||||||
if (authError) return authError;
|
if (authError) return authError;
|
||||||
|
|
||||||
const body = await req.text();
|
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 raw = await res.text();
|
||||||
const response = new NextResponse(raw, { status: res.status, headers: { "Content-Type": "application/json", "Cache-Control": "no-store" } });
|
const response = new NextResponse(raw, { status: res.status, headers: { "Content-Type": "application/json", "Cache-Control": "no-store" } });
|
||||||
return applyAuthResponseCookies(response, auth.response);
|
return applyAuthResponseCookies(response, auth.response);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { NextRequest, NextResponse } from "next/server";
|
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 { buildProxyExceptionResponse } from "@/lib/api-proxy";
|
||||||
import { requireOpsProxyAuth } from "@/lib/ops-proxy-auth";
|
import { requireOpsProxyAuth } from "@/lib/ops-proxy-auth";
|
||||||
|
|
||||||
@@ -14,9 +14,9 @@ export async function POST(req: NextRequest) {
|
|||||||
if (authError) return authError;
|
if (authError) return authError;
|
||||||
|
|
||||||
const body = await req.text();
|
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) {
|
if (ENTITLEMENT_TOKEN) {
|
||||||
headers.Authorization = `Bearer ${ENTITLEMENT_TOKEN}`;
|
headers.set("Authorization", `Bearer ${ENTITLEMENT_TOKEN}`);
|
||||||
}
|
}
|
||||||
const res = await fetch(`${API_BASE}/api/ops/subscriptions/extend`, {
|
const res = await fetch(`${API_BASE}/api/ops/subscriptions/extend`, {
|
||||||
method: "POST", headers, body, cache: "no-store",
|
method: "POST", headers, body, cache: "no-store",
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { NextRequest, NextResponse } from "next/server";
|
|||||||
import {
|
import {
|
||||||
applyAuthResponseCookies,
|
applyAuthResponseCookies,
|
||||||
buildBackendRequestHeaders,
|
buildBackendRequestHeaders,
|
||||||
|
buildJsonBackendRequestHeaders,
|
||||||
} from "@/lib/backend-auth";
|
} from "@/lib/backend-auth";
|
||||||
import { buildProxyExceptionResponse } from "@/lib/api-proxy";
|
import { buildProxyExceptionResponse } from "@/lib/api-proxy";
|
||||||
import { requireOpsProxyAuth } from "@/lib/ops-proxy-auth";
|
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`, {
|
const res = await fetch(`${API_BASE}/api/ops/subscriptions/grant`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { ...(auth.headers as Record<string, string>), "Content-Type": "application/json" },
|
headers: buildJsonBackendRequestHeaders(auth.headers),
|
||||||
body,
|
body,
|
||||||
cache: "no-store",
|
cache: "no-store",
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { NextRequest, NextResponse } from "next/server";
|
|||||||
import {
|
import {
|
||||||
applyAuthResponseCookies,
|
applyAuthResponseCookies,
|
||||||
buildBackendRequestHeaders,
|
buildBackendRequestHeaders,
|
||||||
|
buildJsonBackendRequestHeaders,
|
||||||
} from "@/lib/backend-auth";
|
} from "@/lib/backend-auth";
|
||||||
import { buildProxyExceptionResponse } from "@/lib/api-proxy";
|
import { buildProxyExceptionResponse } from "@/lib/api-proxy";
|
||||||
import { requireOpsProxyAuth } from "@/lib/ops-proxy-auth";
|
import { requireOpsProxyAuth } from "@/lib/ops-proxy-auth";
|
||||||
@@ -24,10 +25,7 @@ export async function POST(req: NextRequest) {
|
|||||||
const body = await req.text();
|
const body = await req.text();
|
||||||
const res = await fetch(`${API_BASE}/api/ops/users/grant-points`, {
|
const res = await fetch(`${API_BASE}/api/ops/users/grant-points`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: buildJsonBackendRequestHeaders(auth.headers),
|
||||||
...auth.headers,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body,
|
body,
|
||||||
cache: "no-store",
|
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;
|
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) {
|
export function requireBackendAuthUser(auth: BackendHeaderBuildResult) {
|
||||||
if (auth.authUserId) return null;
|
if (auth.authUserId) return null;
|
||||||
return applyAuthResponseCookies(
|
return applyAuthResponseCookies(
|
||||||
|
|||||||
Reference in New Issue
Block a user