| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374 |
- import { Request } from 'express';
- const IPV4_REGEX = /^(?:\d{1,3}\.){3}\d{1,3}$/;
- const normalizeIpValue = (value: string): string | null => {
- if (!value) return null;
- const trimmed = value.trim();
- if (!trimmed) return null;
- const lower = trimmed.toLowerCase();
- if (lower.startsWith('::ffff:')) {
- const candidate = lower.slice(7);
- if (IPV4_REGEX.test(candidate)) {
- return candidate;
- }
- }
- return trimmed;
- };
- const isIpv4 = (value: string): boolean => {
- if (!value) return false;
- if (!IPV4_REGEX.test(value)) return false;
- return value.split('.').every((part) => Number.parseInt(part, 10) <= 255);
- };
- const extractHeaderIps = (value?: string | string[]): string[] => {
- if (!value) return [];
- const raw = Array.isArray(value) ? value.join(',') : value;
- return raw
- .split(',')
- .map((entry) => entry.trim())
- .filter(Boolean);
- };
- export function extractClientIp(req: Request): string {
- const headersToCheck = [
- 'cf-connecting-ip',
- 'x-forwarded-for',
- 'x-real-ip',
- ] as const;
- let firstIpv4: string | undefined;
- let fallbackIp: string | undefined;
- for (const header of headersToCheck) {
- const entries = extractHeaderIps(req.headers[header]);
- for (const entry of entries) {
- const normalized = normalizeIpValue(entry);
- if (!normalized) continue;
- if (!fallbackIp) {
- fallbackIp = normalized;
- }
- if (!firstIpv4 && isIpv4(normalized)) {
- firstIpv4 = normalized;
- break;
- }
- }
- if (firstIpv4) {
- break;
- }
- }
- const socketIp =
- req.socket?.remoteAddress && normalizeIpValue(req.socket.remoteAddress);
- if (!fallbackIp && socketIp) {
- fallbackIp = socketIp;
- }
- if (!firstIpv4 && socketIp && isIpv4(socketIp)) {
- firstIpv4 = socketIp;
- }
- return firstIpv4 ?? fallbackIp ?? '';
- }
|