@serve.zone/dcrouter 11.23.5 → 12.0.0

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 (119) hide show
  1. package/dist_serve/bundle.js +1 -1
  2. package/dist_ts/00_commitinfo_data.js +2 -2
  3. package/dist_ts/classes.cert-provision-scheduler.d.ts +6 -8
  4. package/dist_ts/classes.cert-provision-scheduler.js +37 -17
  5. package/dist_ts/classes.dcrouter.d.ts +15 -29
  6. package/dist_ts/classes.dcrouter.js +96 -91
  7. package/dist_ts/classes.storage-cert-manager.d.ts +3 -6
  8. package/dist_ts/classes.storage-cert-manager.js +35 -25
  9. package/dist_ts/config/classes.api-token-manager.d.ts +1 -3
  10. package/dist_ts/config/classes.api-token-manager.js +45 -15
  11. package/dist_ts/config/classes.route-config-manager.d.ts +1 -3
  12. package/dist_ts/config/classes.route-config-manager.js +62 -24
  13. package/dist_ts/{cache → db}/classes.cache.cleaner.d.ts +3 -3
  14. package/dist_ts/db/classes.cache.cleaner.js +130 -0
  15. package/dist_ts/{cache → db}/classes.cached.document.js +1 -1
  16. package/dist_ts/db/classes.dcrouter-db.d.ts +70 -0
  17. package/dist_ts/db/classes.dcrouter-db.js +146 -0
  18. package/dist_ts/db/documents/classes.accounting-session.doc.d.ts +32 -0
  19. package/dist_ts/db/documents/classes.accounting-session.doc.js +214 -0
  20. package/dist_ts/db/documents/classes.acme-cert.doc.d.ts +13 -0
  21. package/dist_ts/db/documents/classes.acme-cert.doc.js +109 -0
  22. package/dist_ts/db/documents/classes.api-token.doc.d.ts +18 -0
  23. package/dist_ts/db/documents/classes.api-token.doc.js +127 -0
  24. package/dist_ts/{cache → db}/documents/classes.cached.email.js +3 -3
  25. package/dist_ts/{cache → db}/documents/classes.cached.ip.reputation.js +3 -3
  26. package/dist_ts/db/documents/classes.cert-backoff.doc.d.ts +11 -0
  27. package/dist_ts/db/documents/classes.cert-backoff.doc.js +97 -0
  28. package/dist_ts/db/documents/classes.proxy-cert.doc.d.ts +12 -0
  29. package/dist_ts/db/documents/classes.proxy-cert.doc.js +103 -0
  30. package/dist_ts/db/documents/classes.remote-ingress-edge.doc.d.ts +17 -0
  31. package/dist_ts/db/documents/classes.remote-ingress-edge.doc.js +130 -0
  32. package/dist_ts/db/documents/classes.route-override.doc.d.ts +10 -0
  33. package/dist_ts/db/documents/classes.route-override.doc.js +91 -0
  34. package/dist_ts/db/documents/classes.stored-route.doc.d.ts +12 -0
  35. package/dist_ts/db/documents/classes.stored-route.doc.js +103 -0
  36. package/dist_ts/db/documents/classes.vlan-mappings.doc.d.ts +15 -0
  37. package/dist_ts/db/documents/classes.vlan-mappings.doc.js +77 -0
  38. package/dist_ts/db/documents/classes.vpn-client.doc.d.ts +18 -0
  39. package/dist_ts/db/documents/classes.vpn-client.doc.js +136 -0
  40. package/dist_ts/db/documents/classes.vpn-server-keys.doc.d.ts +10 -0
  41. package/dist_ts/db/documents/classes.vpn-server-keys.doc.js +94 -0
  42. package/dist_ts/db/documents/index.d.ts +13 -0
  43. package/dist_ts/db/documents/index.js +20 -0
  44. package/dist_ts/{cache → db}/index.d.ts +1 -1
  45. package/dist_ts/db/index.js +9 -0
  46. package/dist_ts/opsserver/handlers/certificate.handler.js +66 -66
  47. package/dist_ts/opsserver/handlers/config.handler.js +14 -15
  48. package/dist_ts/paths.d.ts +0 -1
  49. package/dist_ts/paths.js +1 -2
  50. package/dist_ts/radius/classes.accounting.manager.d.ts +4 -12
  51. package/dist_ts/radius/classes.accounting.manager.js +80 -93
  52. package/dist_ts/radius/classes.radius.server.d.ts +1 -3
  53. package/dist_ts/radius/classes.radius.server.js +4 -6
  54. package/dist_ts/radius/classes.vlan.manager.d.ts +3 -7
  55. package/dist_ts/radius/classes.vlan.manager.js +21 -28
  56. package/dist_ts/radius/index.d.ts +1 -1
  57. package/dist_ts/radius/index.js +1 -1
  58. package/dist_ts/remoteingress/classes.remoteingress-manager.d.ts +3 -5
  59. package/dist_ts/remoteingress/classes.remoteingress-manager.js +41 -21
  60. package/dist_ts/security/classes.ipreputationchecker.d.ts +6 -21
  61. package/dist_ts/security/classes.ipreputationchecker.js +59 -138
  62. package/dist_ts/vpn/classes.vpn-manager.d.ts +4 -22
  63. package/dist_ts/vpn/classes.vpn-manager.js +40 -45
  64. package/dist_ts_oci_container/index.js +4 -4
  65. package/dist_ts_web/00_commitinfo_data.js +2 -2
  66. package/package.json +1 -1
  67. package/readme.storage.md +55 -91
  68. package/ts/00_commitinfo_data.ts +1 -1
  69. package/ts/classes.cert-provision-scheduler.ts +35 -17
  70. package/ts/classes.dcrouter.ts +113 -125
  71. package/ts/classes.storage-cert-manager.ts +34 -22
  72. package/ts/config/classes.api-token-manager.ts +42 -11
  73. package/ts/config/classes.route-config-manager.ts +56 -21
  74. package/ts/{cache → db}/classes.cache.cleaner.ts +6 -6
  75. package/ts/db/classes.dcrouter-db.ts +179 -0
  76. package/ts/db/documents/classes.accounting-session.doc.ts +106 -0
  77. package/ts/db/documents/classes.acme-cert.doc.ts +41 -0
  78. package/ts/db/documents/classes.api-token.doc.ts +56 -0
  79. package/ts/{cache → db}/documents/classes.cached.email.ts +2 -2
  80. package/ts/{cache → db}/documents/classes.cached.ip.reputation.ts +2 -2
  81. package/ts/db/documents/classes.cert-backoff.doc.ts +35 -0
  82. package/ts/db/documents/classes.proxy-cert.doc.ts +38 -0
  83. package/ts/db/documents/classes.remote-ingress-edge.doc.ts +54 -0
  84. package/ts/db/documents/classes.route-override.doc.ts +32 -0
  85. package/ts/db/documents/classes.stored-route.doc.ts +38 -0
  86. package/ts/db/documents/classes.vlan-mappings.doc.ts +32 -0
  87. package/ts/db/documents/classes.vpn-client.doc.ts +57 -0
  88. package/ts/db/documents/classes.vpn-server-keys.doc.ts +31 -0
  89. package/ts/db/documents/index.ts +24 -0
  90. package/ts/{cache → db}/index.ts +6 -2
  91. package/ts/opsserver/handlers/certificate.handler.ts +67 -65
  92. package/ts/opsserver/handlers/config.handler.ts +13 -14
  93. package/ts/paths.ts +0 -1
  94. package/ts/radius/classes.accounting.manager.ts +81 -103
  95. package/ts/radius/classes.radius.server.ts +3 -6
  96. package/ts/radius/classes.vlan.manager.ts +20 -32
  97. package/ts/radius/index.ts +1 -1
  98. package/ts/remoteingress/classes.remoteingress-manager.ts +40 -22
  99. package/ts/security/classes.ipreputationchecker.ts +103 -196
  100. package/ts/vpn/classes.vpn-manager.ts +44 -75
  101. package/ts_web/00_commitinfo_data.ts +1 -1
  102. package/dist_ts/cache/classes.cache.cleaner.js +0 -130
  103. package/dist_ts/cache/classes.cachedb.d.ts +0 -60
  104. package/dist_ts/cache/classes.cachedb.js +0 -126
  105. package/dist_ts/cache/documents/index.d.ts +0 -2
  106. package/dist_ts/cache/documents/index.js +0 -3
  107. package/dist_ts/cache/index.js +0 -7
  108. package/dist_ts/storage/classes.storagemanager.d.ts +0 -83
  109. package/dist_ts/storage/classes.storagemanager.js +0 -348
  110. package/dist_ts/storage/index.d.ts +0 -1
  111. package/dist_ts/storage/index.js +0 -3
  112. package/ts/cache/classes.cachedb.ts +0 -155
  113. package/ts/cache/documents/index.ts +0 -2
  114. package/ts/storage/classes.storagemanager.ts +0 -404
  115. package/ts/storage/index.ts +0 -2
  116. /package/dist_ts/{cache → db}/classes.cached.document.d.ts +0 -0
  117. /package/dist_ts/{cache → db}/documents/classes.cached.email.d.ts +0 -0
  118. /package/dist_ts/{cache → db}/documents/classes.cached.ip.reputation.d.ts +0 -0
  119. /package/ts/{cache → db}/classes.cached.document.ts +0 -0
