@liberstudio/cloudflare-list 2.4.6 → 2.4.7

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,8 +103,8 @@ 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
109
  }),
110
110
  }),
@@ -129,6 +129,34 @@ export class AppModule {}
129
129
  | `silentPaths` | `(string \| RegExp)[]` | no | Path bloccati su Cloudflare ma senza log su file |
130
130
  | `verbose` | `boolean` | no | Se `true`, il log include body, query, params e stack |
131
131
  | `whatsappNotify` | `{ phone: string }` | no | Invia una notifica WhatsApp quando un IP viene bannato |
132
+ | `telegramNotify` | `{ chatId: string }` | no | Invia una notifica Telegram quando un IP viene bannato |
133
+ | `notifyProvider` | `"whatsapp" \| "telegram"` | no | Canale di notifica da usare (default: vedi sotto) |
134
+
135
+ ### `notifyProvider` — scelta del canale di notifica
136
+
137
+ Ad ogni ban viene inviata **una sola** notifica, sul canale scelto:
138
+
139
+ - se `notifyProvider` è impostato, si usa quel canale;
140
+ - altrimenti si usa **WhatsApp** solo se è configurato del tutto (`whatsappNotify` + le 4 variabili `WA_*`), in ogni altro caso **Telegram**.
141
+
142
+ Se il canale scelto non è configurato (es. `telegramNotify` assente) la notifica viene saltata.
143
+
144
+ ### `telegramNotify` — notifiche ban su Telegram
145
+
146
+ Invia il messaggio di ban tramite un bot Telegram. Il token del bot è letto da `process.env.TELEGRAM_BOT_TOKEN`.
147
+
148
+ ```typescript
149
+ telegramNotify: {
150
+ chatId: "123456789", // id della chat, del gruppo (es. "-100...") o "@nomecanale"
151
+ }
152
+ ```
153
+
154
+ 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`.
155
+
156
+ ```env
157
+ TELEGRAM_BOT_TOKEN=123456:ABC-your-bot-token
158
+ TELEGRAM_NOTIFY_CHAT_ID=123456789
159
+ ```
132
160
 
133
161
  ### `whatsappNotify` — notifiche ban su WhatsApp
134
162
 
@@ -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,36 @@ 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
+ this.logger.warn(`Notifica Telegram fallita: ${msg}`);
87
+ });
88
+ }
89
+ sendWhatsappNotification(message) {
64
90
  const wa = this.options.whatsappNotify;
65
91
  if (!wa)
66
92
  return;
@@ -73,7 +99,6 @@ let AttacksService = class AttacksService {
73
99
  return;
74
100
  }
75
101
  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
102
  (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
103
  const msg = err instanceof Error ? err.message : "Errore sconosciuto";
79
104
  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.7",
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"