feat(bun): add getOrigin option for reverse proxy support
Some checks failed
Qwik CI / Build optimizer x86_64-unknown-linux-gnu (push) Has been cancelled
Qwik CI / Build optimizer x86_64-apple-darwin (push) Has been cancelled
Qwik CI / Build optimizer aarch64-apple-darwin (push) Has been cancelled
Qwik CI / Setup (push) Has been cancelled
Qwik CI / Build Qwik (push) Has been cancelled
Qwik CI / Build optimizer x86_64-pc-windows-msvc (push) Has been cancelled
Qwik CI / Bundle Qwik (push) Has been cancelled
Qwik CI / Build Other Packages (push) Has been cancelled
Qwik CI / Build Insights (push) Has been cancelled
Qwik CI / Build Docs (push) Has been cancelled
Qwik CI / Unit Tests (push) Has been cancelled
Qwik CI / E2E Tests (map[browser:chromium host:ubuntu-latest]) (push) Has been cancelled
Qwik CI / E2E Tests (map[browser:chromium host:windows-latest]) (push) Has been cancelled
Qwik CI / E2E Tests (map[browser:webkit host:macos-latest]) (push) Has been cancelled
Qwik CI / E2E CLI Tests (map[host:macos-latest]) (push) Has been cancelled
Qwik CI / E2E CLI Tests (map[host:ubuntu-latest]) (push) Has been cancelled
Qwik CI / E2E CLI Tests (map[host:windows-latest]) (push) Has been cancelled
Qwik CI / Lint Package (push) Has been cancelled
Qwik CI / Release (push) Has been cancelled
Qwik CI / Trigger Qwik City E2E (push) Has been cancelled
Qwik CI / All requirements are met (push) Has been cancelled
Closing Issues For Inactivity / close-issues (push) Has been cancelled
Some checks failed
Qwik CI / Build optimizer x86_64-unknown-linux-gnu (push) Has been cancelled
Qwik CI / Build optimizer x86_64-apple-darwin (push) Has been cancelled
Qwik CI / Build optimizer aarch64-apple-darwin (push) Has been cancelled
Qwik CI / Setup (push) Has been cancelled
Qwik CI / Build Qwik (push) Has been cancelled
Qwik CI / Build optimizer x86_64-pc-windows-msvc (push) Has been cancelled
Qwik CI / Bundle Qwik (push) Has been cancelled
Qwik CI / Build Other Packages (push) Has been cancelled
Qwik CI / Build Insights (push) Has been cancelled
Qwik CI / Build Docs (push) Has been cancelled
Qwik CI / Unit Tests (push) Has been cancelled
Qwik CI / E2E Tests (map[browser:chromium host:ubuntu-latest]) (push) Has been cancelled
Qwik CI / E2E Tests (map[browser:chromium host:windows-latest]) (push) Has been cancelled
Qwik CI / E2E Tests (map[browser:webkit host:macos-latest]) (push) Has been cancelled
Qwik CI / E2E CLI Tests (map[host:macos-latest]) (push) Has been cancelled
Qwik CI / E2E CLI Tests (map[host:ubuntu-latest]) (push) Has been cancelled
Qwik CI / E2E CLI Tests (map[host:windows-latest]) (push) Has been cancelled
Qwik CI / Lint Package (push) Has been cancelled
Qwik CI / Release (push) Has been cancelled
Qwik CI / Trigger Qwik City E2E (push) Has been cancelled
Qwik CI / All requirements are met (push) Has been cancelled
Closing Issues For Inactivity / close-issues (push) Has been cancelled
The Bun adapter now supports computing the correct origin when behind a reverse proxy (Caddy, nginx, etc). This fixes CSRF validation failures where the browser sends Origin: https://example.com but Bun sees http://localhost:4000. Adds: - getOrigin option in QwikCityBunOptions - ORIGIN env var support - PROTOCOL_HEADER/HOST_HEADER env vars for reading proxy headers - normalizeUrl() for CSRF protection against relative protocol URLs Matches the existing Node adapter implementation. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -16,6 +16,34 @@ import { setServerPlatform } from '@builder.io/qwik/server';
|
||||
import { MIME_TYPES } from '../request-handler/mime-types';
|
||||
import { join, extname } from 'node:path';
|
||||
|
||||
function computeOrigin(request: Request, opts?: QwikCityBunOptions): string {
|
||||
return opts?.getOrigin?.(request) ?? Bun.env.ORIGIN ?? fallbackOrigin(request);
|
||||
}
|
||||
|
||||
function fallbackOrigin(request: Request): string {
|
||||
const headers = request.headers;
|
||||
const { PROTOCOL_HEADER, HOST_HEADER } = Bun.env;
|
||||
|
||||
const protocol = (PROTOCOL_HEADER && headers.get(PROTOCOL_HEADER)) || 'http';
|
||||
const host = (HOST_HEADER && headers.get(HOST_HEADER)) || headers.get('host');
|
||||
|
||||
return `${protocol}://${host}`;
|
||||
}
|
||||
|
||||
// do not allow the url to have a relative protocol url
|
||||
// which could bypass of CSRF protections
|
||||
// for example: new URL("//attacker.com", "https://qwik.build.io")
|
||||
// would return "https://attacker.com" when it should be "https://qwik.build.io/attacker.com"
|
||||
function normalizeUrl(url: string, base: string): URL {
|
||||
const DOUBLE_SLASH_REG = /\/\/|\\\\/g;
|
||||
return new URL(url.replace(DOUBLE_SLASH_REG, '/'), base);
|
||||
}
|
||||
|
||||
function getUrl(request: Request, origin: string): URL {
|
||||
const urlObj = new URL(request.url);
|
||||
return normalizeUrl(urlObj.pathname + urlObj.search, origin);
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export function createQwikCity(opts: QwikCityBunOptions) {
|
||||
// @builder.io/qwik-city/middleware/bun
|
||||
@@ -36,7 +64,8 @@ export function createQwikCity(opts: QwikCityBunOptions) {
|
||||
|
||||
async function router(request: Request) {
|
||||
try {
|
||||
const url = new URL(request.url);
|
||||
const origin = computeOrigin(request, opts);
|
||||
const url = getUrl(request, origin);
|
||||
|
||||
const serverRequestEv: ServerRequestEvent<Response> = {
|
||||
mode: 'server',
|
||||
@@ -100,7 +129,8 @@ export function createQwikCity(opts: QwikCityBunOptions) {
|
||||
|
||||
const notFound = async (request: Request) => {
|
||||
try {
|
||||
const url = new URL(request.url);
|
||||
const origin = computeOrigin(request, opts);
|
||||
const url = getUrl(request, origin);
|
||||
|
||||
// In the development server, we replace the getNotFound function
|
||||
// For static paths, we assign a static "Not Found" message.
|
||||
@@ -140,7 +170,8 @@ export function createQwikCity(opts: QwikCityBunOptions) {
|
||||
|
||||
const staticFile = async (request: Request) => {
|
||||
try {
|
||||
const url = new URL(request.url);
|
||||
const origin = computeOrigin(request, opts);
|
||||
const url = getUrl(request, origin);
|
||||
|
||||
if (isStaticPath(request.method || 'GET', url)) {
|
||||
const { filePath, content } = await openStaticFile(url);
|
||||
@@ -190,5 +221,19 @@ export interface QwikCityBunOptions extends ServerRenderOptions {
|
||||
/** Set the Cache-Control header for all static files */
|
||||
cacheControl?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Provide a function that computes the origin of the server, used to resolve relative URLs and
|
||||
* validate the request origin against CSRF attacks.
|
||||
*
|
||||
* When not specified, it defaults to the `ORIGIN` environment variable (if set).
|
||||
*
|
||||
* If `ORIGIN` is not set, it's derived from the incoming request, which is not recommended for
|
||||
* production use. You can specify the `PROTOCOL_HEADER`, `HOST_HEADER` to `X-Forwarded-Proto` and
|
||||
* `X-Forwarded-Host` respectively to override the default behavior.
|
||||
*/
|
||||
getOrigin?: (request: Request) => string | null;
|
||||
|
||||
/** Provide a function that returns a `ClientConn` for the given request. */
|
||||
getClientConn?: (request: Request) => ClientConn;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user