|
@@ -0,0 +1,74 @@
|
|
|
|
|
+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 ?? '';
|
|
|
|
|
+}
|