@push.rocks/smartmta 6.5.2 → 8.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.
- package/changelog.md +40 -0
- package/dist_rust/mailer-bin_linux_amd64 +0 -0
- package/dist_rust/mailer-bin_linux_arm64 +0 -0
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/functions.errors.d.ts +3 -0
- package/dist_ts/functions.errors.js +8 -0
- package/dist_ts/index.d.ts +3 -0
- package/dist_ts/index.js +4 -1
- package/dist_ts/mail/core/classes.bouncemanager.d.ts +16 -12
- package/dist_ts/mail/core/classes.bouncemanager.js +146 -129
- package/dist_ts/mail/core/classes.email.js +15 -13
- package/dist_ts/mail/core/classes.emailvalidator.d.ts +3 -3
- package/dist_ts/mail/core/classes.emailvalidator.js +7 -5
- package/dist_ts/mail/core/classes.templatemanager.d.ts +10 -6
- package/dist_ts/mail/core/classes.templatemanager.js +35 -51
- package/dist_ts/mail/delivery/classes.delivery.queue.d.ts +92 -22
- package/dist_ts/mail/delivery/classes.delivery.queue.js +738 -151
- package/dist_ts/mail/delivery/classes.delivery.system.d.ts +13 -7
- package/dist_ts/mail/delivery/classes.delivery.system.js +458 -145
- package/dist_ts/mail/delivery/classes.unified.rate.limiter.js +9 -8
- package/dist_ts/mail/delivery/functions.safe-observers.d.ts +10 -0
- package/dist_ts/mail/delivery/functions.safe-observers.js +37 -0
- package/dist_ts/mail/delivery/interfaces.d.ts +21 -0
- package/dist_ts/mail/delivery/interfaces.js +1 -1
- package/dist_ts/mail/interfaces.storage.d.ts +37 -6
- package/dist_ts/mail/interfaces.storage.js +33 -3
- package/dist_ts/mail/routing/classes.dkim.manager.d.ts +10 -6
- package/dist_ts/mail/routing/classes.dkim.manager.js +47 -31
- package/dist_ts/mail/routing/classes.dns.manager.d.ts +7 -5
- package/dist_ts/mail/routing/classes.dns.manager.js +22 -11
- package/dist_ts/mail/routing/classes.email.action.executor.d.ts +2 -1
- package/dist_ts/mail/routing/classes.email.action.executor.js +45 -16
- package/dist_ts/mail/routing/classes.email.router.d.ts +5 -2
- package/dist_ts/mail/routing/classes.email.router.js +17 -12
- package/dist_ts/mail/routing/classes.unified.email.server.d.ts +12 -6
- package/dist_ts/mail/routing/classes.unified.email.server.js +69 -78
- package/dist_ts/mail/routing/interfaces.d.ts +0 -2
- package/dist_ts/mail/security/classes.dkimcreator.d.ts +22 -45
- package/dist_ts/mail/security/classes.dkimcreator.js +93 -296
- package/dist_ts/mail/security/classes.spfverifier.js +5 -3
- package/dist_ts/paths.d.ts +0 -12
- package/dist_ts/paths.js +3 -36
- package/dist_ts/plugins.d.ts +2 -5
- package/dist_ts/plugins.js +3 -6
- package/dist_ts/security/classes.contentscanner.js +14 -12
- package/dist_ts/security/classes.ipreputationchecker.d.ts +9 -6
- package/dist_ts/security/classes.ipreputationchecker.js +42 -93
- package/dist_ts/security/classes.rustsecuritybridge.d.ts +52 -4
- package/dist_ts/security/classes.rustsecuritybridge.js +201 -4
- package/dist_ts/security/classes.securitylogger.js +7 -5
- package/dist_ts/security/index.d.ts +1 -1
- package/dist_ts/security/index.js +2 -2
- package/package.json +8 -9
- package/readme.hints.md +4 -3
- package/readme.md +50 -18
- package/readme.plan.md +6 -0
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/functions.errors.ts +8 -0
- package/ts/index.ts +3 -0
- package/ts/mail/core/classes.bouncemanager.ts +188 -155
- package/ts/mail/core/classes.email.ts +20 -14
- package/ts/mail/core/classes.emailvalidator.ts +9 -7
- package/ts/mail/core/classes.templatemanager.ts +42 -57
- package/ts/mail/delivery/classes.delivery.queue.ts +996 -185
- package/ts/mail/delivery/classes.delivery.system.ts +583 -170
- package/ts/mail/delivery/classes.unified.rate.limiter.ts +9 -8
- package/ts/mail/delivery/functions.safe-observers.ts +45 -0
- package/ts/mail/delivery/interfaces.ts +27 -1
- package/ts/mail/interfaces.storage.ts +64 -10
- package/ts/mail/routing/classes.dkim.manager.ts +65 -40
- package/ts/mail/routing/classes.dns.manager.ts +39 -16
- package/ts/mail/routing/classes.email.action.executor.ts +64 -17
- package/ts/mail/routing/classes.email.router.ts +20 -13
- package/ts/mail/routing/classes.unified.email.server.ts +107 -86
- package/ts/mail/routing/interfaces.ts +0 -2
- package/ts/mail/security/classes.dkimcreator.ts +150 -355
- package/ts/mail/security/classes.spfverifier.ts +4 -2
- package/ts/paths.ts +2 -41
- package/ts/plugins.ts +1 -6
- package/ts/security/classes.contentscanner.ts +14 -12
- package/ts/security/classes.ipreputationchecker.ts +46 -99
- package/ts/security/classes.rustsecuritybridge.ts +272 -6
- package/ts/security/classes.securitylogger.ts +6 -4
- package/ts/security/index.ts +5 -1
package/ts/paths.ts
CHANGED
|
@@ -1,48 +1,9 @@
|
|
|
1
1
|
import * as plugins from './plugins.js';
|
|
2
2
|
|
|
3
|
-
//
|
|
4
|
-
|
|
3
|
+
// Code and packaged executable paths only. SmartMTA data is supplied through
|
|
4
|
+
// managed storage interfaces and never persisted directly to the filesystem.
|
|
5
5
|
export const packageDir = plugins.path.join(
|
|
6
6
|
plugins.smartpath.get.dirnameFromImportMetaUrl(import.meta.url),
|
|
7
7
|
'../'
|
|
8
8
|
);
|
|
9
9
|
export const distServe = plugins.path.join(packageDir, './dist_serve');
|
|
10
|
-
|
|
11
|
-
// Configure data directory with environment variable or default to .nogit/data
|
|
12
|
-
const DEFAULT_DATA_PATH = '.nogit/data';
|
|
13
|
-
export const dataDir = process.env.DATA_DIR
|
|
14
|
-
? process.env.DATA_DIR
|
|
15
|
-
: plugins.path.join(baseDir, DEFAULT_DATA_PATH);
|
|
16
|
-
|
|
17
|
-
// MTA directories
|
|
18
|
-
export const keysDir = plugins.path.join(dataDir, 'keys');
|
|
19
|
-
export const dnsRecordsDir = plugins.path.join(dataDir, 'dns');
|
|
20
|
-
export const sentEmailsDir = plugins.path.join(dataDir, 'emails', 'sent');
|
|
21
|
-
export const receivedEmailsDir = plugins.path.join(dataDir, 'emails', 'received');
|
|
22
|
-
export const failedEmailsDir = plugins.path.join(dataDir, 'emails', 'failed'); // For failed emails
|
|
23
|
-
export const logsDir = plugins.path.join(dataDir, 'logs'); // For logs
|
|
24
|
-
|
|
25
|
-
// Email template directories
|
|
26
|
-
export const emailTemplatesDir = plugins.path.join(dataDir, 'templates', 'email');
|
|
27
|
-
export const MtaAttachmentsDir = plugins.path.join(dataDir, 'attachments'); // For email attachments
|
|
28
|
-
|
|
29
|
-
// Configuration path
|
|
30
|
-
export const configPath = process.env.CONFIG_PATH
|
|
31
|
-
? process.env.CONFIG_PATH
|
|
32
|
-
: plugins.path.join(baseDir, 'config.json');
|
|
33
|
-
|
|
34
|
-
// Create directories if they don't exist
|
|
35
|
-
export async function ensureDirectories() {
|
|
36
|
-
// Ensure data directories
|
|
37
|
-
await plugins.smartfs.directory(dataDir).recursive().create();
|
|
38
|
-
await plugins.smartfs.directory(keysDir).recursive().create();
|
|
39
|
-
await plugins.smartfs.directory(dnsRecordsDir).recursive().create();
|
|
40
|
-
await plugins.smartfs.directory(sentEmailsDir).recursive().create();
|
|
41
|
-
await plugins.smartfs.directory(receivedEmailsDir).recursive().create();
|
|
42
|
-
await plugins.smartfs.directory(failedEmailsDir).recursive().create();
|
|
43
|
-
await plugins.smartfs.directory(logsDir).recursive().create();
|
|
44
|
-
|
|
45
|
-
// Ensure email template directories
|
|
46
|
-
await plugins.smartfs.directory(emailTemplatesDir).recursive().create();
|
|
47
|
-
await plugins.smartfs.directory(MtaAttachmentsDir).recursive().create();
|
|
48
|
-
}
|
package/ts/plugins.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
// node native
|
|
2
2
|
import * as dns from 'dns';
|
|
3
|
-
import * as fs from 'fs';
|
|
4
3
|
import * as crypto from 'crypto';
|
|
5
4
|
import * as http from 'http';
|
|
6
5
|
import * as net from 'net';
|
|
@@ -11,7 +10,6 @@ import * as util from 'util';
|
|
|
11
10
|
|
|
12
11
|
export {
|
|
13
12
|
dns,
|
|
14
|
-
fs,
|
|
15
13
|
crypto,
|
|
16
14
|
http,
|
|
17
15
|
net,
|
|
@@ -23,15 +21,12 @@ export {
|
|
|
23
21
|
|
|
24
22
|
// @push.rocks scope
|
|
25
23
|
import * as smartfile from '@push.rocks/smartfile';
|
|
26
|
-
import { SmartFs, SmartFsProviderNode } from '@push.rocks/smartfs';
|
|
27
24
|
import * as smartlog from '@push.rocks/smartlog';
|
|
28
25
|
import * as smartmail from '@push.rocks/smartmail';
|
|
29
26
|
import * as smartpath from '@push.rocks/smartpath';
|
|
30
27
|
import * as smartrust from '@push.rocks/smartrust';
|
|
31
28
|
|
|
32
|
-
export
|
|
33
|
-
|
|
34
|
-
export { smartfile, SmartFs, smartlog, smartmail, smartpath, smartrust };
|
|
29
|
+
export { smartfile, smartlog, smartmail, smartpath, smartrust };
|
|
35
30
|
|
|
36
31
|
// Define SmartLog types for use in error handling
|
|
37
32
|
export type TLogLevel = 'error' | 'warn' | 'info' | 'success' | 'debug';
|
|
@@ -1,11 +1,12 @@
|
|
|
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 { Email } from '../mail/core/classes.email.js';
|
|
5
4
|
import type { IAttachment } from '../mail/core/classes.email.js';
|
|
6
5
|
import { SecurityLogger, SecurityLogLevel, SecurityEventType } from './classes.securitylogger.js';
|
|
7
6
|
import { RustSecurityBridge } from './classes.rustsecuritybridge.js';
|
|
8
7
|
import { LRUCache } from 'lru-cache';
|
|
8
|
+
import { getErrorMessage, getErrorStack } from '../functions.errors.js';
|
|
9
|
+
import { callObserverSafely } from '../mail/delivery/functions.safe-observers.js';
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* Scan result information
|
|
@@ -180,9 +181,10 @@ export class ContentScanner {
|
|
|
180
181
|
|
|
181
182
|
return result;
|
|
182
183
|
} catch (error) {
|
|
183
|
-
|
|
184
|
+
const errorMessage = getErrorMessage(error);
|
|
185
|
+
logger.log('error', `Error scanning email: ${errorMessage}`, {
|
|
184
186
|
messageId: email.getMessageId(),
|
|
185
|
-
error: error
|
|
187
|
+
error: getErrorStack(error)
|
|
186
188
|
});
|
|
187
189
|
|
|
188
190
|
// Return a safe default with error indication
|
|
@@ -192,7 +194,7 @@ export class ContentScanner {
|
|
|
192
194
|
scannedElements: ['error'],
|
|
193
195
|
timestamp: Date.now(),
|
|
194
196
|
threatType: 'scan_error',
|
|
195
|
-
threatDetails: `Scan error: ${
|
|
197
|
+
threatDetails: `Scan error: ${errorMessage}`
|
|
196
198
|
};
|
|
197
199
|
}
|
|
198
200
|
}
|
|
@@ -301,7 +303,7 @@ export class ContentScanner {
|
|
|
301
303
|
.replace(/[\x00-\x09\x0B-\x1F\x7F-\x9F]/g, '') // Remove control chars
|
|
302
304
|
.replace(/\uFFFD/g, ''); // Remove replacement char
|
|
303
305
|
} catch (error) {
|
|
304
|
-
logger.log('warn', `Error extracting text from buffer: ${error
|
|
306
|
+
logger.log('warn', `Error extracting text from buffer: ${getErrorMessage(error)}`);
|
|
305
307
|
return '';
|
|
306
308
|
}
|
|
307
309
|
}
|
|
@@ -341,7 +343,7 @@ export class ContentScanner {
|
|
|
341
343
|
* @param result The scan result
|
|
342
344
|
*/
|
|
343
345
|
private logHighThreatFound(email: Email, result: IScanResult): void {
|
|
344
|
-
SecurityLogger.getInstance().logEvent({
|
|
346
|
+
void callObserverSafely('content scan security log', () => SecurityLogger.getInstance().logEvent({
|
|
345
347
|
level: SecurityLogLevel.ERROR,
|
|
346
348
|
type: SecurityEventType.MALWARE,
|
|
347
349
|
message: `High threat content detected in email from ${email.from} to ${email.to.join(', ')}`,
|
|
@@ -354,8 +356,8 @@ export class ContentScanner {
|
|
|
354
356
|
subject: email.subject
|
|
355
357
|
},
|
|
356
358
|
success: false,
|
|
357
|
-
domain: email.getFromDomain()
|
|
358
|
-
});
|
|
359
|
+
domain: email.getFromDomain() || undefined
|
|
360
|
+
}));
|
|
359
361
|
}
|
|
360
362
|
|
|
361
363
|
/**
|
|
@@ -364,7 +366,7 @@ export class ContentScanner {
|
|
|
364
366
|
* @param result The scan result
|
|
365
367
|
*/
|
|
366
368
|
private logThreatFound(email: Email, result: IScanResult): void {
|
|
367
|
-
SecurityLogger.getInstance().logEvent({
|
|
369
|
+
void callObserverSafely('content scan security log', () => SecurityLogger.getInstance().logEvent({
|
|
368
370
|
level: SecurityLogLevel.WARN,
|
|
369
371
|
type: SecurityEventType.SPAM,
|
|
370
372
|
message: `Suspicious content detected in email from ${email.from} to ${email.to.join(', ')}`,
|
|
@@ -377,8 +379,8 @@ export class ContentScanner {
|
|
|
377
379
|
subject: email.subject
|
|
378
380
|
},
|
|
379
381
|
success: false,
|
|
380
|
-
domain: email.getFromDomain()
|
|
381
|
-
});
|
|
382
|
+
domain: email.getFromDomain() || undefined
|
|
383
|
+
}));
|
|
382
384
|
}
|
|
383
385
|
|
|
384
386
|
/**
|
|
@@ -397,4 +399,4 @@ export class ContentScanner {
|
|
|
397
399
|
return 'high';
|
|
398
400
|
}
|
|
399
401
|
}
|
|
400
|
-
}
|
|
402
|
+
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import * as plugins from '../plugins.js';
|
|
2
|
-
import * as paths from '../paths.js';
|
|
3
1
|
import { logger } from '../logger.js';
|
|
4
|
-
import {
|
|
2
|
+
import { getErrorMessage, getErrorStack } from '../functions.errors.js';
|
|
3
|
+
import type { IStorageManager } from '../mail/interfaces.storage.js';
|
|
5
4
|
import { SecurityLogger, SecurityLogLevel, SecurityEventType } from './classes.securitylogger.js';
|
|
6
5
|
import { RustSecurityBridge } from './classes.rustsecuritybridge.js';
|
|
7
6
|
import { LRUCache } from 'lru-cache';
|
|
7
|
+
import { callObserverSafely } from '../mail/delivery/functions.safe-observers.js';
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Reputation check result information
|
|
@@ -54,20 +54,21 @@ export interface IIPReputationOptions {
|
|
|
54
54
|
highRiskThreshold?: number; // Score below this is high risk
|
|
55
55
|
mediumRiskThreshold?: number; // Score below this is medium risk
|
|
56
56
|
lowRiskThreshold?: number; // Score below this is low risk
|
|
57
|
-
|
|
57
|
+
persistCache?: boolean; // Whether to persist cache through IStorageManager (default: true)
|
|
58
58
|
enableDNSBL?: boolean; // Whether to use DNSBL checks (default: true)
|
|
59
59
|
enableIPInfo?: boolean; // Whether to use IP info service (default: true)
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
/**
|
|
63
63
|
* IP reputation checker — delegates DNSBL lookups to the Rust security bridge.
|
|
64
|
-
* Retains LRU
|
|
64
|
+
* Retains an in-memory LRU cache and optionally persists it through managed storage.
|
|
65
65
|
*/
|
|
66
66
|
export class IPReputationChecker {
|
|
67
67
|
private static instance: IPReputationChecker;
|
|
68
68
|
private reputationCache: LRUCache<string, IReputationResult>;
|
|
69
69
|
private options: Required<IIPReputationOptions>;
|
|
70
|
-
private storageManager?:
|
|
70
|
+
private storageManager?: IStorageManager;
|
|
71
|
+
private initializationPromise: Promise<void>;
|
|
71
72
|
|
|
72
73
|
private static readonly DEFAULT_OPTIONS: Required<IIPReputationOptions> = {
|
|
73
74
|
maxCacheSize: 10000,
|
|
@@ -76,12 +77,12 @@ export class IPReputationChecker {
|
|
|
76
77
|
highRiskThreshold: ReputationThreshold.HIGH_RISK,
|
|
77
78
|
mediumRiskThreshold: ReputationThreshold.MEDIUM_RISK,
|
|
78
79
|
lowRiskThreshold: ReputationThreshold.LOW_RISK,
|
|
79
|
-
|
|
80
|
+
persistCache: true,
|
|
80
81
|
enableDNSBL: true,
|
|
81
82
|
enableIPInfo: true
|
|
82
83
|
};
|
|
83
84
|
|
|
84
|
-
constructor(options: IIPReputationOptions = {}, storageManager?:
|
|
85
|
+
constructor(options: IIPReputationOptions = {}, storageManager?: IStorageManager) {
|
|
85
86
|
this.options = {
|
|
86
87
|
...IPReputationChecker.DEFAULT_OPTIONS,
|
|
87
88
|
...options
|
|
@@ -94,14 +95,19 @@ export class IPReputationChecker {
|
|
|
94
95
|
ttl: this.options.cacheTTL,
|
|
95
96
|
});
|
|
96
97
|
|
|
97
|
-
|
|
98
|
-
this.loadCache()
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
98
|
+
const hydrationPromise = this.options.persistCache
|
|
99
|
+
? this.loadCache()
|
|
100
|
+
: Promise.resolve();
|
|
101
|
+
hydrationPromise.catch(() => undefined);
|
|
102
|
+
this.initializationPromise = hydrationPromise;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Wait until the managed reputation cache has been hydrated. */
|
|
106
|
+
public async initialize(): Promise<void> {
|
|
107
|
+
await this.initializationPromise;
|
|
102
108
|
}
|
|
103
109
|
|
|
104
|
-
public static getInstance(options: IIPReputationOptions = {}, storageManager?:
|
|
110
|
+
public static getInstance(options: IIPReputationOptions = {}, storageManager?: IStorageManager): IPReputationChecker {
|
|
105
111
|
if (!IPReputationChecker.instance) {
|
|
106
112
|
IPReputationChecker.instance = new IPReputationChecker(options, storageManager);
|
|
107
113
|
}
|
|
@@ -113,6 +119,8 @@ export class IPReputationChecker {
|
|
|
113
119
|
*/
|
|
114
120
|
public async checkReputation(ip: string): Promise<IReputationResult> {
|
|
115
121
|
try {
|
|
122
|
+
await this.initialize();
|
|
123
|
+
|
|
116
124
|
if (!this.isValidIPAddress(ip)) {
|
|
117
125
|
logger.log('warn', `Invalid IP address format: ${ip}`);
|
|
118
126
|
return this.createErrorResult(ip, 'Invalid IP address format');
|
|
@@ -146,7 +154,7 @@ export class IPReputationChecker {
|
|
|
146
154
|
|
|
147
155
|
this.reputationCache.set(ip, result);
|
|
148
156
|
|
|
149
|
-
if (this.options.
|
|
157
|
+
if (this.options.persistCache) {
|
|
150
158
|
this.saveCache().catch(error => {
|
|
151
159
|
logger.log('error', `Failed to save IP reputation cache: ${error.message}`);
|
|
152
160
|
});
|
|
@@ -155,11 +163,12 @@ export class IPReputationChecker {
|
|
|
155
163
|
this.logReputationCheck(ip, result);
|
|
156
164
|
return result;
|
|
157
165
|
} catch (error) {
|
|
158
|
-
|
|
166
|
+
const errorMessage = getErrorMessage(error);
|
|
167
|
+
logger.log('error', `Error checking IP reputation for ${ip}: ${errorMessage}`, {
|
|
159
168
|
ip,
|
|
160
|
-
stack: error
|
|
169
|
+
stack: getErrorStack(error)
|
|
161
170
|
});
|
|
162
|
-
const errorResult = this.createErrorResult(ip,
|
|
171
|
+
const errorResult = this.createErrorResult(ip, errorMessage);
|
|
163
172
|
// Cache error results to avoid repeated failing lookups
|
|
164
173
|
this.reputationCache.set(ip, errorResult);
|
|
165
174
|
return errorResult;
|
|
@@ -189,7 +198,7 @@ export class IPReputationChecker {
|
|
|
189
198
|
logLevel = SecurityLogLevel.WARN;
|
|
190
199
|
}
|
|
191
200
|
|
|
192
|
-
SecurityLogger.getInstance().logEvent({
|
|
201
|
+
void callObserverSafely('IP reputation security log', () => SecurityLogger.getInstance().logEvent({
|
|
193
202
|
level: logLevel,
|
|
194
203
|
type: SecurityEventType.IP_REPUTATION,
|
|
195
204
|
message: `IP reputation check ${result.isSpam ? 'flagged spam' : 'completed'} for ${ip}`,
|
|
@@ -204,91 +213,29 @@ export class IPReputationChecker {
|
|
|
204
213
|
blacklists: result.blacklists
|
|
205
214
|
},
|
|
206
215
|
success: !result.isSpam
|
|
207
|
-
});
|
|
216
|
+
}));
|
|
208
217
|
}
|
|
209
218
|
|
|
210
219
|
private async saveCache(): Promise<void> {
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
if (entries.length === 0) {
|
|
218
|
-
return;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
const cacheData = JSON.stringify(entries);
|
|
222
|
-
|
|
223
|
-
if (hasStorageManagerMethods(this.storageManager, ['set'])) {
|
|
224
|
-
await this.storageManager.set('/security/ip-reputation-cache.json', cacheData);
|
|
225
|
-
logger.log('info', `Saved ${entries.length} IP reputation cache entries to StorageManager`);
|
|
226
|
-
} else {
|
|
227
|
-
const cacheDir = plugins.path.join(paths.dataDir, 'security');
|
|
228
|
-
await plugins.smartfs.directory(cacheDir).recursive().create();
|
|
229
|
-
const cacheFile = plugins.path.join(cacheDir, 'ip_reputation_cache.json');
|
|
230
|
-
await plugins.smartfs.file(cacheFile).write(cacheData);
|
|
231
|
-
logger.log('info', `Saved ${entries.length} IP reputation cache entries to disk`);
|
|
232
|
-
}
|
|
233
|
-
} catch (error) {
|
|
234
|
-
logger.log('error', `Failed to save IP reputation cache: ${error.message}`);
|
|
235
|
-
}
|
|
220
|
+
if (!this.storageManager) return;
|
|
221
|
+
const entries = Array.from(this.reputationCache.entries()).map(([ip, data]) => ({ ip, data }));
|
|
222
|
+
if (entries.length === 0) return;
|
|
223
|
+
await this.storageManager.set('/security/ip-reputation-cache.json', JSON.stringify(entries));
|
|
224
|
+
logger.log('info', `Saved ${entries.length} IP reputation cache entries`);
|
|
236
225
|
}
|
|
237
226
|
|
|
238
227
|
private async loadCache(): Promise<void> {
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
const cacheFile = plugins.path.join(paths.dataDir, 'security', 'ip_reputation_cache.json');
|
|
249
|
-
if (plugins.fs.existsSync(cacheFile)) {
|
|
250
|
-
logger.log('info', 'Migrating IP reputation cache from filesystem to StorageManager');
|
|
251
|
-
cacheData = plugins.fs.readFileSync(cacheFile, 'utf8');
|
|
252
|
-
fromFilesystem = true;
|
|
253
|
-
await this.storageManager.set('/security/ip-reputation-cache.json', cacheData);
|
|
254
|
-
logger.log('info', 'IP reputation cache migrated to StorageManager successfully');
|
|
255
|
-
try {
|
|
256
|
-
plugins.fs.unlinkSync(cacheFile);
|
|
257
|
-
logger.log('info', 'Old cache file removed after migration');
|
|
258
|
-
} catch (deleteError) {
|
|
259
|
-
logger.log('warn', `Could not delete old cache file: ${deleteError.message}`);
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
} catch (error) {
|
|
264
|
-
logger.log('error', `Error loading from StorageManager: ${error.message}`);
|
|
265
|
-
}
|
|
266
|
-
} else {
|
|
267
|
-
const cacheFile = plugins.path.join(paths.dataDir, 'security', 'ip_reputation_cache.json');
|
|
268
|
-
if (plugins.fs.existsSync(cacheFile)) {
|
|
269
|
-
cacheData = plugins.fs.readFileSync(cacheFile, 'utf8');
|
|
270
|
-
fromFilesystem = true;
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
if (cacheData) {
|
|
275
|
-
const entries = JSON.parse(cacheData);
|
|
276
|
-
const now = Date.now();
|
|
277
|
-
const validEntries = entries.filter(entry => {
|
|
278
|
-
const age = now - entry.data.timestamp;
|
|
279
|
-
return age < this.options.cacheTTL;
|
|
280
|
-
});
|
|
281
|
-
|
|
282
|
-
for (const entry of validEntries) {
|
|
283
|
-
this.reputationCache.set(entry.ip, entry.data);
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
const source = fromFilesystem ? 'disk' : 'StorageManager';
|
|
287
|
-
logger.log('info', `Loaded ${validEntries.length} IP reputation cache entries from ${source}`);
|
|
288
|
-
}
|
|
289
|
-
} catch (error) {
|
|
290
|
-
logger.log('error', `Failed to load IP reputation cache: ${error.message}`);
|
|
228
|
+
if (!this.storageManager) return;
|
|
229
|
+
const cacheData = await this.storageManager.get('/security/ip-reputation-cache.json');
|
|
230
|
+
if (!cacheData) return;
|
|
231
|
+
|
|
232
|
+
const entries = JSON.parse(cacheData);
|
|
233
|
+
const now = Date.now();
|
|
234
|
+
const validEntries = entries.filter((entry) => now - entry.data.timestamp < this.options.cacheTTL);
|
|
235
|
+
for (const entry of validEntries) {
|
|
236
|
+
this.reputationCache.set(entry.ip, entry.data);
|
|
291
237
|
}
|
|
238
|
+
logger.log('info', `Loaded ${validEntries.length} IP reputation cache entries`);
|
|
292
239
|
}
|
|
293
240
|
|
|
294
241
|
public static getRiskLevel(score: number): 'high' | 'medium' | 'low' | 'trusted' {
|
|
@@ -303,11 +250,11 @@ export class IPReputationChecker {
|
|
|
303
250
|
}
|
|
304
251
|
}
|
|
305
252
|
|
|
306
|
-
public updateStorageManager(storageManager:
|
|
253
|
+
public updateStorageManager(storageManager: IStorageManager): void {
|
|
307
254
|
this.storageManager = storageManager;
|
|
308
255
|
logger.log('info', 'IPReputationChecker storage manager updated');
|
|
309
256
|
|
|
310
|
-
if (this.options.
|
|
257
|
+
if (this.options.persistCache && this.reputationCache.size > 0) {
|
|
311
258
|
this.saveCache().catch(error => {
|
|
312
259
|
logger.log('error', `Failed to save cache to new storage manager: ${error.message}`);
|
|
313
260
|
});
|