Files
3x-ui/docs/lib/xray/reverse-proxy.test.ts
T
MHSanaei d50c419e28 fix(web): read the client address from the proxy end of forwarded headers
getRemoteIp took a trusted proxy's X-Real-IP first, otherwise the leftmost
X-Forwarded-For entry. A proxy that forwards a client-sent X-Real-IP
unchanged (Caddy, HAProxy) or appends to X-Forwarded-For leaves both values
to the client. X-Forwarded-For is now read from the right, skipping trusted
proxies. X-Real-IP is believed only when it agrees with that hop or stands
alone, and a disagreement falls back to the proxy's own address.

The documented Caddyfile and the docs reverse-proxy generator now overwrite
X-Real-IP with the address Caddy saw.
2026-10-24 01:39:23 +02:00

53 lines
2.0 KiB
TypeScript

import { describe, it, expect } from 'vitest';
import { buildProxyConfig, buildCertCommand, type ReverseProxyOptions } from './reverse-proxy';
const base: ReverseProxyOptions = {
server: 'nginx',
domain: 'panel.example.com',
panelPort: '2053',
panelPath: '/panel',
certTool: 'certbot',
};
describe('buildProxyConfig — nginx', () => {
it('includes WebSocket upgrade headers and the upstream port', () => {
const cfg = buildProxyConfig(base);
expect(cfg).toContain('server_name panel.example.com;');
expect(cfg).toContain('proxy_pass http://127.0.0.1:2053;');
expect(cfg).toContain('proxy_set_header Upgrade $http_upgrade;');
expect(cfg).toContain('proxy_set_header Connection "upgrade";');
expect(cfg).toContain('location /panel/');
});
});
describe('buildProxyConfig — caddy', () => {
it('produces a path-scoped reverse_proxy', () => {
const cfg = buildProxyConfig({ ...base, server: 'caddy' });
expect(cfg).toContain('panel.example.com {');
expect(cfg).toContain('reverse_proxy /panel/* 127.0.0.1:2053');
});
it('proxies the whole site when path is root', () => {
const cfg = buildProxyConfig({ ...base, server: 'caddy', panelPath: '/' });
expect(cfg).toContain('reverse_proxy 127.0.0.1:2053');
});
// Caddy forwards a client-sent X-Real-IP unchanged, and the panel's login
// limiter would key on it; only an overwrite gives the address Caddy saw.
it('overwrites X-Real-IP with the address Caddy saw', () => {
for (const panelPath of ['/', '/panel']) {
const cfg = buildProxyConfig({ ...base, server: 'caddy', panelPath });
expect(cfg).toContain('header_up X-Real-IP {remote_host}');
}
});
});
describe('buildCertCommand', () => {
it('supports certbot and acme.sh', () => {
expect(buildCertCommand(base)).toContain('certbot certonly --nginx -d panel.example.com');
expect(buildCertCommand({ ...base, certTool: 'acme.sh' })).toContain(
'acme.sh --issue -d panel.example.com',
);
});
});