client-ip.util.ts 1.8 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374
  1. import { Request } from 'express';
  2. const IPV4_REGEX = /^(?:\d{1,3}\.){3}\d{1,3}$/;
  3. const normalizeIpValue = (value: string): string | null => {
  4. if (!value) return null;
  5. const trimmed = value.trim();
  6. if (!trimmed) return null;
  7. const lower = trimmed.toLowerCase();
  8. if (lower.startsWith('::ffff:')) {
  9. const candidate = lower.slice(7);
  10. if (IPV4_REGEX.test(candidate)) {
  11. return candidate;
  12. }
  13. }
  14. return trimmed;
  15. };
  16. const isIpv4 = (value: string): boolean => {
  17. if (!value) return false;
  18. if (!IPV4_REGEX.test(value)) return false;
  19. return value.split('.').every((part) => Number.parseInt(part, 10) <= 255);
  20. };
  21. const extractHeaderIps = (value?: string | string[]): string[] => {
  22. if (!value) return [];
  23. const raw = Array.isArray(value) ? value.join(',') : value;
  24. return raw
  25. .split(',')
  26. .map((entry) => entry.trim())
  27. .filter(Boolean);
  28. };
  29. export function extractClientIp(req: Request): string {
  30. const headersToCheck = [
  31. 'cf-connecting-ip',
  32. 'x-forwarded-for',
  33. 'x-real-ip',
  34. ] as const;
  35. let firstIpv4: string | undefined;
  36. let fallbackIp: string | undefined;
  37. for (const header of headersToCheck) {
  38. const entries = extractHeaderIps(req.headers[header]);
  39. for (const entry of entries) {
  40. const normalized = normalizeIpValue(entry);
  41. if (!normalized) continue;
  42. if (!fallbackIp) {
  43. fallbackIp = normalized;
  44. }
  45. if (!firstIpv4 && isIpv4(normalized)) {
  46. firstIpv4 = normalized;
  47. break;
  48. }
  49. }
  50. if (firstIpv4) {
  51. break;
  52. }
  53. }
  54. const socketIp =
  55. req.socket?.remoteAddress && normalizeIpValue(req.socket.remoteAddress);
  56. if (!fallbackIp && socketIp) {
  57. fallbackIp = socketIp;
  58. }
  59. if (!firstIpv4 && socketIp && isIpv4(socketIp)) {
  60. firstIpv4 = socketIp;
  61. }
  62. return firstIpv4 ?? fallbackIp ?? '';
  63. }