@push.rocks/smartmta 8.2.1 → 9.1.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 +43 -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/index.d.ts +2 -0
- package/dist_ts/index.js +3 -1
- package/dist_ts/mail/delivery/classes.delivery.system.d.ts +7 -0
- package/dist_ts/mail/delivery/classes.delivery.system.js +33 -4
- package/dist_ts/mail/routing/classes.email.action.executor.d.ts +4 -2
- package/dist_ts/mail/routing/classes.email.action.executor.js +6 -16
- package/dist_ts/mail/routing/classes.email.router.d.ts +28 -0
- package/dist_ts/mail/routing/classes.email.router.js +47 -7
- package/dist_ts/mail/routing/classes.unified.email.server.d.ts +112 -8
- package/dist_ts/mail/routing/classes.unified.email.server.js +491 -32
- package/dist_ts/mail/routing/functions.routing.validation.d.ts +14 -0
- package/dist_ts/mail/routing/functions.routing.validation.js +97 -1
- package/dist_ts/mail/routing/interfaces.d.ts +74 -3
- package/package.json +3 -3
- package/readme.md +109 -12
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/index.ts +2 -0
- package/ts/mail/delivery/classes.delivery.system.ts +36 -3
- package/ts/mail/routing/classes.email.action.executor.ts +7 -28
- package/ts/mail/routing/classes.email.router.ts +64 -10
- package/ts/mail/routing/classes.unified.email.server.ts +587 -44
- package/ts/mail/routing/functions.routing.validation.ts +120 -0
- package/ts/mail/routing/interfaces.ts +73 -3
|
@@ -6,6 +6,21 @@ import type { Email } from '../core/classes.email.js';
|
|
|
6
6
|
import { getErrorMessage } from '../../functions.errors.js';
|
|
7
7
|
import { assertValidRetentionDays } from './functions.routing.validation.js';
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* Convert an address glob pattern ('a@b.com' | '*@b.com' | 'admin@*') into a
|
|
11
|
+
* case-insensitive full-match RegExp. Shared by route matching and auth
|
|
12
|
+
* account scope enforcement so both use identical pattern semantics.
|
|
13
|
+
*/
|
|
14
|
+
export function addressGlobToRegExp(pattern: string): RegExp {
|
|
15
|
+
// Escape special regex characters except * and ?
|
|
16
|
+
const regexString = pattern
|
|
17
|
+
.replace(/[.+^${}()|[\]\\]/g, '\\$&')
|
|
18
|
+
.replace(/\*/g, '.*')
|
|
19
|
+
.replace(/\?/g, '.');
|
|
20
|
+
|
|
21
|
+
return new RegExp(`^${regexString}$`, 'i');
|
|
22
|
+
}
|
|
23
|
+
|
|
9
24
|
/**
|
|
10
25
|
* Email router that evaluates routes and determines actions
|
|
11
26
|
*/
|
|
@@ -14,8 +29,9 @@ export class EmailRouter extends EventEmitter {
|
|
|
14
29
|
private patternCache: Map<string, boolean> = new Map();
|
|
15
30
|
private storageManager?: IStorageManager;
|
|
16
31
|
private persistChanges: boolean;
|
|
32
|
+
private validateAction?: (route: IEmailRoute) => void;
|
|
17
33
|
private initializationPromise: Promise<void>;
|
|
18
|
-
|
|
34
|
+
|
|
19
35
|
/**
|
|
20
36
|
* Create a new email router
|
|
21
37
|
* @param routes Array of email routes
|
|
@@ -24,8 +40,15 @@ export class EmailRouter extends EventEmitter {
|
|
|
24
40
|
constructor(routes: IEmailRoute[], options?: {
|
|
25
41
|
storageManager?: IStorageManager;
|
|
26
42
|
persistChanges?: boolean;
|
|
43
|
+
/**
|
|
44
|
+
* Additional owner-level action policy applied to every configured,
|
|
45
|
+
* updated, and hydrated route (e.g. an embedding server requiring a
|
|
46
|
+
* durable local-delivery owner for `deliver` actions).
|
|
47
|
+
*/
|
|
48
|
+
validateAction?: (route: IEmailRoute) => void;
|
|
27
49
|
}) {
|
|
28
50
|
super();
|
|
51
|
+
this.validateAction = options?.validateAction;
|
|
29
52
|
this.validateRoutes(routes);
|
|
30
53
|
this.routes = this.sortRoutesByPriority(routes);
|
|
31
54
|
this.storageManager = options?.storageManager;
|
|
@@ -49,6 +72,8 @@ export class EmailRouter extends EventEmitter {
|
|
|
49
72
|
throw new Error('Invalid route: missing required fields');
|
|
50
73
|
}
|
|
51
74
|
|
|
75
|
+
this.validateAction?.(route);
|
|
76
|
+
|
|
52
77
|
if (route.action.type !== 'store') {
|
|
53
78
|
return;
|
|
54
79
|
}
|
|
@@ -69,6 +94,16 @@ export class EmailRouter extends EventEmitter {
|
|
|
69
94
|
this.validateRoute(route);
|
|
70
95
|
}
|
|
71
96
|
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Synchronously validate routes against structural rules and the owner's
|
|
100
|
+
* action policy without mutating router state. Callers that stage route
|
|
101
|
+
* updates in their own configuration should validate first so an invalid
|
|
102
|
+
* route set fails loudly before any state changes.
|
|
103
|
+
*/
|
|
104
|
+
public assertRoutesValid(routes: IEmailRoute[]): void {
|
|
105
|
+
this.validateRoutes(routes);
|
|
106
|
+
}
|
|
72
107
|
|
|
73
108
|
/**
|
|
74
109
|
* Sort routes by priority (higher priority first)
|
|
@@ -165,11 +200,17 @@ export class EmailRouter extends EventEmitter {
|
|
|
165
200
|
}
|
|
166
201
|
|
|
167
202
|
// Check authentication
|
|
168
|
-
if (match.authenticated !== undefined &&
|
|
203
|
+
if (match.authenticated !== undefined &&
|
|
169
204
|
context.session.authenticated !== match.authenticated) {
|
|
170
205
|
return false;
|
|
171
206
|
}
|
|
172
|
-
|
|
207
|
+
|
|
208
|
+
// Check authenticated username binding
|
|
209
|
+
if (match.authenticatedUser !== undefined &&
|
|
210
|
+
!this.matchesAuthenticatedUser(context.session, match.authenticatedUser)) {
|
|
211
|
+
return false;
|
|
212
|
+
}
|
|
213
|
+
|
|
173
214
|
// Check headers
|
|
174
215
|
if (match.headers && !this.matchesHeaders(context.email, match.headers)) {
|
|
175
216
|
return false;
|
|
@@ -233,6 +274,25 @@ export class EmailRouter extends EventEmitter {
|
|
|
233
274
|
return false;
|
|
234
275
|
}
|
|
235
276
|
|
|
277
|
+
/**
|
|
278
|
+
* Check if the session is authenticated as one of the required usernames.
|
|
279
|
+
* Unauthenticated sessions never match.
|
|
280
|
+
* @param session SMTP session
|
|
281
|
+
* @param usernames Required username or usernames
|
|
282
|
+
* @returns True if the session's authenticated username is in the set
|
|
283
|
+
*/
|
|
284
|
+
private matchesAuthenticatedUser(
|
|
285
|
+
session: IEmailContext['session'],
|
|
286
|
+
usernames: string | string[],
|
|
287
|
+
): boolean {
|
|
288
|
+
const username = session.user?.username;
|
|
289
|
+
if (!username) {
|
|
290
|
+
return false;
|
|
291
|
+
}
|
|
292
|
+
const allowed = Array.isArray(usernames) ? usernames : [usernames];
|
|
293
|
+
return allowed.includes(username);
|
|
294
|
+
}
|
|
295
|
+
|
|
236
296
|
/**
|
|
237
297
|
* Check if client IP matches patterns
|
|
238
298
|
* @param context Email context
|
|
@@ -354,13 +414,7 @@ export class EmailRouter extends EventEmitter {
|
|
|
354
414
|
* @returns Regular expression
|
|
355
415
|
*/
|
|
356
416
|
private globToRegExp(pattern: string): RegExp {
|
|
357
|
-
|
|
358
|
-
let regexString = pattern
|
|
359
|
-
.replace(/[.+^${}()|[\]\\]/g, '\\$&')
|
|
360
|
-
.replace(/\*/g, '.*')
|
|
361
|
-
.replace(/\?/g, '.');
|
|
362
|
-
|
|
363
|
-
return new RegExp(`^${regexString}$`, 'i');
|
|
417
|
+
return addressGlobToRegExp(pattern);
|
|
364
418
|
}
|
|
365
419
|
|
|
366
420
|
/**
|