@push.rocks/smartmta 5.1.3 → 5.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.
Files changed (98) hide show
  1. package/changelog.md +15 -0
  2. package/dist_ts/00_commitinfo_data.d.ts +8 -0
  3. package/dist_ts/00_commitinfo_data.js +9 -0
  4. package/dist_ts/index.d.ts +3 -0
  5. package/dist_ts/index.js +4 -0
  6. package/dist_ts/logger.d.ts +17 -0
  7. package/dist_ts/logger.js +76 -0
  8. package/dist_ts/mail/core/classes.bouncemanager.d.ts +185 -0
  9. package/dist_ts/mail/core/classes.bouncemanager.js +569 -0
  10. package/dist_ts/mail/core/classes.email.d.ts +291 -0
  11. package/dist_ts/mail/core/classes.email.js +802 -0
  12. package/dist_ts/mail/core/classes.emailvalidator.d.ts +61 -0
  13. package/dist_ts/mail/core/classes.emailvalidator.js +184 -0
  14. package/dist_ts/mail/core/classes.templatemanager.d.ts +95 -0
  15. package/dist_ts/mail/core/classes.templatemanager.js +240 -0
  16. package/dist_ts/mail/core/index.d.ts +4 -0
  17. package/dist_ts/mail/core/index.js +6 -0
  18. package/dist_ts/mail/delivery/classes.delivery.queue.d.ts +163 -0
  19. package/dist_ts/mail/delivery/classes.delivery.queue.js +488 -0
  20. package/dist_ts/mail/delivery/classes.delivery.system.d.ts +160 -0
  21. package/dist_ts/mail/delivery/classes.delivery.system.js +630 -0
  22. package/dist_ts/mail/delivery/classes.unified.rate.limiter.d.ts +200 -0
  23. package/dist_ts/mail/delivery/classes.unified.rate.limiter.js +820 -0
  24. package/dist_ts/mail/delivery/index.d.ts +4 -0
  25. package/dist_ts/mail/delivery/index.js +6 -0
  26. package/dist_ts/mail/delivery/interfaces.d.ts +140 -0
  27. package/dist_ts/mail/delivery/interfaces.js +17 -0
  28. package/dist_ts/mail/index.d.ts +7 -0
  29. package/dist_ts/mail/index.js +12 -0
  30. package/dist_ts/mail/routing/classes.dkim.manager.d.ts +25 -0
  31. package/dist_ts/mail/routing/classes.dkim.manager.js +127 -0
  32. package/dist_ts/mail/routing/classes.dns.manager.d.ts +79 -0
  33. package/dist_ts/mail/routing/classes.dns.manager.js +415 -0
  34. package/dist_ts/mail/routing/classes.domain.registry.d.ts +54 -0
  35. package/dist_ts/mail/routing/classes.domain.registry.js +119 -0
  36. package/dist_ts/mail/routing/classes.email.action.executor.d.ts +33 -0
  37. package/dist_ts/mail/routing/classes.email.action.executor.js +137 -0
  38. package/dist_ts/mail/routing/classes.email.router.d.ts +171 -0
  39. package/dist_ts/mail/routing/classes.email.router.js +494 -0
  40. package/dist_ts/mail/routing/classes.unified.email.server.d.ts +241 -0
  41. package/dist_ts/mail/routing/classes.unified.email.server.js +935 -0
  42. package/dist_ts/mail/routing/index.d.ts +7 -0
  43. package/dist_ts/mail/routing/index.js +9 -0
  44. package/dist_ts/mail/routing/interfaces.d.ts +187 -0
  45. package/dist_ts/mail/routing/interfaces.js +2 -0
  46. package/dist_ts/mail/security/classes.dkimcreator.d.ts +72 -0
  47. package/dist_ts/mail/security/classes.dkimcreator.js +360 -0
  48. package/dist_ts/mail/security/classes.spfverifier.d.ts +62 -0
  49. package/dist_ts/mail/security/classes.spfverifier.js +87 -0
  50. package/dist_ts/mail/security/index.d.ts +2 -0
  51. package/dist_ts/mail/security/index.js +4 -0
  52. package/dist_ts/paths.d.ts +14 -0
  53. package/dist_ts/paths.js +39 -0
  54. package/dist_ts/plugins.d.ts +24 -0
  55. package/dist_ts/plugins.js +28 -0
  56. package/dist_ts/security/classes.contentscanner.d.ts +130 -0
  57. package/dist_ts/security/classes.contentscanner.js +338 -0
  58. package/dist_ts/security/classes.ipreputationchecker.d.ts +73 -0
  59. package/dist_ts/security/classes.ipreputationchecker.js +263 -0
  60. package/dist_ts/security/classes.rustsecuritybridge.d.ts +403 -0
  61. package/dist_ts/security/classes.rustsecuritybridge.js +502 -0
  62. package/dist_ts/security/classes.securitylogger.d.ts +140 -0
  63. package/dist_ts/security/classes.securitylogger.js +235 -0
  64. package/dist_ts/security/index.d.ts +4 -0
  65. package/dist_ts/security/index.js +5 -0
  66. package/package.json +6 -1
  67. package/ts/00_commitinfo_data.ts +8 -0
  68. package/ts/index.ts +3 -0
  69. package/ts/logger.ts +91 -0
  70. package/ts/mail/core/classes.bouncemanager.ts +731 -0
  71. package/ts/mail/core/classes.email.ts +942 -0
  72. package/ts/mail/core/classes.emailvalidator.ts +239 -0
  73. package/ts/mail/core/classes.templatemanager.ts +320 -0
  74. package/ts/mail/core/index.ts +5 -0
  75. package/ts/mail/delivery/classes.delivery.queue.ts +645 -0
  76. package/ts/mail/delivery/classes.delivery.system.ts +816 -0
  77. package/ts/mail/delivery/classes.unified.rate.limiter.ts +1053 -0
  78. package/ts/mail/delivery/index.ts +5 -0
  79. package/ts/mail/delivery/interfaces.ts +167 -0
  80. package/ts/mail/index.ts +17 -0
  81. package/ts/mail/routing/classes.dkim.manager.ts +157 -0
  82. package/ts/mail/routing/classes.dns.manager.ts +573 -0
  83. package/ts/mail/routing/classes.domain.registry.ts +139 -0
  84. package/ts/mail/routing/classes.email.action.executor.ts +175 -0
  85. package/ts/mail/routing/classes.email.router.ts +575 -0
  86. package/ts/mail/routing/classes.unified.email.server.ts +1207 -0
  87. package/ts/mail/routing/index.ts +9 -0
  88. package/ts/mail/routing/interfaces.ts +202 -0
  89. package/ts/mail/security/classes.dkimcreator.ts +447 -0
  90. package/ts/mail/security/classes.spfverifier.ts +126 -0
  91. package/ts/mail/security/index.ts +3 -0
  92. package/ts/paths.ts +48 -0
  93. package/ts/plugins.ts +53 -0
  94. package/ts/security/classes.contentscanner.ts +400 -0
  95. package/ts/security/classes.ipreputationchecker.ts +315 -0
  96. package/ts/security/classes.rustsecuritybridge.ts +964 -0
  97. package/ts/security/classes.securitylogger.ts +299 -0
  98. package/ts/security/index.ts +40 -0
