@liberstudio/cloudflare-list 2.4.6 → 2.4.8

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/LICENSE CHANGED
File without changes
package/README.md CHANGED
@@ -76,7 +76,7 @@ import { CloudflareAttacksModule } from "@liberstudio/cloudflare-list";
76
76
  excludedPaths: ["/api/health", "/api/webhook"],
77
77
  silentPaths: ["/auth/me", "/auth/refresh"],
78
78
  verbose: false,
79
- whatsappNotify: { phone: "393331234567" },
79
+ telegramNotify: { chatId: "123456789" },
80
80
  }),
81
81
  ],
82
82
  })
@@ -103,9 +103,10 @@ import { CloudflareAttacksModule } from "@liberstudio/cloudflare-list";
103
103
  excludedPaths: ["/api/health", "/api/webhook", /^\/api\/public\/.*/],
104
104
  silentPaths: ["/auth/me", "/auth/refresh"],
105
105
  verbose: false,
106
- whatsappNotify: {
107
- phone: config.getOrThrow<string>("WA_NOTIFY_PHONE"),
106
+ telegramNotify: {
107
+ chatId: config.getOrThrow<string>("TELEGRAM_NOTIFY_CHAT_ID"),
108
108
  },
109
+ notifyProvider: config.get<"whatsapp" | "telegram">("NOTIFY_PROVIDER"), // assente → scelta automatica
109
110
  }),
110
111
  }),
111
112
  ],
@@ -129,6 +130,35 @@ export class AppModule {}
129
130
  | `silentPaths` | `(string \| RegExp)[]` | no | Path bloccati su Cloudflare ma senza log su file |
130
131
  | `verbose` | `boolean` | no | Se `true`, il log include body, query, params e stack |
131
132
  | `whatsappNotify` | `{ phone: string }` | no | Invia una notifica WhatsApp quando un IP viene bannato |
133
+ | `telegramNotify` | `{ chatId: string }` | no | Invia una notifica Telegram quando un IP viene bannato |
134
+ | `notifyProvider` | `"whatsapp" \| "telegram"` | no | Canale di notifica da usare (default: vedi sotto) |
135
+
136
+ ### `notifyProvider` — scelta del canale di notifica
137
+
138
+ Ad ogni ban viene inviata **una sola** notifica, sul canale scelto:
139
+
140
+ - se `notifyProvider` è impostato, si usa quel canale;
141
+ - altrimenti si usa **WhatsApp** solo se è configurato del tutto (`whatsappNotify` + le 4 variabili `WA_*`), in ogni altro caso **Telegram**.
142
+
143
+ Se il canale scelto non è configurato (es. `telegramNotify` assente) la notifica viene saltata.
144
+
145
+ ### `telegramNotify` — notifiche ban su Telegram
146
+
147
+ Invia il messaggio di ban tramite un bot Telegram. Il token del bot è letto da `process.env.TELEGRAM_BOT_TOKEN`.
148
+
149
+ ```typescript
150
+ telegramNotify: {
151
+ chatId: "123456789", // id della chat, del gruppo (es. "-100...") o "@nomecanale"
152
+ }
153
+ ```
154
+
155
+ Per ricavare il `chatId`: scrivi un messaggio al bot (o aggiungilo al gruppo), poi apri `https://api.telegram.org/bot<TOKEN>/getUpdates` e leggi `message.chat.id`.
156
+
157
+ ```env
158
+ TELEGRAM_BOT_TOKEN=123456:ABC-your-bot-token
159
+ TELEGRAM_NOTIFY_CHAT_ID=123456789
160
+ NOTIFY_PROVIDER=telegram # opzionale: whatsapp | telegram
161
+ ```
132
162
 
133
163
  ### `whatsappNotify` — notifiche ban su WhatsApp
134
164
 
