mirror of
https://github.com/better-auth/better-auth.git
synced 2026-05-27 01:16:55 -05:00
chore: dispaly warning when baseURL isn't available (#6787)
This commit is contained in:
@@ -273,6 +273,7 @@ export const router = <Option extends BetterAuthOptions>(
|
||||
//handle disabled paths
|
||||
const disabledPaths = ctx.options.disabledPaths || [];
|
||||
const pathname = new URL(req.url).pathname.replace(/\/+$/, "") || "/";
|
||||
|
||||
const normalizedPath =
|
||||
basePath === "/"
|
||||
? pathname
|
||||
|
||||
@@ -463,11 +463,287 @@ describe("disabled paths", async () => {
|
||||
});
|
||||
|
||||
const response = await auth.handler(
|
||||
new Request("http://localhost:3000/api/auth/sign-in/email/", {
|
||||
new Request("http://localhost:3000/api/auth/sign-in/email%2F", {
|
||||
method: "POST",
|
||||
}),
|
||||
);
|
||||
const response2 = await auth.handler(
|
||||
new Request("http://localhost:3000/api/auth/sign-inemail", {
|
||||
method: "POST",
|
||||
}),
|
||||
);
|
||||
expect(response.status).toBe(404);
|
||||
expect(response2.status).toBe(404);
|
||||
});
|
||||
|
||||
it("should return 404 for encoded paths", async () => {
|
||||
const { auth } = await getTestInstance({
|
||||
disabledPaths: ["/sign-in/email"],
|
||||
});
|
||||
|
||||
const response = await auth.handler(
|
||||
new Request("http://localhost:3000/api/auth/sign-in/email%2F", {
|
||||
method: "POST",
|
||||
}),
|
||||
);
|
||||
const _response2 = await auth.handler(
|
||||
new Request("http://localhost:3000/api/auth/sign-inemail", {
|
||||
method: "POST",
|
||||
}),
|
||||
);
|
||||
expect(response.status).toBe(404);
|
||||
});
|
||||
|
||||
it("should block URL encoded slash bypass attempts", async () => {
|
||||
const { auth } = await getTestInstance({
|
||||
disabledPaths: ["/sign-in/email"],
|
||||
});
|
||||
|
||||
// Try various URL encoding bypass attempts
|
||||
const encodedAttempts = [
|
||||
"http://localhost:3000/api/auth/sign-in%2Femail", // %2F = /
|
||||
"http://localhost:3000/api/auth/sign-in%252Femail", // Double encoded
|
||||
"http://localhost:3000/api/auth/sign-in%2femail", // lowercase hex
|
||||
];
|
||||
|
||||
for (const url of encodedAttempts) {
|
||||
const response = await auth.handler(
|
||||
new Request(url, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email: "test@test.com",
|
||||
password: "test",
|
||||
}),
|
||||
}),
|
||||
);
|
||||
// Should either block (404) or normalize and block
|
||||
expect(response.status).toBe(404);
|
||||
}
|
||||
});
|
||||
|
||||
it("should block path traversal attempts", async () => {
|
||||
const { auth } = await getTestInstance({
|
||||
disabledPaths: ["/sign-in/email"],
|
||||
});
|
||||
|
||||
// Try path traversal attempts
|
||||
const traversalAttempts = [
|
||||
"http://localhost:3000/api/auth/sign-in/../sign-in/email",
|
||||
"http://localhost:3000/api/auth/./sign-in/email",
|
||||
"http://localhost:3000/api/auth/sign-in/./email",
|
||||
"http://localhost:3000/api/auth/sign-in//email",
|
||||
"http://localhost:3000/api/auth/sign-in///email",
|
||||
];
|
||||
|
||||
for (const url of traversalAttempts) {
|
||||
const response = await auth.handler(
|
||||
new Request(url, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email: "test@test.com",
|
||||
password: "test",
|
||||
}),
|
||||
}),
|
||||
);
|
||||
expect(response.status).toBe(404);
|
||||
}
|
||||
});
|
||||
|
||||
it("should handle unicode and special characters in disabled paths", async () => {
|
||||
const { auth } = await getTestInstance({
|
||||
disabledPaths: ["/sign-in/email"],
|
||||
});
|
||||
|
||||
// Try unicode normalization attacks
|
||||
const specialAttempts = [
|
||||
"http://localhost:3000/api/auth/sign-in%00/email", // Null byte
|
||||
"http://localhost:3000/api/auth/sign-in\u0000/email", // Unicode null
|
||||
"http://localhost:3000/api/auth/sign-in/email%09", // Tab character
|
||||
"http://localhost:3000/api/auth/sign-in/email%20", // Space
|
||||
];
|
||||
|
||||
for (const url of specialAttempts) {
|
||||
try {
|
||||
const response = await auth.handler(
|
||||
new Request(url, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email: "test@test.com",
|
||||
password: "test",
|
||||
}),
|
||||
}),
|
||||
);
|
||||
// Should either block or handle safely
|
||||
expect([404, 400]).toContain(response.status);
|
||||
} catch (e) {
|
||||
// URL constructor may throw for invalid URLs - this is acceptable
|
||||
expect(e).toBeDefined();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it("should not be affected by case sensitivity bypass", async () => {
|
||||
const { auth } = await getTestInstance({
|
||||
disabledPaths: ["/sign-in/email"],
|
||||
});
|
||||
|
||||
// Try case variations (these should NOT be blocked unless explicitly added)
|
||||
const caseVariations = [
|
||||
"http://localhost:3000/api/auth/Sign-In/Email",
|
||||
"http://localhost:3000/api/auth/SIGN-IN/EMAIL",
|
||||
"http://localhost:3000/api/auth/Sign-in/email",
|
||||
];
|
||||
|
||||
for (const url of caseVariations) {
|
||||
const response = await auth.handler(
|
||||
new Request(url, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email: "test@test.com",
|
||||
password: "test",
|
||||
}),
|
||||
}),
|
||||
);
|
||||
// These should NOT be blocked (404) - they're different paths
|
||||
// The endpoint itself will return an error since it doesn't exist
|
||||
expect(response.status).not.toBe(200);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("trustedProxyHeaders security", () => {
|
||||
it("should not use X-Forwarded headers when trustedProxyHeaders is false", async () => {
|
||||
let capturedBaseURL: string | undefined;
|
||||
const { auth } = await getTestInstance({
|
||||
baseURL: undefined,
|
||||
advanced: {
|
||||
trustedProxyHeaders: false,
|
||||
},
|
||||
hooks: {
|
||||
before: createAuthMiddleware(async (ctx) => {
|
||||
capturedBaseURL = ctx.context.baseURL;
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
await auth.handler(
|
||||
new Request("http://localhost:3000/api/auth/ok", {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"x-forwarded-host": "evil.com",
|
||||
"x-forwarded-proto": "https",
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
// Should use the actual request URL, not the forwarded headers
|
||||
expect(capturedBaseURL).toBe("http://localhost:3000/api/auth");
|
||||
expect(capturedBaseURL).not.toContain("evil.com");
|
||||
});
|
||||
|
||||
it("should validate X-Forwarded headers when trustedProxyHeaders is true", async () => {
|
||||
let capturedBaseURL: string | undefined;
|
||||
const { auth } = await getTestInstance({
|
||||
baseURL: undefined,
|
||||
advanced: {
|
||||
trustedProxyHeaders: true,
|
||||
},
|
||||
hooks: {
|
||||
before: createAuthMiddleware(async (ctx) => {
|
||||
capturedBaseURL = ctx.context.baseURL;
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
await auth.handler(
|
||||
new Request("http://localhost:3000/api/auth/ok", {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"x-forwarded-host": "trusted-proxy.com",
|
||||
"x-forwarded-proto": "https",
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
// When trusted, should use the forwarded headers
|
||||
expect(capturedBaseURL).toBe("https://trusted-proxy.com/api/auth");
|
||||
});
|
||||
|
||||
it("should not trust partial X-Forwarded headers", async () => {
|
||||
const { auth } = await getTestInstance({
|
||||
baseURL: undefined,
|
||||
advanced: {
|
||||
trustedProxyHeaders: true,
|
||||
},
|
||||
});
|
||||
|
||||
// Only X-Forwarded-Host without X-Forwarded-Proto
|
||||
const response1 = await auth.handler(
|
||||
new Request("http://localhost:3000/api/auth/ok", {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"x-forwarded-host": "evil.com",
|
||||
// Missing x-forwarded-proto
|
||||
},
|
||||
}),
|
||||
);
|
||||
expect(response1.status).toBe(200);
|
||||
|
||||
// Only X-Forwarded-Proto without X-Forwarded-Host
|
||||
const response2 = await auth.handler(
|
||||
new Request("http://localhost:3000/api/auth/ok", {
|
||||
method: "GET",
|
||||
headers: {
|
||||
// Missing x-forwarded-host
|
||||
"x-forwarded-proto": "https",
|
||||
},
|
||||
}),
|
||||
);
|
||||
expect(response2.status).toBe(200);
|
||||
});
|
||||
|
||||
it("should handle malformed X-Forwarded headers gracefully", async () => {
|
||||
const { auth } = await getTestInstance({
|
||||
baseURL: "http://localhost:3000",
|
||||
advanced: {
|
||||
trustedProxyHeaders: true,
|
||||
},
|
||||
});
|
||||
|
||||
const malformedHeaders = [
|
||||
{
|
||||
"x-forwarded-host": "../../../../etc/passwd",
|
||||
"x-forwarded-proto": "http",
|
||||
},
|
||||
{ "x-forwarded-host": "evil.com:99999", "x-forwarded-proto": "http" },
|
||||
{ "x-forwarded-host": "evil.com", "x-forwarded-proto": "javascript" },
|
||||
{ "x-forwarded-host": "evil.com", "x-forwarded-proto": "file" },
|
||||
{ "x-forwarded-host": "", "x-forwarded-proto": "http" },
|
||||
{ "x-forwarded-host": " ", "x-forwarded-proto": "http" },
|
||||
];
|
||||
|
||||
for (const headers of malformedHeaders) {
|
||||
const response = await auth.handler(
|
||||
new Request("http://localhost:3000/api/auth/ok", {
|
||||
method: "GET",
|
||||
headers,
|
||||
}),
|
||||
);
|
||||
// Should either use fallback baseURL or handle gracefully
|
||||
expect(response.status).toBe(200);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -104,6 +104,12 @@ export async function createAuthContext(
|
||||
const logger = createLogger(options.logger);
|
||||
const baseURL = getBaseURL(options.baseURL, options.basePath);
|
||||
|
||||
if (!baseURL && !options.advanced?.trustedProxyHeaders) {
|
||||
logger.error(
|
||||
`[better-auth] Base URL could not be determined. Please set a valid base URL using the baseURL config option or the BETTER_AUTH_BASE_URL environment variable. Without this, callbacks and redirects may not work correctly.`,
|
||||
);
|
||||
}
|
||||
|
||||
const secret =
|
||||
options.secret ||
|
||||
env.BETTER_AUTH_SECRET ||
|
||||
|
||||
263
packages/better-auth/src/utils/url.test.ts
Normal file
263
packages/better-auth/src/utils/url.test.ts
Normal file
@@ -0,0 +1,263 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { getBaseURL } from "./url";
|
||||
|
||||
describe("getBaseURL", () => {
|
||||
describe("trustedProxyHeaders validation", () => {
|
||||
it("should reject malicious protocol in X-Forwarded-Proto", () => {
|
||||
const request = new Request("http://localhost:3000/test", {
|
||||
headers: {
|
||||
"x-forwarded-host": "example.com",
|
||||
"x-forwarded-proto": "javascript",
|
||||
},
|
||||
});
|
||||
|
||||
const result = getBaseURL(undefined, "/auth", request, false, true);
|
||||
expect(result).toBe("http://localhost:3000/auth");
|
||||
});
|
||||
|
||||
it("should reject file protocol in X-Forwarded-Proto", () => {
|
||||
const request = new Request("http://localhost:3000/test", {
|
||||
headers: {
|
||||
"x-forwarded-host": "example.com",
|
||||
"x-forwarded-proto": "file",
|
||||
},
|
||||
});
|
||||
|
||||
const result = getBaseURL(undefined, "/auth", request, false, true);
|
||||
|
||||
expect(result).toBe("http://localhost:3000/auth");
|
||||
});
|
||||
|
||||
it("should reject path traversal in X-Forwarded-Host", () => {
|
||||
const request = new Request("http://localhost:3000/test", {
|
||||
headers: {
|
||||
"x-forwarded-host": "../../../etc/passwd",
|
||||
"x-forwarded-proto": "http",
|
||||
},
|
||||
});
|
||||
|
||||
const result = getBaseURL(undefined, "/auth", request, false, true);
|
||||
expect(result).toBe("http://localhost:3000/auth");
|
||||
});
|
||||
|
||||
it("should reject null bytes in X-Forwarded-Host", () => {
|
||||
expect(() => {
|
||||
new Request("http://localhost:3000/test", {
|
||||
headers: {
|
||||
"x-forwarded-host": "evil.com\u0000.example.com",
|
||||
"x-forwarded-proto": "http",
|
||||
},
|
||||
});
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
it("should reject HTML injection in X-Forwarded-Host", () => {
|
||||
const request = new Request("http://localhost:3000/test", {
|
||||
headers: {
|
||||
"x-forwarded-host": "<script>alert('xss')</script>",
|
||||
"x-forwarded-proto": "http",
|
||||
},
|
||||
});
|
||||
|
||||
const result = getBaseURL(undefined, "/auth", request, false, true);
|
||||
|
||||
expect(result).toBe("http://localhost:3000/auth");
|
||||
});
|
||||
|
||||
it("should reject empty X-Forwarded-Host", () => {
|
||||
const request = new Request("http://localhost:3000/test", {
|
||||
headers: {
|
||||
"x-forwarded-host": "",
|
||||
"x-forwarded-proto": "http",
|
||||
},
|
||||
});
|
||||
|
||||
const result = getBaseURL(undefined, "/auth", request, false, true);
|
||||
|
||||
expect(result).toBe("http://localhost:3000/auth");
|
||||
});
|
||||
|
||||
it("should reject whitespace-only X-Forwarded-Host", () => {
|
||||
const request = new Request("http://localhost:3000/test", {
|
||||
headers: {
|
||||
"x-forwarded-host": " ",
|
||||
"x-forwarded-proto": "http",
|
||||
},
|
||||
});
|
||||
|
||||
const result = getBaseURL(undefined, "/auth", request, false, true);
|
||||
|
||||
expect(result).toBe("http://localhost:3000/auth");
|
||||
});
|
||||
|
||||
it("should accept valid hostname with port", () => {
|
||||
const request = new Request("http://localhost:3000/test", {
|
||||
headers: {
|
||||
"x-forwarded-host": "example.com:8080",
|
||||
"x-forwarded-proto": "https",
|
||||
},
|
||||
});
|
||||
|
||||
const result = getBaseURL(undefined, "/auth", request, false, true);
|
||||
|
||||
expect(result).toBe("https://example.com:8080/auth");
|
||||
});
|
||||
|
||||
it("should accept valid IPv4 address", () => {
|
||||
const request = new Request("http://localhost:3000/test", {
|
||||
headers: {
|
||||
"x-forwarded-host": "192.168.1.1",
|
||||
"x-forwarded-proto": "https",
|
||||
},
|
||||
});
|
||||
|
||||
const result = getBaseURL(undefined, "/auth", request, false, true);
|
||||
|
||||
expect(result).toBe("https://192.168.1.1/auth");
|
||||
});
|
||||
|
||||
it("should accept valid IPv4 address with port", () => {
|
||||
const request = new Request("http://localhost:3000/test", {
|
||||
headers: {
|
||||
"x-forwarded-host": "192.168.1.1:3000",
|
||||
"x-forwarded-proto": "http",
|
||||
},
|
||||
});
|
||||
|
||||
const result = getBaseURL(undefined, "/auth", request, false, true);
|
||||
|
||||
expect(result).toBe("http://192.168.1.1:3000/auth");
|
||||
});
|
||||
|
||||
it("should accept valid IPv6 address in brackets", () => {
|
||||
const request = new Request("http://localhost:3000/test", {
|
||||
headers: {
|
||||
"x-forwarded-host": "[2001:db8::1]",
|
||||
"x-forwarded-proto": "https",
|
||||
},
|
||||
});
|
||||
|
||||
const result = getBaseURL(undefined, "/auth", request, false, true);
|
||||
|
||||
expect(result).toBe("https://[2001:db8::1]/auth");
|
||||
});
|
||||
|
||||
it("should accept localhost", () => {
|
||||
const request = new Request("http://localhost:3000/test", {
|
||||
headers: {
|
||||
"x-forwarded-host": "localhost",
|
||||
"x-forwarded-proto": "http",
|
||||
},
|
||||
});
|
||||
|
||||
const result = getBaseURL(undefined, "/auth", request, false, true);
|
||||
|
||||
expect(result).toBe("http://localhost/auth");
|
||||
});
|
||||
|
||||
it("should accept localhost with port", () => {
|
||||
const request = new Request("http://localhost:3000/test", {
|
||||
headers: {
|
||||
"x-forwarded-host": "localhost:8080",
|
||||
"x-forwarded-proto": "http",
|
||||
},
|
||||
});
|
||||
|
||||
const result = getBaseURL(undefined, "/auth", request, false, true);
|
||||
|
||||
expect(result).toBe("http://localhost:8080/auth");
|
||||
});
|
||||
|
||||
it("should reject invalid port numbers", () => {
|
||||
const request = new Request("http://localhost:3000/test", {
|
||||
headers: {
|
||||
"x-forwarded-host": "example.com:999999",
|
||||
"x-forwarded-proto": "http",
|
||||
},
|
||||
});
|
||||
|
||||
const result = getBaseURL(undefined, "/auth", request, false, true);
|
||||
|
||||
// Should fall back to request URL due to invalid port
|
||||
expect(result).toBe("http://localhost:3000/auth");
|
||||
});
|
||||
|
||||
it("should only accept http or https protocols", () => {
|
||||
const protocols = ["ftp", "ws", "wss", "data", "blob", "javascript"];
|
||||
|
||||
for (const proto of protocols) {
|
||||
const request = new Request("http://localhost:3000/test", {
|
||||
headers: {
|
||||
"x-forwarded-host": "example.com",
|
||||
"x-forwarded-proto": proto,
|
||||
},
|
||||
});
|
||||
|
||||
const result = getBaseURL(undefined, "/auth", request, false, true);
|
||||
|
||||
expect(result).toBe("http://localhost:3000/auth");
|
||||
}
|
||||
});
|
||||
|
||||
it("should not use proxy headers when trustedProxyHeaders is false", () => {
|
||||
const request = new Request("http://localhost:3000/test", {
|
||||
headers: {
|
||||
"x-forwarded-host": "evil.com",
|
||||
"x-forwarded-proto": "https",
|
||||
},
|
||||
});
|
||||
|
||||
const result = getBaseURL(undefined, "/auth", request, false, false);
|
||||
|
||||
expect(result).toBe("http://localhost:3000/auth");
|
||||
});
|
||||
|
||||
it("should require both headers to be present", () => {
|
||||
// Only host
|
||||
const request1 = new Request("http://localhost:3000/test", {
|
||||
headers: {
|
||||
"x-forwarded-host": "example.com",
|
||||
},
|
||||
});
|
||||
|
||||
const result1 = getBaseURL(undefined, "/auth", request1, false, true);
|
||||
expect(result1).toBe("http://localhost:3000/auth");
|
||||
|
||||
// Only proto
|
||||
const request2 = new Request("http://localhost:3000/test", {
|
||||
headers: {
|
||||
"x-forwarded-proto": "https",
|
||||
},
|
||||
});
|
||||
|
||||
const result2 = getBaseURL(undefined, "/auth", request2, false, true);
|
||||
expect(result2).toBe("http://localhost:3000/auth");
|
||||
});
|
||||
|
||||
it("should handle subdomain correctly", () => {
|
||||
const request = new Request("http://localhost:3000/test", {
|
||||
headers: {
|
||||
"x-forwarded-host": "api.example.com",
|
||||
"x-forwarded-proto": "https",
|
||||
},
|
||||
});
|
||||
|
||||
const result = getBaseURL(undefined, "/auth", request, false, true);
|
||||
|
||||
expect(result).toBe("https://api.example.com/auth");
|
||||
});
|
||||
|
||||
it("should handle deep subdomain correctly", () => {
|
||||
const request = new Request("http://localhost:3000/test", {
|
||||
headers: {
|
||||
"x-forwarded-host": "api.v1.staging.example.com",
|
||||
"x-forwarded-proto": "https",
|
||||
},
|
||||
});
|
||||
|
||||
const result = getBaseURL(undefined, "/auth", request, false, true);
|
||||
|
||||
expect(result).toBe("https://api.v1.staging.example.com/auth");
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -50,6 +50,57 @@ function withPath(url: string, path = "/api/auth") {
|
||||
return `${trimmedUrl}${path}`;
|
||||
}
|
||||
|
||||
function validateProxyHeader(header: string, type: "host" | "proto"): boolean {
|
||||
if (!header || header.trim() === "") {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (type === "proto") {
|
||||
// Only allow http and https protocols
|
||||
return header === "http" || header === "https";
|
||||
}
|
||||
|
||||
if (type === "host") {
|
||||
const suspiciousPatterns = [
|
||||
/\.\./, // Path traversal
|
||||
/\0/, // Null bytes
|
||||
/[\s]/, // Whitespace (except legitimate spaces that should be trimmed)
|
||||
/^[.]/, // Starting with dot
|
||||
/[<>'"]/, // HTML/script injection characters
|
||||
/javascript:/i, // Protocol injection
|
||||
/file:/i, // File protocol
|
||||
/data:/i, // Data protocol
|
||||
];
|
||||
|
||||
if (suspiciousPatterns.some((pattern) => pattern.test(header))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Basic hostname validation (allows localhost, IPs, and domains with ports)
|
||||
// This is a simple check, not exhaustive RFC validation
|
||||
const hostnameRegex =
|
||||
/^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*(:[0-9]{1,5})?$/;
|
||||
|
||||
// Also allow IPv4 addresses
|
||||
const ipv4Regex = /^(\d{1,3}\.){3}\d{1,3}(:[0-9]{1,5})?$/;
|
||||
|
||||
// Also allow IPv6 addresses in brackets
|
||||
const ipv6Regex = /^\[[0-9a-fA-F:]+\](:[0-9]{1,5})?$/;
|
||||
|
||||
// Allow localhost variations
|
||||
const localhostRegex = /^localhost(:[0-9]{1,5})?$/i;
|
||||
|
||||
return (
|
||||
hostnameRegex.test(header) ||
|
||||
ipv4Regex.test(header) ||
|
||||
ipv6Regex.test(header) ||
|
||||
localhostRegex.test(header)
|
||||
);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export function getBaseURL(
|
||||
url?: string,
|
||||
path?: string,
|
||||
@@ -78,7 +129,14 @@ export function getBaseURL(
|
||||
const fromRequest = request?.headers.get("x-forwarded-host");
|
||||
const fromRequestProto = request?.headers.get("x-forwarded-proto");
|
||||
if (fromRequest && fromRequestProto && trustedProxyHeaders) {
|
||||
return withPath(`${fromRequestProto}://${fromRequest}`, path);
|
||||
if (
|
||||
validateProxyHeader(fromRequestProto, "proto") &&
|
||||
validateProxyHeader(fromRequest, "host")
|
||||
) {
|
||||
try {
|
||||
return withPath(`${fromRequestProto}://${fromRequest}`, path);
|
||||
} catch (_error) {}
|
||||
}
|
||||
}
|
||||
|
||||
if (request) {
|
||||
|
||||
Reference in New Issue
Block a user