diff --git a/CHANGELOG.md b/CHANGELOG.md index 9111ba91..79d43fee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - [x] 🥺 Emoji Reactions are now available! You can react to any note with custom emojis. - [x] 🔎 Added support for [batch account data API](https://docs.joinmastodon.org/methods/accounts/#index). +- [x] 🛡️ Added support for `X-Forwarded-For` and `X-Forwarded-Proto` headers from trusted proxies. ### Backend diff --git a/config/config.example.toml b/config/config.example.toml index 1712b1a3..9d424a53 100644 --- a/config/config.example.toml +++ b/config/config.example.toml @@ -65,6 +65,11 @@ base_url = "https://example.com" bind = "0.0.0.0" bind_port = 8080 +# IPs of trusted proxies (for X-Forwarded-For and X-Forwarded-Proto headers) +# v4, v6, ranges and wildcards are supported +# Your proxy must set these headers correctly for versia-server to work +proxy_ips = [] + # Bans IPv4 or IPv6 IPs (wildcards, networks and ranges are supported) banned_ips = [] # Banned user agents, regex format diff --git a/packages/config/index.ts b/packages/config/index.ts index af67a3b8..e99ff545 100644 --- a/packages/config/index.ts +++ b/packages/config/index.ts @@ -384,6 +384,7 @@ export const ConfigSchema = z ), bind: z.string().min(1).default("0.0.0.0"), bind_port: unixPort.default(8080), + proxy_ips: z.array(ip).default([]), banned_ips: z.array(ip).default([]), banned_user_agents: z.array(regex).default([]), proxy_address: url diff --git a/utils/server.ts b/utils/server.ts index af8111f0..1860955d 100644 --- a/utils/server.ts +++ b/utils/server.ts @@ -2,6 +2,7 @@ import type { ConfigSchema } from "@versia-server/config"; import { debugResponse } from "@versia-server/kit/api"; import { type Server, serve } from "bun"; import type { Hono } from "hono"; +import { matches } from "ip-matching"; import type { z } from "zod"; import type { HonoEnv } from "~/types/api.ts"; @@ -22,7 +23,34 @@ export const createServer = ( : undefined, hostname: config.http.bind, async fetch(req, server): Promise { - const output = await app.fetch(req, { ip: server.requestIP(req) }); + const ip = server.requestIP(req); + const isTrustedProxy = + config.http.proxy_ips.includes("*") || + (ip && + config.http.proxy_ips.some((proxyIp) => + matches(ip.address, proxyIp), + )); + + const url = new URL(req.url); + + if (ip && isTrustedProxy) { + const xff = req.headers.get("x-forwarded-for"); + const xfp = req.headers.get("x-forwarded-proto"); + + if (xff) { + const forwardedIps = xff.split(",").map((s) => s.trim()); + const originalIp = forwardedIps[0]; + + ip.address = originalIp; + ip.family = originalIp.includes(":") ? "IPv6" : "IPv4"; + } + + if (xfp) { + url.protocol = xfp; + } + } + + const output = await app.fetch(req, { ip }); await debugResponse(output.clone());