@@ -8,6 +8,9 @@ export declare class AttacksService {
8
8
  private readonly ASN_LOOKUP_URL;
9
9
  constructor(httpService: HttpService, options: CloudflareAttacksOptions);
10
10
  blockIp(rawIp: string): Promise<CloudflareResponse | null>;
11
+ private sendNotification;
12
+ private isWhatsappConfigured;
13
+ private sendTelegramNotification;
11
14
  private sendWhatsappNotification;
12
15
  private lookupAsnRange;
13
16
  updateIpList(cidr: string): Promise<CloudflareResponse>;
@@ -47,7 +47,7 @@ let AttacksService = class AttacksService {
47
47
  if (asnRange && !(await (0, utils_1.googleBotOverlapsCidr)(asnRange))) {
48
48
  this.logger.error(`ASN range trovato per ${rawIp}: ${asnRange} — blocco intera subnet`);
49
49
  const result = await this.updateIpList(asnRange);
50
- this.sendWhatsappNotification(rawIp, asnRange);
50
+ this.sendNotification(rawIp, asnRange);
51
51
  return result;
52
52
  }
53
53
  if (asnRange) {
@@ -57,10 +57,37 @@ let AttacksService = class AttacksService {
57
57
  if (!cidr)
58
58
  throw new Error(`IP non valido: ${rawIp}`);
59
59
  const result = await this.updateIpList(cidr);
60
- this.sendWhatsappNotification(rawIp, cidr);
60
+ this.sendNotification(rawIp, cidr);
61
61
  return result;
62
62
  }
63
- sendWhatsappNotification(rawIp, cidr) {
63
+ sendNotification(rawIp, cidr) {
64
+ const message = `🚨 IP bannato su Cloudflare\nProvenienza: ${this.options.comment}\nIP: ${rawIp}\nSubnet bloccata: ${cidr}`;
65
+ const provider = this.options.notifyProvider ?? (this.isWhatsappConfigured() ? "whatsapp" : "telegram");
66
+ if (provider === "whatsapp")
67
+ this.sendWhatsappNotification(message);
68
+ else
69
+ this.sendTelegramNotification(message);
70
+ }
71
+ isWhatsappConfigured() {
72
+ const env = process.env;
73
+ return !!(this.options.whatsappNotify && env["WA_URL"] && env["WA_USER"] && env["WA_PASSWORD"] && env["CLOUDFLARE_WA_DEVICE_ID"]);
74
+ }
75
+ sendTelegramNotification(message) {
76
+ const tg = this.options.telegramNotify;
77
+ if (!tg)
78
+ return;
79
+ const token = process.env["TELEGRAM_BOT_TOKEN"];
80
+ if (!token) {
81
+ this.logger.warn("Notifica Telegram saltata: TELEGRAM_BOT_TOKEN mancante");
82
+ return;
83
+ }
84
+ (0, rxjs_1.firstValueFrom)(this.httpService.post(`https://api.telegram.org/bot${token}/sendMessage`, { chat_id: tg.chatId, text: message })).catch((err) => {
85
+ const msg = err instanceof Error ? err.message : "Errore sconosciuto";
86
+ const description = axios_2.default.isAxiosError(err) ? err.response?.data?.description : undefined;
87
+ this.logger.warn(`Notifica Telegram fallita: ${msg}${description ? ` (${description})` : ""}`);
88
+ });
89
+ }
90
+ sendWhatsappNotification(message) {
64
91
  const wa = this.options.whatsappNotify;
65
92
  if (!wa)
66
93
  return;
@@ -73,7 +100,6 @@ let AttacksService = class AttacksService {
73
100
  return;
74
101
  }
75
102
  const auth = Buffer.from(`${user}:${password}`).toString("base64");
76
- const message = `🚨 IP bannato su Cloudflare\nProvenienza: ${this.options.comment}\nIP: ${rawIp}\nSubnet bloccata: ${cidr}`;
77
103
  (0, rxjs_1.firstValueFrom)(this.httpService.post(`${url}/send/message`, { phone: wa.phone, message }, { headers: { Authorization: `Basic ${auth}`, "X-Device-Id": deviceId } })).catch((err) => {
78
104
  const msg = err instanceof Error ? err.message : "Errore sconosciuto";
79
105
  this.logger.warn(`Notifica WhatsApp fallita: ${msg}`);
@@ -1,6 +1,10 @@
1
1
  export interface WhatsappNotifyOptions {
2
2
  phone: string;
3
3
  }
4
+ export interface TelegramNotifyOptions {
5
+ chatId: string;
6
+ }
7
+ export type NotifyProvider = "whatsapp" | "telegram";
4
8
  export interface CloudflareAttacksOptions {
5
9
  apiToken: string;
6
10
  accountId: string;
@@ -12,6 +16,8 @@ export interface CloudflareAttacksOptions {
12
16
  silentPaths?: (string | RegExp)[];
13
17
  verbose?: boolean;
14
18
  whatsappNotify?: WhatsappNotifyOptions;
19
+ telegramNotify?: TelegramNotifyOptions;
20
+ notifyProvider?: NotifyProvider;
15
21
  }
16
22
  export interface CloudflareAttacksAsyncOptions {
17
23
  imports?: any[];
@@ -53,11 +53,19 @@ let AttackLoggerMiddleware = AttackLoggerMiddleware_1 = class AttackLoggerMiddle
53
53
  if (!ip || this.isThrottled(ip))
54
54
  return;
55
55
  const silent = this.isSilent(req.url);
56
+ // Provenienza reale della richiesta: serve a capire se è passata dalla zona Cloudflare
57
+ // (cf-ray assente → accesso diretto all'origin; host inatteso → altro hostname/zona)
58
+ const origin = {
59
+ remoteAddress: req.socket.remoteAddress ?? "unknown",
60
+ host: this.sanitize(req.headers.host ?? "-"),
61
+ cfRay: this.sanitize(String(req.headers["cf-ray"] ?? "-")),
62
+ };
56
63
  if (!silent) {
57
64
  const entry = {
58
65
  timestamp: new Date().toISOString(),
59
66
  type: "ATTACK",
60
67
  ip,
68
+ ...origin,
61
69
  method: req.method,
62
70
  url: this.sanitize(req.url),
63
71
  ...(this.options.verbose && {
@@ -77,6 +85,7 @@ let AttackLoggerMiddleware = AttackLoggerMiddleware_1 = class AttackLoggerMiddle
77
85
  this.logger.warn(`IP non valido: ${ip}`);
78
86
  return;
79
87
  }
88
+ this.logger.warn(`Provenienza ${ip}: socket=${origin.remoteAddress} host=${origin.host} cf-ray=${origin.cfRay}`);
80
89
  this.attSrv.blockIp(ip).catch((err) => {
81
90
  const msg = err instanceof Error ? err.message : "Errore sconosciuto";
82
91
  this.logger.error(`Aggiornamento Cloudflare fallito: ${msg}`);
@@ -11,4 +11,8 @@ function validateOptions(opzioni) {
11
11
  throw new Error(`CloudflareAttacksOptions.${campo} deve essere una stringa non vuota`);
12
12
  }
13
13
  }
14
+ const provider = opzioni.notifyProvider;
15
+ if (provider !== undefined && provider !== "whatsapp" && provider !== "telegram") {
16
+ throw new Error(`CloudflareAttacksOptions.notifyProvider deve essere "whatsapp" o "telegram"`);
17
+ }
14
18
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@liberstudio/cloudflare-list",
3
- "version": "2.4.6",
3
+ "version": "2.4.8",
4
4
  "description": "Modulo NestJS per gestione IP List Cloudflare",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -25,19 +25,19 @@
25
25
  "pre-commit": "[ -f \"$HOME/.bashrc\" ] && \\. \"$HOME/.bashrc\"; export NVM_DIR=\"$HOME/.nvm\"; [ -s \"$NVM_DIR/nvm.sh\" ] && \\. \"$NVM_DIR/nvm.sh\"; npm run version:bump && git add package.json package-lock.json"
26
26
  },
27
27
  "peerDependencies": {
28
- "@nestjs/axios": ">=3.0.0",
29
- "@nestjs/common": ">=10.4.3",
30
- "@nestjs/core": ">=10.4.3",
28
+ "@nestjs/axios": "^3.0.0 || ^4.0.0 || ^12.0.0",
29
+ "@nestjs/common": "^10.4.3 || ^11.0.0 || ^12.0.0",
30
+ "@nestjs/core": "^10.4.3 || ^11.0.0 || ^12.0.0",
31
31
  "axios": ">=1.18.0",
32
32
  "rxjs": ">=7.0.0"
33
33
  },
34
34
  "devDependencies": {
35
- "@nestjs/axios": "^4.0.1",
36
- "@nestjs/common": "^11.1.27",
37
- "@nestjs/core": "^11.1.27",
35
+ "@nestjs/axios": "^12.0.1",
36
+ "@nestjs/common": "^12.4.0",
37
+ "@nestjs/core": "^12.4.0",
38
38
  "@types/express": "^5.0.6",
39
39
  "@types/node": "^20.19.43",
40
- "axios": "^1.18.0",
40
+ "axios": "^1.21.0",
41
41
  "rxjs": "^7.8.2",
42
42
  "simple-git-hooks": "^2.13.1",
43
43
  "typescript": "^5.0.0"