@@ -0,0 +1,820 @@
1
+ import * as plugins from '../../plugins.js';
2
+ import { EventEmitter } from 'node:events';
3
+ import { logger } from '../../logger.js';
4
+ import { SecurityLogger, SecurityLogLevel, SecurityEventType } from '../../security/index.js';
5
+ /**
6
+ * Unified rate limiter for all email processing modes
7
+ */
8
+ export class UnifiedRateLimiter extends EventEmitter {
9
+ config;
10
+ counters = new Map();
11
+ patternCounters = new Map();
12
+ ipCounters = new Map();
13
+ domainCounters = new Map();
14
+ cleanupInterval;
15
+ stats;
16
+ /**
17
+ * Create a new unified rate limiter
18
+ * @param config Rate limit configuration
19
+ */
20
+ constructor(config) {
21
+ super();
22
+ // Set default configuration
23
+ this.config = {
24
+ global: {
25
+ maxMessagesPerMinute: config.global.maxMessagesPerMinute || 100,
26
+ maxRecipientsPerMessage: config.global.maxRecipientsPerMessage || 100,
27
+ maxConnectionsPerIP: config.global.maxConnectionsPerIP || 20,
28
+ maxErrorsPerIP: config.global.maxErrorsPerIP || 10,
29
+ maxAuthFailuresPerIP: config.global.maxAuthFailuresPerIP || 5,
30
+ blockDuration: config.global.blockDuration || 3600000 // 1 hour
31
+ },
32
+ patterns: config.patterns || {},
33
+ ips: config.ips || {},
34
+ blocks: config.blocks || {}
35
+ };
36
+ // Initialize statistics
37
+ this.stats = {
38
+ activeCounters: 0,
39
+ totalBlocked: 0,
40
+ currentlyBlocked: 0,
41
+ byPattern: {},
42
+ byIp: {}
43
+ };
44
+ // Start cleanup interval
45
+ this.startCleanupInterval();
46
+ }
47
+ /**
48
+ * Start the cleanup interval
49
+ */
50
+ startCleanupInterval() {
51
+ if (this.cleanupInterval) {
52
+ clearInterval(this.cleanupInterval);
53
+ }
54
+ // Run cleanup every minute
55
+ this.cleanupInterval = setInterval(() => this.cleanup(), 60000);
56
+ }
57
+ /**
58
+ * Stop the cleanup interval
59
+ */
60
+ stop() {
61
+ if (this.cleanupInterval) {
62
+ clearInterval(this.cleanupInterval);
63
+ this.cleanupInterval = undefined;
64
+ }
65
+ }
66
+ /**
67
+ * Destroy the rate limiter and clean up all resources
68
+ */
69
+ destroy() {
70
+ // Stop the cleanup interval
71
+ this.stop();
72
+ // Clear all maps to free memory
73
+ this.counters.clear();
74
+ this.ipCounters.clear();
75
+ this.patternCounters.clear();
76
+ // Clear blocks
77
+ if (this.config.blocks) {
78
+ this.config.blocks = {};
79
+ }
80
+ // Clear statistics
81
+ this.stats = {
82
+ activeCounters: 0,
83
+ totalBlocked: 0,
84
+ currentlyBlocked: 0,
85
+ byPattern: {},
86
+ byIp: {}
87
+ };
88
+ logger.log('info', 'UnifiedRateLimiter destroyed');
89
+ }
90
+ /**
91
+ * Clean up expired counters and blocks
92
+ */
93
+ cleanup() {
94
+ const now = Date.now();
95
+ // Clean up expired blocks
96
+ if (this.config.blocks) {
97
+ for (const [ip, expiry] of Object.entries(this.config.blocks)) {
98
+ if (expiry <= now) {
99
+ delete this.config.blocks[ip];
100
+ logger.log('info', `Rate limit block expired for IP ${ip}`);
101
+ // Update statistics
102
+ if (this.stats.byIp[ip]) {
103
+ this.stats.byIp[ip].blocked = false;
104
+ }
105
+ this.stats.currentlyBlocked--;
106
+ }
107
+ }
108
+ }
109
+ // Clean up old counters (older than 10 minutes)
110
+ const cutoff = now - 600000;
111
+ // Clean global counters
112
+ for (const [key, counter] of this.counters.entries()) {
113
+ if (counter.lastReset < cutoff) {
114
+ this.counters.delete(key);
115
+ }
116
+ }
117
+ // Clean pattern counters
118
+ for (const [key, counter] of this.patternCounters.entries()) {
119
+ if (counter.lastReset < cutoff) {
120
+ this.patternCounters.delete(key);
121
+ }
122
+ }
123
+ // Clean IP counters
124
+ for (const [key, counter] of this.ipCounters.entries()) {
125
+ if (counter.lastReset < cutoff) {
126
+ this.ipCounters.delete(key);
127
+ }
128
+ }
129
+ // Clean domain counters
130
+ for (const [key, counter] of this.domainCounters.entries()) {
131
+ if (counter.lastReset < cutoff) {
132
+ this.domainCounters.delete(key);
133
+ }
134
+ }
135
+ // Update statistics
136
+ this.updateStats();
137
+ }
138
+ /**
139
+ * Check if a message is allowed by rate limits
140
+ * @param email Email address
141
+ * @param ip IP address
142
+ * @param recipients Number of recipients
143
+ * @param pattern Matched pattern
144
+ * @param domain Domain name for domain-specific limits
145
+ * @returns Result of rate limit check
146
+ */
147
+ checkMessageLimit(email, ip, recipients, pattern, domain) {
148
+ // Check if IP is blocked
149
+ if (this.isIpBlocked(ip)) {
150
+ return {
151
+ allowed: false,
152
+ reason: 'IP is blocked',
153
+ resetIn: this.getBlockReleaseTime(ip)
154
+ };
155
+ }
156
+ // Check global message rate limit
157
+ const globalResult = this.checkGlobalMessageLimit(email);
158
+ if (!globalResult.allowed) {
159
+ return globalResult;
160
+ }
161
+ // Check pattern-specific limit if pattern is provided
162
+ if (pattern) {
163
+ const patternResult = this.checkPatternMessageLimit(pattern);
164
+ if (!patternResult.allowed) {
165
+ return patternResult;
166
+ }
167
+ }
168
+ // Check domain-specific limit if domain is provided
169
+ if (domain) {
170
+ const domainResult = this.checkDomainMessageLimit(domain);
171
+ if (!domainResult.allowed) {
172
+ return domainResult;
173
+ }
174
+ }
175
+ // Check IP-specific limit
176
+ const ipResult = this.checkIpMessageLimit(ip);
177
+ if (!ipResult.allowed) {
178
+ return ipResult;
179
+ }
180
+ // Check recipient limit
181
+ const recipientResult = this.checkRecipientLimit(email, recipients, pattern, domain);
182
+ if (!recipientResult.allowed) {
183
+ return recipientResult;
184
+ }
185
+ // All checks passed
186
+ return { allowed: true };
187
+ }
188
+ /**
189
+ * Check global message rate limit
190
+ * @param email Email address
191
+ */
192
+ checkGlobalMessageLimit(email) {
193
+ const now = Date.now();
194
+ const limit = this.config.global.maxMessagesPerMinute;
195
+ if (!limit) {
196
+ return { allowed: true };
197
+ }
198
+ // Get or create counter
199
+ const key = 'global';
200
+ let counter = this.counters.get(key);
201
+ if (!counter) {
202
+ counter = {
203
+ count: 0,
204
+ lastReset: now,
205
+ recipients: 0,
206
+ errors: 0,
207
+ authFailures: 0,
208
+ connections: 0
209
+ };
210
+ this.counters.set(key, counter);
211
+ }
212
+ // Check if counter needs to be reset
213
+ if (now - counter.lastReset >= 60000) {
214
+ counter.count = 0;
215
+ counter.lastReset = now;
216
+ }
217
+ // Check if limit is exceeded
218
+ if (counter.count >= limit) {
219
+ // Calculate reset time
220
+ const resetIn = 60000 - (now - counter.lastReset);
221
+ return {
222
+ allowed: false,
223
+ reason: 'Global message rate limit exceeded',
224
+ limit,
225
+ current: counter.count,
226
+ resetIn
227
+ };
228
+ }
229
+ // Increment counter
230
+ counter.count++;
231
+ // Update statistics
232
+ this.updateStats();
233
+ return { allowed: true };
234
+ }
235
+ /**
236
+ * Check pattern-specific message rate limit
237
+ * @param pattern Pattern to check
238
+ */
239
+ checkPatternMessageLimit(pattern) {
240
+ const now = Date.now();
241
+ // Get pattern-specific limit or use global
242
+ const patternConfig = this.config.patterns?.[pattern];
243
+ const limit = patternConfig?.maxMessagesPerMinute || this.config.global.maxMessagesPerMinute;
244
+ if (!limit) {
245
+ return { allowed: true };
246
+ }
247
+ // Get or create counter
248
+ let counter = this.patternCounters.get(pattern);
249
+ if (!counter) {
250
+ counter = {
251
+ count: 0,
252
+ lastReset: now,
253
+ recipients: 0,
254
+ errors: 0,
255
+ authFailures: 0,
256
+ connections: 0
257
+ };
258
+ this.patternCounters.set(pattern, counter);
259
+ // Initialize pattern stats if needed
260
+ if (!this.stats.byPattern[pattern]) {
261
+ this.stats.byPattern[pattern] = {
262
+ messagesPerMinute: 0,
263
+ totalMessages: 0,
264
+ totalBlocked: 0
265
+ };
266
+ }
267
+ }
268
+ // Check if counter needs to be reset
269
+ if (now - counter.lastReset >= 60000) {
270
+ counter.count = 0;
271
+ counter.lastReset = now;
272
+ }
273
+ // Check if limit is exceeded
274
+ if (counter.count >= limit) {
275
+ // Calculate reset time
276
+ const resetIn = 60000 - (now - counter.lastReset);
277
+ // Update statistics
278
+ this.stats.byPattern[pattern].totalBlocked++;
279
+ this.stats.totalBlocked++;
280
+ return {
281
+ allowed: false,
282
+ reason: `Pattern "${pattern}" message rate limit exceeded`,
283
+ limit,
284
+ current: counter.count,
285
+ resetIn
286
+ };
287
+ }
288
+ // Increment counter
289
+ counter.count++;
290
+ // Update statistics
291
+ this.stats.byPattern[pattern].messagesPerMinute = counter.count;
292
+ this.stats.byPattern[pattern].totalMessages++;
293
+ return { allowed: true };
294
+ }
295
+ /**
296
+ * Check domain-specific message rate limit
297
+ * @param domain Domain to check
298
+ */
299
+ checkDomainMessageLimit(domain) {
300
+ const now = Date.now();
301
+ // Get domain-specific limit or use global
302
+ const domainConfig = this.config.domains?.[domain];
303
+ const limit = domainConfig?.maxMessagesPerMinute || this.config.global.maxMessagesPerMinute;
304
+ if (!limit) {
305
+ return { allowed: true };
306
+ }
307
+ // Get or create counter
308
+ let counter = this.domainCounters.get(domain);
309
+ if (!counter) {
310
+ counter = {
311
+ count: 0,
312
+ lastReset: now,
313
+ recipients: 0,
314
+ errors: 0,
315
+ authFailures: 0,
316
+ connections: 0
317
+ };
318
+ this.domainCounters.set(domain, counter);
319
+ }
320
+ // Check if counter needs to be reset
321
+ if (now - counter.lastReset >= 60000) {
322
+ counter.count = 0;
323
+ counter.lastReset = now;
324
+ }
325
+ // Check if limit is exceeded
326
+ if (counter.count >= limit) {
327
+ // Calculate reset time
328
+ const resetIn = 60000 - (now - counter.lastReset);
329
+ logger.log('warn', `Domain ${domain} rate limit exceeded: ${counter.count}/${limit} messages per minute`);
330
+ return {
331
+ allowed: false,
332
+ reason: `Domain "${domain}" message rate limit exceeded`,
333
+ limit,
334
+ current: counter.count,
335
+ resetIn
336
+ };
337
+ }
338
+ // Increment counter
339
+ counter.count++;
340
+ return { allowed: true };
341
+ }
342
+ /**
343
+ * Check IP-specific message rate limit
344
+ * @param ip IP address
345
+ */
346
+ checkIpMessageLimit(ip) {
347
+ const now = Date.now();
348
+ // Get IP-specific limit or use global
349
+ const ipConfig = this.config.ips?.[ip];
350
+ const limit = ipConfig?.maxMessagesPerMinute || this.config.global.maxMessagesPerMinute;
351
+ if (!limit) {
352
+ return { allowed: true };
353
+ }
354
+ // Get or create counter
355
+ let counter = this.ipCounters.get(ip);
356
+ if (!counter) {
357
+ counter = {
358
+ count: 0,
359
+ lastReset: now,
360
+ recipients: 0,
361
+ errors: 0,
362
+ authFailures: 0,
363
+ connections: 0
364
+ };
365
+ this.ipCounters.set(ip, counter);
366
+ // Initialize IP stats if needed
367
+ if (!this.stats.byIp[ip]) {
368
+ this.stats.byIp[ip] = {
369
+ messagesPerMinute: 0,
370
+ totalMessages: 0,
371
+ totalBlocked: 0,
372
+ connections: 0,
373
+ errors: 0,
374
+ authFailures: 0,
375
+ blocked: false
376
+ };
377
+ }
378
+ }
379
+ // Check if counter needs to be reset
380
+ if (now - counter.lastReset >= 60000) {
381
+ counter.count = 0;
382
+ counter.lastReset = now;
383
+ }
384
+ // Check if limit is exceeded
385
+ if (counter.count >= limit) {
386
+ // Calculate reset time
387
+ const resetIn = 60000 - (now - counter.lastReset);
388
+ // Update statistics
389
+ this.stats.byIp[ip].totalBlocked++;
390
+ this.stats.totalBlocked++;
391
+ return {
392
+ allowed: false,
393
+ reason: `IP ${ip} message rate limit exceeded`,
394
+ limit,
395
+ current: counter.count,
396
+ resetIn
397
+ };
398
+ }
399
+ // Increment counter
400
+ counter.count++;
401
+ // Update statistics
402
+ this.stats.byIp[ip].messagesPerMinute = counter.count;
403
+ this.stats.byIp[ip].totalMessages++;
404
+ return { allowed: true };
405
+ }
406
+ /**
407
+ * Check recipient limit
408
+ * @param email Email address
409
+ * @param recipients Number of recipients
410
+ * @param pattern Matched pattern
411
+ * @param domain Domain name
412
+ */
413
+ checkRecipientLimit(email, recipients, pattern, domain) {
414
+ // Get the most specific limit available
415
+ let limit = this.config.global.maxRecipientsPerMessage;
416
+ // Check pattern-specific limit
417
+ if (pattern && this.config.patterns?.[pattern]?.maxRecipientsPerMessage) {
418
+ limit = this.config.patterns[pattern].maxRecipientsPerMessage;
419
+ }
420
+ // Check domain-specific limit (overrides pattern if present)
421
+ if (domain && this.config.domains?.[domain]?.maxRecipientsPerMessage) {
422
+ limit = this.config.domains[domain].maxRecipientsPerMessage;
423
+ }
424
+ if (!limit) {
425
+ return { allowed: true };
426
+ }
427
+ // Check if limit is exceeded
428
+ if (recipients > limit) {
429
+ return {
430
+ allowed: false,
431
+ reason: 'Recipient limit exceeded',
432
+ limit,
433
+ current: recipients
434
+ };
435
+ }
436
+ return { allowed: true };
437
+ }
438
+ /**
439
+ * Record a connection from an IP
440
+ * @param ip IP address
441
+ * @returns Result of rate limit check
442
+ */
443
+ recordConnection(ip) {
444
+ const now = Date.now();
445
+ // Check if IP is blocked
446
+ if (this.isIpBlocked(ip)) {
447
+ return {
448
+ allowed: false,
449
+ reason: 'IP is blocked',
450
+ resetIn: this.getBlockReleaseTime(ip)
451
+ };
452
+ }
453
+ // Get IP-specific limit or use global
454
+ const ipConfig = this.config.ips?.[ip];
455
+ const limit = ipConfig?.maxConnectionsPerIP || this.config.global.maxConnectionsPerIP;
456
+ if (!limit) {
457
+ return { allowed: true };
458
+ }
459
+ // Get or create counter
460
+ let counter = this.ipCounters.get(ip);
461
+ if (!counter) {
462
+ counter = {
463
+ count: 0,
464
+ lastReset: now,
465
+ recipients: 0,
466
+ errors: 0,
467
+ authFailures: 0,
468
+ connections: 0
469
+ };
470
+ this.ipCounters.set(ip, counter);
471
+ // Initialize IP stats if needed
472
+ if (!this.stats.byIp[ip]) {
473
+ this.stats.byIp[ip] = {
474
+ messagesPerMinute: 0,
475
+ totalMessages: 0,
476
+ totalBlocked: 0,
477
+ connections: 0,
478
+ errors: 0,
479
+ authFailures: 0,
480
+ blocked: false
481
+ };
482
+ }
483
+ }
484
+ // Check if counter needs to be reset
485
+ if (now - counter.lastReset >= 60000) {
486
+ counter.connections = 0;
487
+ counter.lastReset = now;
488
+ }
489
+ // Check if limit is exceeded
490
+ if (counter.connections >= limit) {
491
+ // Calculate reset time
492
+ const resetIn = 60000 - (now - counter.lastReset);
493
+ // Update statistics
494
+ this.stats.byIp[ip].totalBlocked++;
495
+ this.stats.totalBlocked++;
496
+ return {
497
+ allowed: false,
498
+ reason: `IP ${ip} connection rate limit exceeded`,
499
+ limit,
500
+ current: counter.connections,
501
+ resetIn
502
+ };
503
+ }
504
+ // Increment counter
505
+ counter.connections++;
506
+ // Update statistics
507
+ this.stats.byIp[ip].connections = counter.connections;
508
+ return { allowed: true };
509
+ }
510
+ /**
511
+ * Record an error from an IP
512
+ * @param ip IP address
513
+ * @returns True if IP should be blocked
514
+ */
515
+ recordError(ip) {
516
+ const now = Date.now();
517
+ // Get IP-specific limit or use global
518
+ const ipConfig = this.config.ips?.[ip];
519
+ const limit = ipConfig?.maxErrorsPerIP || this.config.global.maxErrorsPerIP;
520
+ if (!limit) {
521
+ return false;
522
+ }
523
+ // Get or create counter
524
+ let counter = this.ipCounters.get(ip);
525
+ if (!counter) {
526
+ counter = {
527
+ count: 0,
528
+ lastReset: now,
529
+ recipients: 0,
530
+ errors: 0,
531
+ authFailures: 0,
532
+ connections: 0
533
+ };
534
+ this.ipCounters.set(ip, counter);
535
+ // Initialize IP stats if needed
536
+ if (!this.stats.byIp[ip]) {
537
+ this.stats.byIp[ip] = {
538
+ messagesPerMinute: 0,
539
+ totalMessages: 0,
540
+ totalBlocked: 0,
541
+ connections: 0,
542
+ errors: 0,
543
+ authFailures: 0,
544
+ blocked: false
545
+ };
546
+ }
547
+ }
548
+ // Check if counter needs to be reset
549
+ if (now - counter.lastReset >= 60000) {
550
+ counter.errors = 0;
551
+ counter.lastReset = now;
552
+ }
553
+ // Increment counter
554
+ counter.errors++;
555
+ // Update statistics
556
+ this.stats.byIp[ip].errors = counter.errors;
557
+ // Check if limit is exceeded
558
+ if (counter.errors >= limit) {
559
+ // Block the IP
560
+ this.blockIp(ip);
561
+ logger.log('warn', `IP ${ip} blocked due to excessive errors (${counter.errors}/${limit})`);
562
+ SecurityLogger.getInstance().logEvent({
563
+ level: SecurityLogLevel.WARN,
564
+ type: SecurityEventType.RATE_LIMITING,
565
+ message: 'IP blocked due to excessive errors',
566
+ ipAddress: ip,
567
+ details: {
568
+ errors: counter.errors,
569
+ limit
570
+ },
571
+ success: false
572
+ });
573
+ return true;
574
+ }
575
+ return false;
576
+ }
577
+ /**
578
+ * Record an authentication failure from an IP
579
+ * @param ip IP address
580
+ * @returns True if IP should be blocked
581
+ */
582
+ recordAuthFailure(ip) {
583
+ const now = Date.now();
584
+ // Get IP-specific limit or use global
585
+ const ipConfig = this.config.ips?.[ip];
586
+ const limit = ipConfig?.maxAuthFailuresPerIP || this.config.global.maxAuthFailuresPerIP;
587
+ if (!limit) {
588
+ return false;
589
+ }
590
+ // Get or create counter
591
+ let counter = this.ipCounters.get(ip);
592
+ if (!counter) {
593
+ counter = {
594
+ count: 0,
595
+ lastReset: now,
596
+ recipients: 0,
597
+ errors: 0,
598
+ authFailures: 0,
599
+ connections: 0
600
+ };
601
+ this.ipCounters.set(ip, counter);
602
+ // Initialize IP stats if needed
603
+ if (!this.stats.byIp[ip]) {
604
+ this.stats.byIp[ip] = {
605
+ messagesPerMinute: 0,
606
+ totalMessages: 0,
607
+ totalBlocked: 0,
608
+ connections: 0,
609
+ errors: 0,
610
+ authFailures: 0,
611
+ blocked: false
612
+ };
613
+ }
614
+ }
615
+ // Check if counter needs to be reset
616
+ if (now - counter.lastReset >= 60000) {
617
+ counter.authFailures = 0;
618
+ counter.lastReset = now;
619
+ }
620
+ // Increment counter
621
+ counter.authFailures++;
622
+ // Update statistics
623
+ this.stats.byIp[ip].authFailures = counter.authFailures;
624
+ // Check if limit is exceeded
625
+ if (counter.authFailures >= limit) {
626
+ // Block the IP
627
+ this.blockIp(ip);
628
+ logger.log('warn', `IP ${ip} blocked due to excessive authentication failures (${counter.authFailures}/${limit})`);
629
+ SecurityLogger.getInstance().logEvent({
630
+ level: SecurityLogLevel.WARN,
631
+ type: SecurityEventType.AUTHENTICATION,
632
+ message: 'IP blocked due to excessive authentication failures',
633
+ ipAddress: ip,
634
+ details: {
635
+ authFailures: counter.authFailures,
636
+ limit
637
+ },
638
+ success: false
639
+ });
640
+ return true;
641
+ }
642
+ return false;
643
+ }
644
+ /**
645
+ * Block an IP address
646
+ * @param ip IP address to block
647
+ * @param duration Override the default block duration (milliseconds)
648
+ */
649
+ blockIp(ip, duration) {
650
+ if (!this.config.blocks) {
651
+ this.config.blocks = {};
652
+ }
653
+ // Set block expiry time
654
+ const expiry = Date.now() + (duration || this.config.global.blockDuration || 3600000);
655
+ this.config.blocks[ip] = expiry;
656
+ // Update statistics
657
+ if (!this.stats.byIp[ip]) {
658
+ this.stats.byIp[ip] = {
659
+ messagesPerMinute: 0,
660
+ totalMessages: 0,
661
+ totalBlocked: 0,
662
+ connections: 0,
663
+ errors: 0,
664
+ authFailures: 0,
665
+ blocked: false
666
+ };
667
+ }
668
+ this.stats.byIp[ip].blocked = true;
669
+ this.stats.currentlyBlocked++;
670
+ // Emit event
671
+ this.emit('ipBlocked', {
672
+ ip,
673
+ expiry,
674
+ duration: duration || this.config.global.blockDuration
675
+ });
676
+ logger.log('warn', `IP ${ip} blocked until ${new Date(expiry).toISOString()}`);
677
+ }
678
+ /**
679
+ * Unblock an IP address
680
+ * @param ip IP address to unblock
681
+ */
682
+ unblockIp(ip) {
683
+ if (!this.config.blocks) {
684
+ return;
685
+ }
686
+ // Remove block
687
+ delete this.config.blocks[ip];
688
+ // Update statistics
689
+ if (this.stats.byIp[ip]) {
690
+ this.stats.byIp[ip].blocked = false;
691
+ this.stats.currentlyBlocked--;
692
+ }
693
+ // Emit event
694
+ this.emit('ipUnblocked', { ip });
695
+ logger.log('info', `IP ${ip} unblocked`);
696
+ }
697
+ /**
698
+ * Check if an IP is blocked
699
+ * @param ip IP address to check
700
+ */
701
+ isIpBlocked(ip) {
702
+ if (!this.config.blocks) {
703
+ return false;
704
+ }
705
+ // Check if IP is in blocks
706
+ if (!(ip in this.config.blocks)) {
707
+ return false;
708
+ }
709
+ // Check if block has expired
710
+ const expiry = this.config.blocks[ip];
711
+ if (expiry <= Date.now()) {
712
+ // Remove expired block
713
+ delete this.config.blocks[ip];
714
+ // Update statistics
715
+ if (this.stats.byIp[ip]) {
716
+ this.stats.byIp[ip].blocked = false;
717
+ this.stats.currentlyBlocked--;
718
+ }
719
+ return false;
720
+ }
721
+ return true;
722
+ }
723
+ /**
724
+ * Get the time until a block is released
725
+ * @param ip IP address
726
+ * @returns Milliseconds until release or 0 if not blocked
727
+ */
728
+ getBlockReleaseTime(ip) {
729
+ if (!this.config.blocks || !(ip in this.config.blocks)) {
730
+ return 0;
731
+ }
732
+ const expiry = this.config.blocks[ip];
733
+ const now = Date.now();
734
+ return expiry > now ? expiry - now : 0;
735
+ }
736
+ /**
737
+ * Update rate limiter statistics
738
+ */
739
+ updateStats() {
740
+ // Update active counters count
741
+ this.stats.activeCounters = this.counters.size + this.patternCounters.size + this.ipCounters.size;
742
+ // Emit statistics update
743
+ this.emit('statsUpdated', this.stats);
744
+ }
745
+ /**
746
+ * Get rate limiter statistics
747
+ */
748
+ getStats() {
749
+ return { ...this.stats };
750
+ }
751
+ /**
752
+ * Update rate limiter configuration
753
+ * @param config New configuration
754
+ */
755
+ updateConfig(config) {
756
+ if (config.global) {
757
+ this.config.global = {
758
+ ...this.config.global,
759
+ ...config.global
760
+ };
761
+ }
762
+ if (config.patterns) {
763
+ this.config.patterns = {
764
+ ...this.config.patterns,
765
+ ...config.patterns
766
+ };
767
+ }
768
+ if (config.ips) {
769
+ this.config.ips = {
770
+ ...this.config.ips,
771
+ ...config.ips
772
+ };
773
+ }
774
+ logger.log('info', 'Rate limiter configuration updated');
775
+ }
776
+ /**
777
+ * Get configuration for debugging
778
+ */
779
+ getConfig() {
780
+ return { ...this.config };
781
+ }
782
+ /**
783
+ * Apply domain-specific rate limits
784
+ * Merges domain limits with existing configuration
785
+ * @param domain Domain name
786
+ * @param limits Rate limit configuration for the domain
787
+ */
788
+ applyDomainLimits(domain, limits) {
789
+ if (!this.config.domains) {
790
+ this.config.domains = {};
791
+ }
792
+ // Merge the limits with any existing domain config
793
+ this.config.domains[domain] = {
794
+ ...this.config.domains[domain],
795
+ ...limits
796
+ };
797
+ logger.log('info', `Applied rate limits for domain ${domain}:`, limits);
798
+ }
799
+ /**
800
+ * Remove domain-specific rate limits
801
+ * @param domain Domain name
802
+ */
803
+ removeDomainLimits(domain) {
804
+ if (this.config.domains && this.config.domains[domain]) {
805
+ delete this.config.domains[domain];
806
+ // Also remove the counter
807
+ this.domainCounters.delete(domain);
808
+ logger.log('info', `Removed rate limits for domain ${domain}`);
809
+ }
810
+ }
811
+ /**
812
+ * Get domain-specific rate limits
813
+ * @param domain Domain name
814
+ * @returns Domain rate limit config or undefined
815
+ */
816
+ getDomainLimits(domain) {
817
+ return this.config.domains?.[domain];
818
+ }
819
+ }
820
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy51bmlmaWVkLnJhdGUubGltaXRlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3RzL21haWwvZGVsaXZlcnkvY2xhc3Nlcy51bmlmaWVkLnJhdGUubGltaXRlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLGtCQUFrQixDQUFDO0FBQzVDLE9BQU8sRUFBRSxZQUFZLEVBQUUsTUFBTSxhQUFhLENBQUM7QUFDM0MsT0FBTyxFQUFFLE1BQU0sRUFBRSxNQUFNLGlCQUFpQixDQUFDO0FBQ3pDLE9BQU8sRUFBRSxjQUFjLEVBQUUsZ0JBQWdCLEVBQUUsaUJBQWlCLEVBQUUsTUFBTSx5QkFBeUIsQ0FBQztBQWdGOUY7O0dBRUc7QUFDSCxNQUFNLE9BQU8sa0JBQW1CLFNBQVEsWUFBWTtJQUMxQyxNQUFNLENBQTBCO0lBQ2hDLFFBQVEsR0FBK0IsSUFBSSxHQUFHLEVBQUUsQ0FBQztJQUNqRCxlQUFlLEdBQStCLElBQUksR0FBRyxFQUFFLENBQUM7SUFDeEQsVUFBVSxHQUErQixJQUFJLEdBQUcsRUFBRSxDQUFDO0lBQ25ELGNBQWMsR0FBK0IsSUFBSSxHQUFHLEVBQUUsQ0FBQztJQUN2RCxlQUFlLENBQWtCO0lBQ2pDLEtBQUssQ0FBb0I7SUFFakM7OztPQUdHO0lBQ0gsWUFBWSxNQUErQjtRQUN6QyxLQUFLLEVBQUUsQ0FBQztRQUVSLDRCQUE0QjtRQUM1QixJQUFJLENBQUMsTUFBTSxHQUFHO1lBQ1osTUFBTSxFQUFFO2dCQUNOLG9CQUFvQixFQUFFLE1BQU0sQ0FBQyxNQUFNLENBQUMsb0JBQW9CLElBQUksR0FBRztnQkFDL0QsdUJBQXVCLEVBQUUsTUFBTSxDQUFDLE1BQU0sQ0FBQyx1QkFBdUIsSUFBSSxHQUFHO2dCQUNyRSxtQkFBbUIsRUFBRSxNQUFNLENBQUMsTUFBTSxDQUFDLG1CQUFtQixJQUFJLEVBQUU7Z0JBQzVELGNBQWMsRUFBRSxNQUFNLENBQUMsTUFBTSxD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