import net from "net"; import type { Request } from "express"; /** * Seznam adres, ze kterých smí přijít hlavička s identitou uživatele (přihlášení přes proxy). * Podporuje jednotlivé adresy i rozsahy CIDR, IPv4 i IPv6 (např. "10.42.0.0/16,127.0.0.1,::1"). * Neplatná položka je chyba konfigurace — server kvůli ní nenaběhne (lepší než tiše důvěřovat). */ export function parseTrustedIps(list: string): net.BlockList { const blockList = new net.BlockList(); for (const raw of list.split(',').map(s => s.trim()).filter(Boolean)) { const [address, prefix] = raw.split('/'); const family = net.isIP(address); if (!family) { throw new Error(`HTTP_REMOTE_TRUSTED_IPS: neplatná adresa "${raw}"`); } const type = family === 4 ? 'ipv4' : 'ipv6'; if (prefix === undefined) { blockList.addAddress(address, type); continue; } const bits = Number(prefix); if (!Number.isInteger(bits) || bits < 0 || bits > (family === 4 ? 32 : 128)) { throw new Error(`HTTP_REMOTE_TRUSTED_IPS: neplatný rozsah "${raw}"`); } blockList.addSubnet(address, bits, type); } return blockList; } /** * Je adresa přímého protějšku spojení mezi důvěryhodnými? IPv4 mapovaná do IPv6 * („::ffff:10.42.0.84", jak ji hlásí Node na dual-stack socketu) se porovnává jako IPv4. */ export function isTrustedPeer(address: string | undefined, trusted: net.BlockList): boolean { if (!address) return false; const mapped = address.match(/^::ffff:(\d+\.\d+\.\d+\.\d+)$/i); const normalized = mapped ? mapped[1] : address; const family = net.isIP(normalized); if (!family) return false; return trusted.check(normalized, family === 4 ? 'ipv4' : 'ipv6'); } const warnedPeers = new Set(); /** * Vrátí identitu uživatele z hlavičky proxy, ale jen pokud spojení přišlo z důvěryhodné adresy. * Rozhoduje skutečná adresa TCP spojení (ne req.ip, které lze ovlivnit hlavičkou X-Forwarded-For). * Hlavičku od nedůvěryhodné adresy ignoruje a jednou za běh ji zaloguje (odhalí chybnou konfiguraci). */ export function getTrustedRemoteUser(req: Request, headerName: string, trusted: net.BlockList): string | undefined { const value = req.header(headerName); if (!value) return undefined; const peer = req.socket?.remoteAddress; if (!isTrustedPeer(peer, trusted)) { const key = peer ?? 'neznámá adresa'; if (!warnedPeers.has(key)) { warnedPeers.add(key); console.warn(`Přihlášení přes hlavičky: ignoruji hlavičku ${headerName} z nedůvěryhodné adresy ${key} (viz HTTP_REMOTE_TRUSTED_IPS)`); } return undefined; } return Buffer.from(value, 'latin1').toString(); }