fix(auth): Anmeldung unter Node 24 (Proxy um NextRequest)
Docker-Images bauen und veroeffentlichen / lint (push) Successful in 1m15s
Docker-Images bauen und veroeffentlichen / typecheck (push) Successful in 1m20s
Docker-Images bauen und veroeffentlichen / test (push) Successful in 1m19s
Docker-Images bauen und veroeffentlichen / build-and-push (push) Failing after 1m14s
Docker-Images bauen und veroeffentlichen / lint (push) Successful in 1m15s
Docker-Images bauen und veroeffentlichen / typecheck (push) Successful in 1m20s
Docker-Images bauen und veroeffentlichen / test (push) Successful in 1m19s
Docker-Images bauen und veroeffentlichen / build-and-push (push) Failing after 1m14s
Next reicht Route-Handlern einen Proxy um NextRequest. undici 7 (Node 24) prueft private Felder (#state), die ein Proxy nicht besitzt; new Request(request) warf "Cannot read private member #state" und jede /api/auth-Anfrage endete lokal mit 500. Produktion (Node 22, undici 6) war nicht betroffen. withClientIp baut die Anfrage jetzt aus URL, Methode, Headern und Body neu. Regressionstest mit einem Proxy wie in Next.
This commit is contained in:
1 parent
0421d7d57a
commit
9785eb221f
3 files changed
+37
-9
No files matched your search
@@ -3,9 +3,9 @@ import { getAuth } from "@/lib/auth";
|
||||
import { withClientIp } from "@/lib/rate-limit";
|
||||
|
||||
export async function GET(request: Request) {
|
||||
return toNextJsHandler(getAuth()).GET(withClientIp(request));
|
||||
return toNextJsHandler(getAuth()).GET(await withClientIp(request));
|
||||
}
|
||||
|
||||
export async function POST(request: Request) {
|
||||
return toNextJsHandler(getAuth()).POST(withClientIp(request));
|
||||
return toNextJsHandler(getAuth()).POST(await withClientIp(request));
|
||||
}
|
||||
+11
-3
@@ -147,7 +147,15 @@ export function headersWithClientIp(source: Headers): Headers {
|
||||
return headers;
|
||||
}
|
||||
|
||||
/** Kopie der Anfrage mit CLIENT_IP_HEADER aus clientIp(); ein mitgeschickter Wert wird ersetzt. */
|
||||
export function withClientIp(request: Request): Request {
|
||||
return new Request(request, { headers: headersWithClientIp(request.headers) });
|
||||
/**
|
||||
* Kopie der Anfrage mit CLIENT_IP_HEADER aus clientIp(); ein mitgeschickter Wert wird ersetzt.
|
||||
*
|
||||
* Bewusst aus URL, Methode und Body neu gebaut statt `new Request(request, …)`:
|
||||
* Next reicht Route-Handlern einen Proxy um NextRequest, und undici 7 (Node 24)
|
||||
* prueft private Felder (#state), die ein Proxy nicht hat ("Cannot read private
|
||||
* member #state"). Unter Node 22 (undici 6, Symbole) fiel das nicht auf.
|
||||
*/
|
||||
export async function withClientIp(request: Request): Promise<Request> {
|
||||
const body = request.method === "GET" || request.method === "HEAD" ? undefined : await request.arrayBuffer();
|
||||
return new Request(request.url, { method: request.method, headers: headersWithClientIp(request.headers), body, signal: request.signal });
|
||||
}
|
||||
+24
-4
@@ -28,18 +28,38 @@ describe("clientIp hinter Traefik", () => {
|
||||
expect(rateLimitSubject("198.51.100.9")).toBe("198.51.100.9");
|
||||
});
|
||||
|
||||
test("better-auth bekommt eine einzelne gültige IP (es normalisiert IPv6 selbst auf /64)", () => {
|
||||
test("better-auth bekommt eine einzelne gültige IP (es normalisiert IPv6 selbst auf /64)", async () => {
|
||||
const request = new Request("https://example.org/api/auth/sign-in/email", { headers: { "x-forwarded-for": "2001:db8:1:2:aaaa::1" } });
|
||||
expect(withClientIp(request).headers.get(CLIENT_IP_HEADER)).toBe("2001:db8:1:2:aaaa::1");
|
||||
expect((await withClientIp(request)).headers.get(CLIENT_IP_HEADER)).toBe("2001:db8:1:2:aaaa::1");
|
||||
});
|
||||
|
||||
test("withClientIp setzt den Header für better-auth und überschreibt einen mitgeschickten Wert", () => {
|
||||
test("withClientIp setzt den Header für better-auth und überschreibt einen mitgeschickten Wert", async () => {
|
||||
const request = new Request("https://example.org/api/auth/sign-in/email", {
|
||||
method: "POST",
|
||||
headers: { "x-forwarded-for": "1.2.3.4, 198.51.100.9", [CLIENT_IP_HEADER]: "1.2.3.4" },
|
||||
body: "{}",
|
||||
});
|
||||
expect(withClientIp(request).headers.get(CLIENT_IP_HEADER)).toBe("198.51.100.9");
|
||||
expect((await withClientIp(request)).headers.get(CLIENT_IP_HEADER)).toBe("198.51.100.9");
|
||||
});
|
||||
|
||||
test("withClientIp verträgt Nexts Proxy um NextRequest (Node 24, undici 7) und behält Methode und Body", async () => {
|
||||
// Next 16 reicht Route-Handlern einen Proxy, dessen Methoden an das Original gebunden sind.
|
||||
// undici 7 prüft private Felder (#state); ein Proxy besteht diese Prüfung nicht.
|
||||
const original = new Request("https://example.org/api/auth/sign-in/email", {
|
||||
method: "POST",
|
||||
headers: { "x-forwarded-for": "198.51.100.9", "content-type": "application/json" },
|
||||
body: '{"email":"[email protected]"}',
|
||||
});
|
||||
const wieNext = new Proxy(original, {
|
||||
get(target, key) {
|
||||
const wert = Reflect.get(target, key, target);
|
||||
return typeof wert === "function" ? wert.bind(target) : wert;
|
||||
},
|
||||
});
|
||||
const kopie = await withClientIp(wieNext);
|
||||
expect(kopie.method).toBe("POST");
|
||||
expect(kopie.headers.get(CLIENT_IP_HEADER)).toBe("198.51.100.9");
|
||||
expect(await kopie.text()).toBe('{"email":"[email protected]"}');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in new issue
Block a user