@@ -1,8 +1,8 @@
1
1
  import * as plugins from '../plugins.js';
2
- import * as paths from '../paths.js';
3
2
  import { logger } from '../logger.js';
4
3
  import { SecurityLogger, SecurityLogLevel, SecurityEventType } from './classes.securitylogger.js';
5
4
  import { LRUCache } from 'lru-cache';
5
+ import { CachedIPReputation } from '../db/documents/classes.cached.ip.reputation.js';
6
6
 
7
7
  /**
8
8
  * Reputation check result information
@@ -52,7 +52,7 @@ export interface IIPReputationOptions {
52
52
  highRiskThreshold?: number; // Score below this is high risk
53
53
  mediumRiskThreshold?: number; // Score below this is medium risk
54
54
  lowRiskThreshold?: number; // Score below this is low risk
55
- enableLocalCache?: boolean; // Whether to persist cache to disk (default: true)
55
+ enableLocalCache?: boolean; // Whether to persist cache to database (default: true)
56
56
  enableDNSBL?: boolean; // Whether to use DNSBL checks (default: true)
57
57
  enableIPInfo?: boolean; // Whether to use IP info service (default: true)
58
58
  }
@@ -64,10 +64,7 @@ export class IPReputationChecker {
64
64
  private static instance: IPReputationChecker | undefined;
65
65
  private reputationCache: LRUCache<string, IReputationResult>;
66
66
  private options: Required<IIPReputationOptions>;
67
- private storageManager?: any; // StorageManager instance
68
- private saveCacheTimer: ReturnType<typeof setTimeout> | null = null;
69
- private static readonly SAVE_CACHE_DEBOUNCE_MS = 30_000;
70
-
67
+
71
68
  // Default DNSBL servers
72
69
  private static readonly DEFAULT_DNSBL_SERVERS = [
73
70
  'zen.spamhaus.org', // Spamhaus
@@ -75,13 +72,13 @@ export class IPReputationChecker {
75
72
  'b.barracudacentral.org', // Barracuda
76
73
  'spam.dnsbl.sorbs.net', // SORBS
77
74
  'dnsbl.sorbs.net', // SORBS (expanded)
78
- 'cbl.abuseat.org', // Composite Blocking List
75
+ 'cbl.abuseat.org', // Composite Blocking List
79
76
  'xbl.spamhaus.org', // Spamhaus XBL
80
77
  'pbl.spamhaus.org', // Spamhaus PBL
81
78
  'dnsbl-1.uceprotect.net', // UCEPROTECT
82
79
  'psbl.surriel.com' // PSBL
83
80
  ];
84
-
81
+
85
82
  // Default options
86
83
  private static readonly DEFAULT_OPTIONS: Required<IIPReputationOptions> = {
87
84
  maxCacheSize: 10000,
@@ -94,54 +91,40 @@ export class IPReputationChecker {
94
91
  enableDNSBL: true,
95
92
  enableIPInfo: true
96
93
  };
97
-
94
+
98
95
  /**
99
96
  * Constructor for IPReputationChecker
100
97
  * @param options Configuration options
101
- * @param storageManager Optional StorageManager instance for persistence
102
98
  */
