@liberstudio/cloudflare-list 2.3.2 → 2.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,7 +7,7 @@ export declare class AttacksService {
7
7
  private readonly API_URL;
8
8
  private readonly ASN_LOOKUP_URL;
9
9
  constructor(httpService: HttpService, options: CloudflareAttacksOptions);
10
- blockIp(rawIp: string): Promise<CloudflareResponse>;
10
+ blockIp(rawIp: string): Promise<CloudflareResponse | null>;
11
11
  private lookupAsnRange;
12
12
  updateIpList(cidr: string): Promise<CloudflareResponse>;
13
13
  }
@@ -31,6 +31,10 @@ let AttacksService = class AttacksService {
31
31
  this.ASN_LOOKUP_URL = `https://api.hackertarget.com/aslookup`;
32
32
  }
33
33
  async blockIp(rawIp) {
34
+ if (await (0, utils_1.isCloudflareIp)(rawIp)) {
35
+ this.logger.log(`IP ${rawIp} appartiene al range Cloudflare — ban saltato`);
36
+ return null;
37
+ }
34
38
  const asnRange = await this.lookupAsnRange(rawIp);
35
39
  if (asnRange) {
36
40
  this.logger.log(`ASN range trovato per ${rawIp}: ${asnRange} — blocco intera subnet`);
@@ -0,0 +1 @@
1
+ export declare function isCloudflareIp(ip: string): Promise<boolean>;
@@ -0,0 +1,82 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.isCloudflareIp = isCloudflareIp;
7
+ const axios_1 = __importDefault(require("axios"));
8
+ const net_1 = require("net");
9
+ const CF_IPS_V4_URL = "https://www.cloudflare.com/ips-v4/";
10
+ const CF_IPS_V6_URL = "https://www.cloudflare.com/ips-v6/";
11
+ const TTL_MS = 24 * 60 * 60 * 1000;
12
+ let cache = null;
13
+ let inFlight = null;
14
+ async function loadCloudflareIps() {
15
+ if (cache && Date.now() < cache.expiresAt)
16
+ return cache;
17
+ if (inFlight)
18
+ return inFlight;
19
+ inFlight = (async () => {
20
+ const [v4Res, v6Res] = await Promise.all([
21
+ axios_1.default.get(CF_IPS_V4_URL, { responseType: "text" }),
22
+ axios_1.default.get(CF_IPS_V6_URL, { responseType: "text" }),
23
+ ]);
24
+ const parse = (data) => data.split(/\r?\n/).map((s) => s.trim()).filter(Boolean);
25
+ cache = {
26
+ v4: parse(v4Res.data),
27
+ v6: parse(v6Res.data),
28
+ expiresAt: Date.now() + TTL_MS,
29
+ };
30
+ return cache;
31
+ })().finally(() => {
32
+ inFlight = null;
33
+ });
34
+ return inFlight;
35
+ }
36
+ function ipv4ToInt(ip) {
37
+ return ip.split(".").reduce((acc, oct) => (acc << 8) + Number(oct), 0) >>> 0;
38
+ }
39
+ function ipv6ToBigInt(ip) {
40
+ const [headStr, tailStr] = ip.includes("::") ? ip.split("::") : [ip, ""];
41
+ const head = headStr ? headStr.split(":") : [];
42
+ const tail = tailStr ? tailStr.split(":") : [];
43
+ const missing = 8 - head.length - tail.length;
44
+ const groups = [...head, ...Array(missing).fill("0"), ...tail];
45
+ return groups.reduce((acc, g) => (acc << 16n) + BigInt(parseInt(g || "0", 16)), 0n);
46
+ }
47
+ function isInCidrV4(ip, cidr) {
48
+ const [range, bitsStr] = cidr.split("/");
49
+ const bits = bitsStr === undefined ? 32 : Number(bitsStr);
50
+ if (bits === 0)
51
+ return true;
52
+ const mask = (0xffffffff << (32 - bits)) >>> 0;
53
+ return (ipv4ToInt(ip) & mask) === (ipv4ToInt(range) & mask);
54
+ }
55
+ function isInCidrV6(ip, cidr) {
56
+ const [range, bitsStr] = cidr.split("/");
57
+ const prefix = bitsStr === undefined ? 128 : Number(bitsStr);
58
+ if (prefix === 0)
59
+ return true;
60
+ const mask = ((1n << 128n) - 1n) ^ ((1n << BigInt(128 - prefix)) - 1n);
61
+ return (ipv6ToBigInt(ip) & mask) === (ipv6ToBigInt(range) & mask);
62
+ }
63
+ async function isCloudflareIp(ip) {
64
+ if (!ip)
65
+ return false;
66
+ let pure = ip.includes("/") ? ip.split("/")[0] : ip;
67
+ pure = pure.trim();
68
+ if (pure.startsWith("::ffff:"))
69
+ pure = pure.slice("::ffff:".length);
70
+ const version = (0, net_1.isIP)(pure);
71
+ if (!version)
72
+ return false;
73
+ try {
74
+ const { v4, v6 } = await loadCloudflareIps();
75
+ return version === 4
76
+ ? v4.some((cidr) => isInCidrV4(pure, cidr))
77
+ : v6.some((cidr) => isInCidrV6(pure, cidr));
78
+ }
79
+ catch {
80
+ return false;
81
+ }
82
+ }
@@ -1,3 +1,4 @@
1
1
  export * from './cloudflare.constants';
2
+ export * from './cloudflare-ips.util';
2
3
  export * from './get-client-ip.util';
3
4
  export * from './options.validator';
@@ -15,5 +15,6 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./cloudflare.constants"), exports);
18
+ __exportStar(require("./cloudflare-ips.util"), exports);
18
19
  __exportStar(require("./get-client-ip.util"), exports);
19
20
  __exportStar(require("./options.validator"), exports);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@liberstudio/cloudflare-list",
3
- "version": "2.3.2",
3
+ "version": "2.3.4",
4
4
  "description": "Modulo NestJS per gestione IP List Cloudflare",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",