@liberstudio/cloudflare-list 2.1.9 → 2.2.1

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.
package/README.md CHANGED
@@ -35,6 +35,7 @@ import { CloudflareAttacksModule } from "@liberstudio/cloudflare-list";
35
35
  apiToken: "<api_token>",
36
36
  comment: "<comment>",
37
37
  logPath: "<logPath>",
38
+ excludedPaths: "<excludedList>",
38
39
  }),
39
40
  ],
40
41
  })
@@ -52,12 +53,13 @@ import { CloudflareAttacksModule } from "@liberstudio/cloudflare-list";
52
53
  imports: [ConfigModule],
53
54
  inject: [ConfigService],
54
55
  useFactory: (config: ConfigService) => ({
55
- apiToken: config.getOrThrow<string>('CLOUDFLARE_API_TOKEN'),
56
- accountId: config.getOrThrow<string>('CLOUDFLARE_ACCOUNT_ID'),
57
- listId: config.getOrThrow<string>('CLOUDFLARE_LIST_ID'),
58
- comment: config.get<string>('CLOUDFLARE_LIST_COMMENT') || 'Blocked',
59
- logPath: config.get<string>('CLOUDFLARE_LIST_LOG_PATH') || '/var/log/nestjs-attacks.log',
60
- })
56
+ apiToken: config.getOrThrow<string>("CLOUDFLARE_API_TOKEN"),
57
+ accountId: config.getOrThrow<string>("CLOUDFLARE_ACCOUNT_ID"),
58
+ listId: config.getOrThrow<string>("CLOUDFLARE_LIST_ID"),
59
+ comment: config.get<string>("CLOUDFLARE_LIST_COMMENT") || "Blocked",
60
+ logPath: config.get<string>("CLOUDFLARE_LIST_LOG_PATH") || "/var/log/nestjs-attacks.log",
61
+ excludedPaths: ["/api/health", "/api/webhook", /^\/api\/public\/.*/],
62
+ }),
61
63
  }),
62
64
  ],
63
65
  })
@@ -65,4 +67,5 @@ export class AppModule {}
65
67
  ```
66
68
 
67
69
  ## License
70
+
68
71
  This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
@@ -4,6 +4,7 @@ export interface CloudflareAttacksOptions {
4
4
  listId: string;
5
5
  comment: string;
6
6
  logPath: string;
7
+ excludedPaths?: (string | RegExp)[];
7
8
  }
8
9
  export interface CloudflareAttacksAsyncOptions {
9
10
  imports?: any[];
@@ -15,5 +15,6 @@ export declare class AttackLoggerMiddleware implements NestMiddleware, OnModuleD
15
15
  private isThrottled;
16
16
  private sanitize;
17
17
  private ensureDirectoryExists;
18
+ private isExcluded;
18
19
  onModuleDestroy(): void;
19
20
  }
@@ -42,7 +42,7 @@ let AttackLoggerMiddleware = AttackLoggerMiddleware_1 = class AttackLoggerMiddle
42
42
  }
43
43
  use(req, res, next) {
44
44
  res.on("finish", () => {
45
- if (res.statusCode === 404) {
45
+ if (res.statusCode === 404 && !this.isExcluded(req.url)) {
46
46
  this.handleSuspicious(req);
47
47
  }
48
48
  });
@@ -65,9 +65,11 @@ let AttackLoggerMiddleware = AttackLoggerMiddleware_1 = class AttackLoggerMiddle
65
65
  this.logger.debug("Stream del log degli attacchi svuotato");
66
66
  });
67
67
  }
68
- const cidr = (0, utils_1.toCidr)(ip);
69
- if (!cidr)
68
+ const cidr = (0, utils_1.normalizeIp)(ip);
69
+ if (!cidr) {
70
+ this.logger.warn(`IP non valido: ${ip}`);
70
71
  return;
72
+ }
71
73
  this.attSrv.updateIpList(cidr).catch((err) => {
72
74
  const msg = err instanceof Error ? err.message : "Errore sconosciuto";
73
75
  this.logger.error(`Aggiornamento Cloudflare fallito: ${msg}`);
@@ -92,6 +94,10 @@ let AttackLoggerMiddleware = AttackLoggerMiddleware_1 = class AttackLoggerMiddle
92
94
  this.logger.log(`Cartella dei log creata: ${dir}`);
93
95
  }
94
96
  }
97
+ isExcluded(url) {
98
+ const excluded = this.options.excludedPaths ?? [];
99
+ return excluded.some((p) => (p instanceof RegExp ? p.test(url) : url.startsWith(p)));
100
+ }
95
101
  onModuleDestroy() {
96
102
  this.stream.end();
97
103
  for (const timeout of this.recentIps.values()) {
@@ -1,3 +1,3 @@
1
- import { Request } from "express";
1
+ import type { Request } from "express";
2
2
  export declare function getClientIp(req: Request): string;
3
- export declare function toCidr(ip: string): string | null;
3
+ export declare function normalizeIp(ip: string): string | null;
@@ -1,7 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getClientIp = getClientIp;
4
- exports.toCidr = toCidr;
4
+ exports.normalizeIp = normalizeIp;
5
+ const net_1 = require("net");
5
6
  function getClientIp(req) {
6
7
  const cfIp = req.headers["cf-connecting-ip"];
7
8
  if (typeof cfIp === "string")
@@ -15,16 +16,39 @@ function getClientIp(req) {
15
16
  }
16
17
  return req.socket.remoteAddress ?? "unknown";
17
18
  }
18
- function toCidr(ip) {
19
+ function normalizeIp(ip) {
19
20
  if (!ip)
20
21
  return null;
21
- // IPv6
22
- if (ip.includes(":")) {
23
- return `${ip}/128`;
22
+ ip = ip.trim();
23
+ // IPv4-mapped IPv6 → converti in IPv4 puro
24
+ if (ip.startsWith("::ffff:")) {
25
+ ip = ip.replace("::ffff:", "");
24
26
  }
25
- // IPv4
26
- if (ip.includes(".")) {
27
+ const version = (0, net_1.isIP)(ip);
28
+ if (!version)
29
+ return null;
30
+ // Blocca loopback
31
+ if (ip === "127.0.0.1" || ip === "::1")
32
+ return null;
33
+ // Blocca IPv4 privati
34
+ if (version === 4) {
35
+ if (ip.startsWith("10.") || ip.startsWith("192.168.") || (ip.startsWith("172.") && isPrivate172(ip))) {
36
+ return null;
37
+ }
27
38
  return `${ip}/32`;
28
39
  }
40
+ // Blocca IPv6 link-local e unique local
41
+ if (version === 6) {
42
+ if (ip.startsWith("fe80:") || // link-local
43
+ ip.startsWith("fc") || // unique local
44
+ ip.startsWith("fd")) {
45
+ return null;
46
+ }
47
+ return `${ip}/128`;
48
+ }
29
49
  return null;
30
50
  }
51
+ function isPrivate172(ip) {
52
+ const secondOctet = Number(ip.split('.')[1]);
53
+ return secondOctet >= 16 && secondOctet <= 31;
54
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@liberstudio/cloudflare-list",
3
- "version": "2.1.9",
3
+ "version": "2.2.1",
4
4
  "description": "Modulo NestJS per gestione IP List Cloudflare",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",