103
- constructor(options: IIPReputationOptions = {}, storageManager?: any) {
99
+ constructor(options: IIPReputationOptions = {}) {
104
100
  // Merge with default options
105
101
  this.options = {
106
102
  ...IPReputationChecker.DEFAULT_OPTIONS,
107
103
  ...options
108
104
  };
109
-
110
- this.storageManager = storageManager;
111
-
112
- // If no storage manager provided, log warning
113
- if (!storageManager && this.options.enableLocalCache) {
114
- logger.log('warn',
115
- '⚠️ WARNING: IPReputationChecker initialized without StorageManager.\n' +
116
- ' IP reputation cache will only be stored to filesystem.\n' +
117
- ' Consider passing a StorageManager instance for better storage flexibility.'
118
- );
119
- }
120
-
105
+
121
106
  // Initialize reputation cache
122
107
  this.reputationCache = new LRUCache<string, IReputationResult>({
123
108
  max: this.options.maxCacheSize,
124
109
  ttl: this.options.cacheTTL, // Cache TTL
125
110
  });
126
-
127
- // Load cache from disk if enabled
111
+
112
+ // Load persisted reputations into in-memory cache
128
113
  if (this.options.enableLocalCache) {
129
- // Fire and forget the load operation
130
- this.loadCache().catch((error: unknown) => {
114
+ this.loadCacheFromDb().catch((error: unknown) => {
131
115
  logger.log('error', `Failed to load IP reputation cache during initialization: ${(error as Error).message}`);
132
116
  });
133
117
  }
134
118
  }
135
-
119
+
136
120
  /**
137
121
  * Get the singleton instance of the checker
138
122
  * @param options Configuration options
139
- * @param storageManager Optional StorageManager instance for persistence
140
123
  * @returns Singleton instance
141
124
  */
142
- public static getInstance(options: IIPReputationOptions = {}, storageManager?: any): IPReputationChecker {
125
+ public static getInstance(options: IIPReputationOptions = {}): IPReputationChecker {
143
126
  if (!IPReputationChecker.instance) {
144
- IPReputationChecker.instance = new IPReputationChecker(options, storageManager);
127
+ IPReputationChecker.instance = new IPReputationChecker(options);
145
128
  }
146
129
  return IPReputationChecker.instance;
147
130
  }
@@ -150,12 +133,6 @@ export class IPReputationChecker {
150
133
  * Reset the singleton instance (for shutdown/testing)
151
134
  */
152
135
  public static resetInstance(): void {
153
- if (IPReputationChecker.instance) {
154
- if (IPReputationChecker.instance.saveCacheTimer) {
155
- clearTimeout(IPReputationChecker.instance.saveCacheTimer);
156
- IPReputationChecker.instance.saveCacheTimer = null;
157
- }
158
- }
159
136
  IPReputationChecker.instance = undefined;
160
137
  }
161
138
 
@@ -171,8 +148,8 @@ export class IPReputationChecker {
171
148
  logger.log('warn', `Invalid IP address format: ${ip}`);
172
149
  return this.createErrorResult(ip, 'Invalid IP address format');
173
150
  }
174
-
175
- // Check cache first
151
+
152
+ // Check in-memory LRU cache first (fast path)
176
153
  const cachedResult = this.reputationCache.get(ip);
177
154
  if (cachedResult) {
178
155
  logger.log('info', `Using cached reputation data for IP ${ip}`, {
@@ -181,7 +158,7 @@ export class IPReputationChecker {
181
158
  });
182
159
  return cachedResult;
183
160
  }
184
-
161
+
185
162
  // Initialize empty result
186
163
  const result: IReputationResult = {
187
164
  score: 100, // Start with perfect score
@@ -191,51 +168,53 @@ export class IPReputationChecker {
191
168
  isVPN: false,
192
169
  timestamp: Date.now()
193
170
  };
194
-
171
+
195
172
  // Check IP against DNS blacklists if enabled
196
173
  if (this.options.enableDNSBL) {
197
174
  const dnsblResult = await this.checkDNSBL(ip);
198
-
175
+
199
176
  // Update result with DNSBL information
200
177
  result.score -= dnsblResult.listCount * 10; // Subtract 10 points per blacklist
201
178
  result.isSpam = dnsblResult.listCount > 0;
202
179
  result.blacklists = dnsblResult.lists;
203
180
  }
204
-
181
+
205
182
  // Get additional IP information if enabled
206
183
  if (this.options.enableIPInfo) {
207
184
  const ipInfo = await this.getIPInfo(ip);
208
-
185
+
209
186
  // Update result with IP info
210
187
  result.country = ipInfo.country;
211
188
  result.asn = ipInfo.asn;
212
189
  result.org = ipInfo.org;
213
-
190
+
214
191
  // Adjust score based on IP type
215
192
  if (ipInfo.type === IPType.PROXY || ipInfo.type === IPType.TOR || ipInfo.type === IPType.VPN) {
216
193
  result.score -= 30; // Subtract 30 points for proxies, Tor, VPNs
217
-
194
+
218
195
  // Set proxy flags
219
196
  result.isProxy = ipInfo.type === IPType.PROXY;
220
197
  result.isTor = ipInfo.type === IPType.TOR;
221
198
  result.isVPN = ipInfo.type === IPType.VPN;
222
199
  }
223
200
  }
224
-
201
+
225
202
  // Ensure score is between 0 and 100
226
203
  result.score = Math.max(0, Math.min(100, result.score));
227
-
228
- // Update cache with result
204
+
205
+ // Update in-memory LRU cache
229
206
  this.reputationCache.set(ip, result);
230
-
231
- // Schedule debounced cache save if enabled
207
+
208
+ // Persist to database if enabled (fire and forget)
232
209
  if (this.options.enableLocalCache) {
233
- this.debouncedSaveCache();
210
+ this.persistReputationToDb(ip, result).catch((error: unknown) => {
211
+ logger.log('error', `Failed to persist IP reputation for ${ip}: ${(error as Error).message}`);
212
+ });
234
213
  }
235
-
214
+
236
215
  // Log the reputation check
237
216
  this.logReputationCheck(ip, result);
238
-
217
+
239
218
  return result;
240
219
  } catch (error: unknown) {
241
220
  logger.log('error', `Error checking IP reputation for ${ip}: ${(error as Error).message}`, {
@@ -246,7 +225,7 @@ export class IPReputationChecker {
246
225
  return this.createErrorResult(ip, (error as Error).message);
247
226
  }
248
227
  }
249
-
228
+
250
229
  /**
251
230
  * Check an IP against DNS blacklists
252
231
  * @param ip IP address to check
@@ -259,7 +238,7 @@ export class IPReputationChecker {
259
238
  try {
260
239
  // Reverse the IP for DNSBL queries
261
240
  const reversedIP = this.reverseIP(ip);
262
-
241
+
263
242
  const results = await Promise.allSettled(
264
243
  this.options.dnsblServers.map(async (server) => {
265
244
  try {
@@ -274,14 +253,14 @@ export class IPReputationChecker {
274
253
  }
275
254
  })
276
255
  );
277
-
256
+
278
257
  // Extract successful lookups (listed in DNSBL)
279
258
  const lists = results
280
- .filter((result): result is PromiseFulfilledResult<string> =>
259
+ .filter((result): result is PromiseFulfilledResult<string> =>
281
260
  result.status === 'fulfilled' && result.value !== null
282
261
  )
283
262
  .map(result => result.value);
284
-
263
+
285
264
  return {
286
265
  listCount: lists.length,
287
266
  lists
@@ -294,7 +273,7 @@ export class IPReputationChecker {
294
273
  };
295
274
  }
296
275
  }
297
-
276
+
298
277
  /**
299
278
  * Get information about an IP address
300
279
  * @param ip IP address to check
@@ -309,16 +288,16 @@ export class IPReputationChecker {
309
288
  try {
310
289
  // In a real implementation, this would use an IP data service API
311
290
  // For this implementation, we'll use a simplified approach
312
-
291
+
313
292
  // Check if it's a known Tor exit node (simplified)
314
293
  const isTor = ip.startsWith('171.25.') || ip.startsWith('185.220.') || ip.startsWith('95.216.');
315
-
294
+
316
295
  // Check if it's a known VPN (simplified)
317
296
  const isVPN = ip.startsWith('185.156.') || ip.startsWith('37.120.');
318
-
297
+
319
298
  // Check if it's a known proxy (simplified)
320
299
  const isProxy = ip.startsWith('34.92.') || ip.startsWith('34.206.');
321
-
300
+
322
301
  // Determine IP type
323
302
  let type = IPType.UNKNOWN;
324
303
  if (isTor) {
@@ -341,7 +320,7 @@ export class IPReputationChecker {
341
320
  type = IPType.RESIDENTIAL;
342
321
  }
343
322
  }
344
-
323
+
345
324
  // Return the information
346
325
  return {
347
326
  country: this.determineCountry(ip), // Simplified, would use geolocation service
@@ -356,7 +335,7 @@ export class IPReputationChecker {
356
335
  };
357
336
  }
358
337
  }
359
-
338
+
360
339
  /**
361
340
  * Simplified method to determine country from IP
362
341
  * In a real implementation, this would use a geolocation database or service
@@ -371,7 +350,7 @@ export class IPReputationChecker {
371
350
  if (ip.startsWith('171.')) return 'DE';
372
351
  return 'XX'; // Unknown
373
352
  }
374
-
353
+
375
354
  /**
376
355
  * Simplified method to determine organization from IP
377
356
  * In a real implementation, this would use an IP-to-org database or service
@@ -387,7 +366,7 @@ export class IPReputationChecker {
387
366
  if (ip.startsWith('185.220.')) return 'Tor Exit Node';
388
367
  return 'Unknown';
389
368
  }
390
-
369
+
391
370
  /**
392
371
  * Reverse an IP address for DNSBL lookups (e.g., 1.2.3.4 -> 4.3.2.1)
393
372
  * @param ip IP address to reverse
@@ -396,7 +375,7 @@ export class IPReputationChecker {
396
375
  private reverseIP(ip: string): string {
397
376
  return ip.split('.').reverse().join('.');
398
377
  }
399
-
378
+
400
379
  /**
401
380
  * Create an error result for when reputation check fails
402
381
  * @param ip IP address
@@ -414,7 +393,7 @@ export class IPReputationChecker {
414
393
  error: errorMessage
415
394
  };
416
395
  }
417
-
396
+
418
397
  /**
419
398
  * Validate IP address format
420
399
  * @param ip IP address to validate
@@ -425,7 +404,7 @@ export class IPReputationChecker {
425
404
  const ipv4Pattern = /^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
426
405
  return ipv4Pattern.test(ip);
427
406
  }
428
-
407
+
429
408
  /**
430
409
  * Log reputation check to security logger
431
410
  * @param ip IP address
@@ -439,7 +418,7 @@ export class IPReputationChecker {
439
418
  } else if (result.score < this.options.mediumRiskThreshold) {
440
419
  logLevel = SecurityLogLevel.INFO;
441
420
  }
442
-
421
+
443
422
  // Log the check
444
423
  SecurityLogger.getInstance().logEvent({
445
424
  level: logLevel,
@@ -458,131 +437,76 @@ export class IPReputationChecker {
458
437
  success: !result.isSpam
459
438
  });
460
439
  }
461
-
462
- /**
463
- * Schedule a debounced cache save (at most once per SAVE_CACHE_DEBOUNCE_MS)
464
- */
465
- private debouncedSaveCache(): void {
466
- if (this.saveCacheTimer) {
467
- return; // already scheduled
468
- }
469
- this.saveCacheTimer = setTimeout(() => {
470
- this.saveCacheTimer = null;
471
- this.saveCache().catch((error: unknown) => {
472
- logger.log('error', `Failed to save IP reputation cache: ${(error as Error).message}`);
473
- });
474
- }, IPReputationChecker.SAVE_CACHE_DEBOUNCE_MS);
475
- }
476
440
 
477
441
  /**
478
- * Save cache to disk or storage manager
442
+ * Persist a single IP reputation result to the database via CachedIPReputation
479
443
  */
480
- private async saveCache(): Promise<void> {
444
+ private async persistReputationToDb(ip: string, result: IReputationResult): Promise<void> {
481
445
  try {
482
- // Convert cache entries to serializable array
483
- const entries = Array.from(this.reputationCache.entries()).map(([ip, data]) => ({
484
- ip,
485
- data
486
- }));
487
-
488
- // Only save if we have entries
489
- if (entries.length === 0) {
490
- return;
491
- }
492
-
493
- const cacheData = JSON.stringify(entries);
494
-
495
- // Save to storage manager if available
496
- if (this.storageManager) {
497
- await this.storageManager.set('/security/ip-reputation-cache.json', cacheData);
498
- logger.log('info', `Saved ${entries.length} IP reputation cache entries to StorageManager`);
446
+ const data = {
447
+ score: result.score,
448
+ isSpam: result.isSpam,
449
+ isProxy: result.isProxy,
450
+ isTor: result.isTor,
451
+ isVPN: result.isVPN,
452
+ country: result.country,
453
+ asn: result.asn,
454
+ org: result.org,
455
+ blacklists: result.blacklists,
456
+ };
457
+
458
+ const existing = await CachedIPReputation.findByIP(ip);
459
+ if (existing) {
460
+ existing.updateReputation(data);
461
+ await existing.save();
499
462
  } else {
500
- // Fall back to filesystem
501
- const cacheDir = plugins.path.join(paths.dataDir, 'security');
502
- plugins.fsUtils.ensureDirSync(cacheDir);
503
-
504
- const cacheFile = plugins.path.join(cacheDir, 'ip_reputation_cache.json');
505
- plugins.fsUtils.toFsSync(cacheData, cacheFile);
506
-
507
- logger.log('info', `Saved ${entries.length} IP reputation cache entries to disk`);
463
+ const doc = CachedIPReputation.fromReputationData(ip, data);
464
+ await doc.save();
508
465
  }
509
466
  } catch (error: unknown) {
510
- logger.log('error', `Failed to save IP reputation cache: ${(error as Error).message}`);
467
+ logger.log('error', `Failed to persist IP reputation for ${ip}: ${(error as Error).message}`);
511
468
  }
512
469
  }
513
470
 
514
471
  /**
515
- * Load cache from disk or storage manager
472
+ * Load persisted reputations from CachedIPReputation documents into the in-memory LRU cache
516
473
  */
517
- private async loadCache(): Promise<void> {
474
+ private async loadCacheFromDb(): Promise<void> {
518
475
  try {
519
- let cacheData: string | null = null;
520
- let fromFilesystem = false;
521
-
522
- // Try to load from storage manager first
523
- if (this.storageManager) {
524
- try {
525
- cacheData = await this.storageManager.get('/security/ip-reputation-cache.json');
526
-
527
- if (!cacheData) {
528
- // Check if data exists in filesystem and migrate it
529
- const cacheFile = plugins.path.join(paths.dataDir, 'security', 'ip_reputation_cache.json');
530
-
531
- if (plugins.fs.existsSync(cacheFile)) {
532
- logger.log('info', 'Migrating IP reputation cache from filesystem to StorageManager');
533
- cacheData = plugins.fs.readFileSync(cacheFile, 'utf8');
534
- fromFilesystem = true;
535
-
536
- // Migrate to storage manager
537
- await this.storageManager.set('/security/ip-reputation-cache.json', cacheData);
538
- logger.log('info', 'IP reputation cache migrated to StorageManager successfully');
539
-
540
- // Optionally delete the old file after successful migration
541
- try {
542
- plugins.fs.unlinkSync(cacheFile);
543
- logger.log('info', 'Old cache file removed after migration');
544
- } catch (deleteError) {
545
- logger.log('warn', `Could not delete old cache file: ${(deleteError as Error).message}`);
546
- }
547
- }
548
- }
549
- } catch (error: unknown) {
550
- logger.log('error', `Error loading from StorageManager: ${(error as Error).message}`);
551
- }
552
- } else {
553
- // No storage manager, load from filesystem
554
- const cacheFile = plugins.path.join(paths.dataDir, 'security', 'ip_reputation_cache.json');
555
-
556
- if (plugins.fs.existsSync(cacheFile)) {
557
- cacheData = plugins.fs.readFileSync(cacheFile, 'utf8');
558
- fromFilesystem = true;
476
+ const docs = await CachedIPReputation.getInstances({});
477
+ let loadedCount = 0;
478
+
479
+ for (const doc of docs) {
480
+ // Skip expired documents
481
+ if (doc.isExpired()) {
482
+ continue;
559
483
  }
484
+
485
+ const result: IReputationResult = {
486
+ score: doc.score,
487
+ isSpam: doc.isSpam,
488
+ isProxy: doc.isProxy,
489
+ isTor: doc.isTor,
490
+ isVPN: doc.isVPN,
491
+ country: doc.country || undefined,
492
+ asn: doc.asn || undefined,
493
+ org: doc.org || undefined,
494
+ blacklists: doc.blacklists || [],
495
+ timestamp: doc.lastAccessedAt?.getTime() ?? doc.createdAt?.getTime() ?? Date.now(),
496
+ };
497
+
498
+ this.reputationCache.set(doc.ipAddress, result);
499
+ loadedCount++;
560
500
  }
561
-
562
- // Parse and restore cache if data was found
563
- if (cacheData) {
564
- const entries = JSON.parse(cacheData);
565
-
566
- // Validate and filter entries
567
- const now = Date.now();
568
- const validEntries = entries.filter(entry => {
569
- const age = now - entry.data.timestamp;
570
- return age < this.options.cacheTTL; // Only load entries that haven't expired
571
- });
572
-
573
- // Restore cache
574
- for (const entry of validEntries) {
575
- this.reputationCache.set(entry.ip, entry.data);
576
- }
577
-
578
- const source = fromFilesystem ? 'disk' : 'StorageManager';
579
- logger.log('info', `Loaded ${validEntries.length} IP reputation cache entries from ${source}`);
501
+
502
+ if (loadedCount > 0) {
503
+ logger.log('info', `Loaded ${loadedCount} IP reputation cache entries from database`);
580
504
  }
581
505
  } catch (error: unknown) {
582
- logger.log('error', `Failed to load IP reputation cache: ${(error as Error).message}`);
506
+ logger.log('error', `Failed to load IP reputation cache from database: ${(error as Error).message}`);
583
507
  }
584
508
  }
585
-
509
+
586
510
  /**
587
511
  * Get the risk level for a reputation score
588
512
  * @param score Reputation score (0-100)
@@ -599,21 +523,4 @@ export class IPReputationChecker {
599
523
  return 'trusted';
600
524
  }
601
525
  }
602
-
603
- /**
604
- * Update the storage manager after instantiation
605
- * This is useful when the storage manager is not available at construction time
606
- * @param storageManager The StorageManager instance to use
607
- */
608
- public updateStorageManager(storageManager: any): void {
609
- this.storageManager = storageManager;
610
- logger.log('info', 'IPReputationChecker storage manager updated');
611
-
612
- // If cache is enabled and we have entries, save them to the new storage manager
613
- if (this.options.enableLocalCache && this.reputationCache.size > 0) {
614
- this.saveCache().catch((error: unknown) => {
615
- logger.log('error', `Failed to save cache to new storage manager: ${(error as Error).message}`);
616
- });
617
- }
618
- }
619
- }
526
+ }