@push.rocks/smartmta 5.3.0 → 5.3.3

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 (38) hide show
  1. package/changelog.md +23 -0
  2. package/dist_ts/00_commitinfo_data.js +1 -1
  3. package/dist_ts/mail/core/classes.bouncemanager.d.ts +7 -1
  4. package/dist_ts/mail/core/classes.bouncemanager.js +25 -5
  5. package/dist_ts/mail/delivery/classes.delivery.queue.d.ts +4 -2
  6. package/dist_ts/mail/delivery/classes.delivery.queue.js +30 -14
  7. package/dist_ts/mail/delivery/classes.unified.rate.limiter.js +13 -1
  8. package/dist_ts/mail/index.d.ts +1 -0
  9. package/dist_ts/mail/index.js +2 -1
  10. package/dist_ts/mail/interfaces.storage.d.ts +7 -0
  11. package/dist_ts/mail/interfaces.storage.js +4 -0
  12. package/dist_ts/mail/routing/classes.dkim.manager.d.ts +2 -1
  13. package/dist_ts/mail/routing/classes.dkim.manager.js +17 -8
  14. package/dist_ts/mail/routing/classes.dns.manager.d.ts +1 -5
  15. package/dist_ts/mail/routing/classes.dns.manager.js +2 -2
  16. package/dist_ts/mail/routing/classes.email.router.d.ts +2 -1
  17. package/dist_ts/mail/routing/classes.email.router.js +6 -5
  18. package/dist_ts/mail/routing/classes.unified.email.server.d.ts +17 -3
  19. package/dist_ts/mail/routing/classes.unified.email.server.js +32 -8
  20. package/dist_ts/mail/security/classes.dkimcreator.d.ts +10 -5
  21. package/dist_ts/mail/security/classes.dkimcreator.js +91 -70
  22. package/dist_ts/security/classes.ipreputationchecker.d.ts +4 -3
  23. package/dist_ts/security/classes.ipreputationchecker.js +4 -3
  24. package/package.json +11 -11
  25. package/readme.hints.md +1 -1
  26. package/readme.md +17 -0
  27. package/ts/00_commitinfo_data.ts +1 -1
  28. package/ts/mail/core/classes.bouncemanager.ts +32 -9
  29. package/ts/mail/delivery/classes.delivery.queue.ts +40 -21
  30. package/ts/mail/delivery/classes.unified.rate.limiter.ts +15 -1
  31. package/ts/mail/index.ts +2 -1
  32. package/ts/mail/interfaces.storage.ts +13 -0
  33. package/ts/mail/routing/classes.dkim.manager.ts +24 -11
  34. package/ts/mail/routing/classes.dns.manager.ts +3 -8
  35. package/ts/mail/routing/classes.email.router.ts +7 -6
  36. package/ts/mail/routing/classes.unified.email.server.ts +52 -12
  37. package/ts/mail/security/classes.dkimcreator.ts +115 -91
  38. package/ts/security/classes.ipreputationchecker.ts +7 -6
@@ -1,5 +1,6 @@
1
1
  import * as plugins from '../../plugins.js';
2
2
  import * as paths from '../../paths.js';
3
+ import { type IStorageManagerLike, hasStorageManagerMethods } from '../interfaces.storage.js';
3
4
 
4
5
  import { Email } from '../core/classes.email.js';
5
6
  // MtaService reference removed
