Skip to content

Commit

Permalink
feat: add behindProxy option to App.listen()
Browse files Browse the repository at this point in the history
  • Loading branch information
dahlia committed Sep 29, 2024
1 parent b5389f6 commit 05773ef
Showing 1 changed file with 54 additions and 2 deletions.
56 changes: 54 additions & 2 deletions src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,17 @@ const DEFAULT_NOT_ALLOWED_METHOD = () => {
throw new HttpError(405);
};

export type ListenOptions = Partial<Deno.ServeTlsOptions> & {
export interface HandlerOptions {
/**
* Whether to respect `X-Forwarded-Proto`, `X-Forwarded-Host`,
* `X-Forwarded-Port`, and `X-Forwarded-For` headers. This is useful when
* Fresh is behind a reverse proxy like Nginx or Caddy.
* @default {false}
*/
behindProxy?: boolean;
}

export type ListenOptions = Partial<Deno.ServeTlsOptions> & HandlerOptions & {
remoteAddress?: string;
};

Expand Down Expand Up @@ -167,7 +177,7 @@ export class App<State> {
return this;
}

async handler(): Promise<
async handler(options: HandlerOptions = {}): Promise<
(request: Request, info?: Deno.ServeHandlerInfo) => Promise<Response>
> {
if (this.#buildCache === null) {
Expand All @@ -184,6 +194,8 @@ export class App<State> {
return missingBuildHandler;
}

const behindProxy = options.behindProxy ?? false;

return async (
req: Request,
conn: Deno.ServeHandlerInfo | Deno.ServeUnixHandlerInfo =
Expand All @@ -193,6 +205,46 @@ export class App<State> {
// Prevent open redirect attacks
url.pathname = url.pathname.replace(/\/+/g, "/");

// If Fresh is behind a reverse proxy, we need to respect the headers
// that the proxy sets
if (behindProxy) {
const proto = req.headers.get("X-Forwarded-Proto") ?? "http";
const host = req.headers.get("X-Forwarded-Host");
const port = req.headers.get("X-Forwarded-Port");
if (
url.protocol !== `${proto}:` || host != null && url.hostname !== host
) {
url.protocol = proto;
if (host != null) url.hostname = host;
if (
port != null &&
(proto === "http" && port !== "80" ||
proto === "https" && port !== "443")
) url.port = port;

req = new Request(url, req);
}

conn = {
remoteAddr: {
transport: "tcp",
hostname: req.headers.get("X-Forwarded-For") ??
("hostname" in conn.remoteAddr
? conn.remoteAddr.hostname
: "localhost"),
port: port
? parseInt(port)
: proto === "https"
? 443
: proto === "http"
? 80
: "port" in conn.remoteAddr
? conn.remoteAddr.port
: 80,
},
};
}

const method = req.method.toUpperCase() as Method;
const matched = this.#router.match(method, url);

Expand Down

0 comments on commit 05773ef

Please sign in to comment.