@@ -24,13 +25,47 @@ export interface IDkimKeyMetadata {
24
25
 
25
26
  export class DKIMCreator {
26
27
  private keysDir: string;
27
- private storageManager?: any; // StorageManager instance
28
+ private storageManager?: IStorageManagerLike;
28
29
 
29
- constructor(keysDir = paths.keysDir, storageManager?: any) {
30
+ constructor(keysDir = paths.keysDir, storageManager?: IStorageManagerLike) {
30
31
  this.keysDir = keysDir;
31
32
  this.storageManager = storageManager;
32
33
  }
33
34
 
35
+ private async writeKeyPairToFilesystem(
36
+ privateKeyPath: string,
37
+ publicKeyPath: string,
38
+ privateKey: string,
39
+ publicKey: string,
40
+ ): Promise<void> {
41
+ await Promise.all([writeFile(privateKeyPath, privateKey), writeFile(publicKeyPath, publicKey)]);
42
+ }
43
+
44
+ private async storeLegacyKeysToStorage(domain: string, privateKey: string, publicKey: string): Promise<void> {
45
+ if (!hasStorageManagerMethods(this.storageManager, ['set'])) {
46
+ return;
47
+ }
48
+ await Promise.all([
49
+ this.storageManager.set(`/email/dkim/${domain}/private.key`, privateKey),
50
+ this.storageManager.set(`/email/dkim/${domain}/public.key`, publicKey),
51
+ ]);
52
+ }
53
+
54
+ private async storeSelectorKeysToStorage(
55
+ domain: string,
56
+ selector: string,
57
+ privateKey: string,
58
+ publicKey: string,
59
+ ): Promise<void> {
60
+ if (!hasStorageManagerMethods(this.storageManager, ['set'])) {
61
+ return;
62
+ }
63
+ await Promise.all([
64
+ this.storageManager.set(`/email/dkim/${domain}/${selector}/private.key`, privateKey),
65
+ this.storageManager.set(`/email/dkim/${domain}/${selector}/public.key`, publicKey),
66
+ ]);
67
+ }
68
+
34
69
  public async getKeyPathsForDomain(domainArg: string): Promise<IKeyPaths> {
35
70
  return {
36
71
  privateKeyPath: plugins.path.join(this.keysDir, `${domainArg}-private.pem`),
@@ -51,6 +86,20 @@ export class DKIMCreator {
51
86
  }
52
87
  }
53
88
 
89
+ public async handleDKIMKeysForSelector(domainArg: string, selector: string = 'default', keySize: number = 2048): Promise<void> {
90
+ if (selector === 'default') {
91
+ await this.handleDKIMKeysForDomain(domainArg);
92
+ return;
93
+ }
94
+
95
+ try {
96
+ await this.readDKIMKeysForSelector(domainArg, selector);
97
+ } catch {
98
+ console.log(`No DKIM keys found for ${domainArg}/${selector}. Generating...`);
99
+ await this.createAndStoreDKIMKeysForSelector(domainArg, selector, keySize);
100
+ }
101
+ }
102
+
54
103
  public async handleDKIMKeysForEmail(email: Email): Promise<void> {
55
104
  const domain = email.from.split('@')[1];
56
105
  await this.handleDKIMKeysForDomain(domain);
@@ -59,7 +108,7 @@ export class DKIMCreator {
59
108
  // Read DKIM keys - always use storage manager, migrate from filesystem if needed
60
109
  public async readDKIMKeys(domainArg: string): Promise<{ privateKey: string; publicKey: string }> {
61
110
  // Try to read from storage manager first
62
- if (this.storageManager) {
111
+ if (hasStorageManagerMethods(this.storageManager, ['get', 'set'])) {
63
112
  try {
64
113
  const [privateKey, publicKey] = await Promise.all([
65
114
  this.storageManager.get(`/email/dkim/${domainArg}/private.key`),
@@ -87,10 +136,7 @@ export class DKIMCreator {
87
136
 
88
137
  // Migrate to storage manager
89
138
  console.log(`Migrating DKIM keys for ${domainArg} from filesystem to StorageManager`);
90
- await Promise.all([
91
- this.storageManager.set(`/email/dkim/${domainArg}/private.key`, privateKey),
92
- this.storageManager.set(`/email/dkim/${domainArg}/public.key`, publicKey)
93
- ]);
139
+ await this.storeLegacyKeysToStorage(domainArg, privateKey, publicKey);
94
140
 
95
141
  return { privateKey, publicKey };
96
142
  } catch (error) {
@@ -116,9 +162,9 @@ export class DKIMCreator {
116
162
  }
117
163
 
118
164
  // Create an RSA DKIM key pair - changed to public for API access
119
- public async createDKIMKeys(): Promise<{ privateKey: string; publicKey: string }> {
165
+ public async createDKIMKeys(keySize: number = 2048): Promise<{ privateKey: string; publicKey: string }> {
120
166
  const { privateKey, publicKey } = await generateKeyPair('rsa', {
121
- modulusLength: 2048,
167
+ modulusLength: keySize,
122
168
  publicKeyEncoding: { type: 'spki', format: 'pem' },
123
169
  privateKeyEncoding: { type: 'pkcs1', format: 'pem' },
124
170
  });
@@ -136,75 +182,58 @@ export class DKIMCreator {
136
182
  return { privateKey, publicKey };
137
183
  }
138
184
 
139
- // Store a DKIM key pair - uses storage manager if available, else disk
140
- public async storeDKIMKeys(
141
- privateKey: string,
142
- publicKey: string,
143
- privateKeyPath: string,
144
- publicKeyPath: string
145
- ): Promise<void> {
146
- // Store in storage manager if available
147
- if (this.storageManager) {
148
- // Extract domain from path (e.g., /path/to/keys/example.com-private.pem -> example.com)
149
- const match = privateKeyPath.match(/\/([^\/]+)-private\.pem$/);
150
- if (match) {
151
- const domain = match[1];
152
- await Promise.all([
153
- this.storageManager.set(`/email/dkim/${domain}/private.key`, privateKey),
154
- this.storageManager.set(`/email/dkim/${domain}/public.key`, publicKey)
155
- ]);
156
- }
157
- }
158
-
159
- // Also store to filesystem for backward compatibility
160
- await Promise.all([writeFile(privateKeyPath, privateKey), writeFile(publicKeyPath, publicKey)]);
161
- }
162
-
163
185
  // Create a DKIM key pair and store it to disk - changed to public for API access
164
- public async createAndStoreDKIMKeys(domain: string): Promise<void> {
165
- const { privateKey, publicKey } = await this.createDKIMKeys();
186
+ public async createAndStoreDKIMKeys(domain: string, keySize: number = 2048): Promise<void> {
187
+ const { privateKey, publicKey } = await this.createDKIMKeys(keySize);
166
188
  const keyPaths = await this.getKeyPathsForDomain(domain);
167
- await this.storeDKIMKeys(
168
- privateKey,
169
- publicKey,
170
- keyPaths.privateKeyPath,
171
- keyPaths.publicKeyPath
172
- );
189
+ await this.storeLegacyKeysToStorage(domain, privateKey, publicKey);
190
+ await this.writeKeyPairToFilesystem(keyPaths.privateKeyPath, keyPaths.publicKeyPath, privateKey, publicKey);
191
+ await this.saveKeyMetadata({
192
+ domain,
193
+ selector: 'default',
194
+ createdAt: Date.now(),
195
+ keySize,
196
+ });
173
197
  console.log(`DKIM keys for ${domain} created and stored.`);
174
198
  }
175
199
 
176
- // Changed to public for API access
177
- public async getDNSRecordForDomain(domainArg: string): Promise<plugins.tsclass.network.IDnsRecord> {
178
- await this.handleDKIMKeysForDomain(domainArg);
179
- const keys = await this.readDKIMKeys(domainArg);
180
-
181
- // Remove the PEM header and footer and newlines
182
- const pemHeader = '-----BEGIN PUBLIC KEY-----';
183
- const pemFooter = '-----END PUBLIC KEY-----';
184
- const keyContents = keys.publicKey
185
- .replace(pemHeader, '')
186
- .replace(pemFooter, '')
187
- .replace(/\n/g, '');
188
-
189
- // Detect key type from PEM header
190
- const keyAlgo = keys.privateKey.includes('ED25519') || keys.publicKey.length < 200 ? 'ed25519' : 'rsa';
200
+ public async createAndStoreDKIMKeysForSelector(
201
+ domain: string,
202
+ selector: string,
203
+ keySize: number = 2048,
204
+ ): Promise<void> {
205
+ if (selector === 'default') {
206
+ await this.createAndStoreDKIMKeys(domain, keySize);
207
+ return;
208
+ }
191
209
 
192
- // Now generate the DKIM DNS TXT record
193
- const dnsRecordValue = `v=DKIM1; h=sha256; k=${keyAlgo}; p=${keyContents}`;
210
+ const { privateKey, publicKey } = await this.createDKIMKeys(keySize);
211
+ const keyPaths = await this.getKeyPathsForSelector(domain, selector);
212
+ await this.storeSelectorKeysToStorage(domain, selector, privateKey, publicKey);
213
+ await this.writeKeyPairToFilesystem(keyPaths.privateKeyPath, keyPaths.publicKeyPath, privateKey, publicKey);
214
+ await this.saveKeyMetadata({
215
+ domain,
216
+ selector,
217
+ createdAt: Date.now(),
218
+ keySize,
219
+ });
220
+ console.log(`DKIM keys for ${domain}/${selector} created and stored.`);
221
+ }
194
222
 
195
- return {
196
- name: `mta._domainkey.${domainArg}`,
197
- type: 'TXT',
198
- dnsSecEnabled: null,
199
- value: dnsRecordValue,
200
- };
223
+ // Changed to public for API access
224
+ public async getDNSRecordForDomain(
225
+ domainArg: string,
226
+ selector: string = 'default',
227
+ ): Promise<plugins.tsclass.network.IDnsRecord> {
228
+ await this.handleDKIMKeysForSelector(domainArg, selector);
229
+ return this.getDNSRecordForSelector(domainArg, selector);
201
230
  }
202
231
 
203
232
  /**
204
233
  * Get DKIM key metadata for a domain
205
234
  */
206
235
  private async getKeyMetadata(domain: string, selector: string = 'default'): Promise<IDkimKeyMetadata | null> {
207
- if (!this.storageManager) {
236
+ if (!hasStorageManagerMethods(this.storageManager, ['get'])) {
208
237
  return null;
209
238
  }
210
239
 
@@ -222,7 +251,7 @@ export class DKIMCreator {
222
251
  * Save DKIM key metadata
223
252
  */
224
253
  private async saveKeyMetadata(metadata: IDkimKeyMetadata): Promise<void> {
225
- if (!this.storageManager) {
254
+ if (!hasStorageManagerMethods(this.storageManager, ['set'])) {
226
255
  return;
227
256
  }
228
257
 
@@ -259,30 +288,16 @@ export class DKIMCreator {
259
288
  const newSelector = `key${now.getFullYear()}${String(now.getMonth() + 1).padStart(2, '0')}`;
260
289
 
261
290
  // Create new keys with custom key size
262
- const { privateKey, publicKey } = await generateKeyPair('rsa', {
263
- modulusLength: keySize,
264
- publicKeyEncoding: { type: 'spki', format: 'pem' },
265
- privateKeyEncoding: { type: 'pkcs1', format: 'pem' },
266
- });
291
+ const { privateKey, publicKey } = await this.createDKIMKeys(keySize);
267
292
 
268
293
  // Store new keys with new selector
269
294
  const newKeyPaths = await this.getKeyPathsForSelector(domain, newSelector);
270
295
 
271
296
  // Store in storage manager if available
272
- if (this.storageManager) {
273
- await Promise.all([
274
- this.storageManager.set(`/email/dkim/${domain}/${newSelector}/private.key`, privateKey),
275
- this.storageManager.set(`/email/dkim/${domain}/${newSelector}/public.key`, publicKey)
276
- ]);
277
- }
297
+ await this.storeSelectorKeysToStorage(domain, newSelector, privateKey, publicKey);
278
298
 
279
299
  // Also store to filesystem
280
- await this.storeDKIMKeys(
281
- privateKey,
282
- publicKey,
283
- newKeyPaths.privateKeyPath,
284
- newKeyPaths.publicKeyPath
285
- );
300
+ await this.writeKeyPairToFilesystem(newKeyPaths.privateKeyPath, newKeyPaths.publicKeyPath, privateKey, publicKey);
286
301
 
287
302
  // Save metadata for new keys
288
303
  const metadata: IDkimKeyMetadata = {
@@ -320,7 +335,7 @@ export class DKIMCreator {
320
335
  */
321
336
  public async readDKIMKeysForSelector(domain: string, selector: string): Promise<{ privateKey: string; publicKey: string }> {
322
337
  // Try to read from storage manager first
323
- if (this.storageManager) {
338
+ if (hasStorageManagerMethods(this.storageManager, ['get', 'set'])) {
324
339
  try {
325
340
  const [privateKey, publicKey] = await Promise.all([
326
341
  this.storageManager.get(`/email/dkim/${domain}/${selector}/private.key`),
@@ -330,6 +345,10 @@ export class DKIMCreator {
330
345
  if (privateKey && publicKey) {
331
346
  return { privateKey, publicKey };
332
347
  }
348
+
349
+ if (selector === 'default') {
350
+ return await this.readDKIMKeys(domain);
351
+ }
333
352
  } catch (error) {
334
353
  // Fall through to migration check
335
354
  }
@@ -347,10 +366,7 @@ export class DKIMCreator {
347
366
 
348
367
  // Migrate to storage manager
349
368
  console.log(`Migrating DKIM keys for ${domain}/${selector} from filesystem to StorageManager`);
350
- await Promise.all([
351
- this.storageManager.set(`/email/dkim/${domain}/${selector}/private.key`, privateKey),
352
- this.storageManager.set(`/email/dkim/${domain}/${selector}/public.key`, publicKey)
353
- ]);
369
+ await this.storeSelectorKeysToStorage(domain, selector, privateKey, publicKey);
354
370
 
355
371
  return { privateKey, publicKey };
356
372
  } catch (error) {
@@ -361,6 +377,9 @@ export class DKIMCreator {
361
377
  }
362
378
  } else {
363
379
  // No storage manager, use filesystem directly
380
+ if (selector === 'default') {
381
+ return this.readDKIMKeys(domain);
382
+ }
364
383
  const keyPaths = await this.getKeyPathsForSelector(domain, selector);
365
384
  const [privateKeyBuffer, publicKeyBuffer] = await Promise.all([
366
385
  readFile(keyPaths.privateKeyPath),
@@ -406,7 +425,8 @@ export class DKIMCreator {
406
425
  * Clean up old DKIM keys after grace period
407
426
  */
408
427
  public async cleanupOldKeys(domain: string, gracePeriodDays: number = 30): Promise<void> {
409
- if (!this.storageManager) {
428
+ if (!hasStorageManagerMethods(this.storageManager, ['get', 'list', 'delete'])) {
429
+ console.log(`StorageManager for ${domain} does not support list/delete. Skipping DKIM cleanup.`);
410
430
  return;
411
431
  }
412
432
 
@@ -436,7 +456,11 @@ export class DKIMCreator {
436
456
  console.warn(`Failed to delete old key files: ${error.message}`);
437
457
  }
438
458
 
439
- // Delete metadata
459
+ // Delete selector-specific storage keys and metadata
460
+ await Promise.all([
461
+ this.storageManager.delete(`/email/dkim/${domain}/${metadata.selector}/private.key`),
462
+ this.storageManager.delete(`/email/dkim/${domain}/${metadata.selector}/public.key`),
463
+ ]);
440
464
  await this.storageManager.delete(key);
441
465
  }
442
466
  }
@@ -444,4 +468,4 @@ export class DKIMCreator {
444
468
  }
445
469
  }
446
470
  }
447
- }
471
+ }
@@ -1,6 +1,7 @@
1
1
  import * as plugins from '../plugins.js';
2
2
  import * as paths from '../paths.js';
3
3
  import { logger } from '../logger.js';
4
+ import { hasStorageManagerMethods, type IStorageManagerLike } from '../mail/interfaces.storage.js';
4
5
  import { SecurityLogger, SecurityLogLevel, SecurityEventType } from './classes.securitylogger.js';
5
6
  import { RustSecurityBridge } from './classes.rustsecuritybridge.js';
6
7
  import { LRUCache } from 'lru-cache';
@@ -66,7 +67,7 @@ export class IPReputationChecker {
66
67
  private static instance: IPReputationChecker;
67
68
  private reputationCache: LRUCache<string, IReputationResult>;
68
69
  private options: Required<IIPReputationOptions>;
69
- private storageManager?: any;
70
+ private storageManager?: IStorageManagerLike;
70
71
 
71
72
  private static readonly DEFAULT_OPTIONS: Required<IIPReputationOptions> = {
72
73
  maxCacheSize: 10000,
@@ -80,7 +81,7 @@ export class IPReputationChecker {
80
81
  enableIPInfo: true
81
82
  };
82
83
 
83
- constructor(options: IIPReputationOptions = {}, storageManager?: any) {
84
+ constructor(options: IIPReputationOptions = {}, storageManager?: IStorageManagerLike) {
84
85
  this.options = {
85
86
  ...IPReputationChecker.DEFAULT_OPTIONS,
86
87
  ...options
@@ -100,7 +101,7 @@ export class IPReputationChecker {
100
101
  }
101
102
  }
102
103
 
103
- public static getInstance(options: IIPReputationOptions = {}, storageManager?: any): IPReputationChecker {
104
+ public static getInstance(options: IIPReputationOptions = {}, storageManager?: IStorageManagerLike): IPReputationChecker {
104
105
  if (!IPReputationChecker.instance) {
105
106
  IPReputationChecker.instance = new IPReputationChecker(options, storageManager);
106
107
  }
@@ -219,7 +220,7 @@ export class IPReputationChecker {
219
220
 
220
221
  const cacheData = JSON.stringify(entries);
221
222
 
222
- if (this.storageManager) {
223
+ if (hasStorageManagerMethods(this.storageManager, ['set'])) {
223
224
  await this.storageManager.set('/security/ip-reputation-cache.json', cacheData);
224
225
  logger.log('info', `Saved ${entries.length} IP reputation cache entries to StorageManager`);
225
226
  } else {
@@ -239,7 +240,7 @@ export class IPReputationChecker {
239
240
  let cacheData: string | null = null;
240
241
  let fromFilesystem = false;
241
242
 
242
- if (this.storageManager) {
243
+ if (hasStorageManagerMethods(this.storageManager, ['get', 'set'])) {
243
244
  try {
244
245
  cacheData = await this.storageManager.get('/security/ip-reputation-cache.json');
245
246
 
@@ -302,7 +303,7 @@ export class IPReputationChecker {
302
303
  }
303
304
  }
304
305
 
305
- public updateStorageManager(storageManager: any): void {
306
+ public updateStorageManager(storageManager: IStorageManagerLike): void {
306
307
  this.storageManager = storageManager;
307
308
  logger.log('info', 'IPReputationChecker storage manager updated');
308
309