@serve.zone/dcrouter 18.0.2 → 18.1.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.
- package/deno.json +1 -1
- package/dist_serve/bundle.js +1529 -1382
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.dcrouter.d.ts +7 -2
- package/dist_ts/classes.dcrouter.js +61 -9
- package/dist_ts/db/documents/classes.authentication-event.doc.d.ts +25 -0
- package/dist_ts/db/documents/classes.authentication-event.doc.js +273 -0
- package/dist_ts/db/documents/classes.email-traffic-bucket.doc.d.ts +25 -0
- package/dist_ts/db/documents/classes.email-traffic-bucket.doc.js +200 -0
- package/dist_ts/db/documents/classes.smtp-account.doc.d.ts +31 -0
- package/dist_ts/db/documents/classes.smtp-account.doc.js +176 -0
- package/dist_ts/db/documents/index.d.ts +3 -0
- package/dist_ts/db/documents/index.js +4 -1
- package/dist_ts/email/classes.accepted-email-spool.d.ts +1 -0
- package/dist_ts/email/classes.accepted-email-spool.js +9 -1
- package/dist_ts/email/classes.email-settings.manager.d.ts +7 -0
- package/dist_ts/email/classes.email-settings.manager.js +30 -1
- package/dist_ts/email/classes.smtp-account.manager.d.ts +110 -0
- package/dist_ts/email/classes.smtp-account.manager.js +507 -0
- package/dist_ts/email/classes.workapp-mail-manager.d.ts +23 -4
- package/dist_ts/email/classes.workapp-mail-manager.js +39 -46
- package/dist_ts/email/index.d.ts +2 -0
- package/dist_ts/email/index.js +3 -1
- package/dist_ts/email/smtp-scram.d.ts +13 -0
- package/dist_ts/email/smtp-scram.js +19 -0
- package/dist_ts/monitoring/classes.metricsmanager.d.ts +19 -0
- package/dist_ts/monitoring/classes.metricsmanager.js +240 -44
- package/dist_ts/opsserver/classes.opsserver.d.ts +1 -0
- package/dist_ts/opsserver/classes.opsserver.js +3 -1
- package/dist_ts/opsserver/handlers/admin.handler.d.ts +1 -0
- package/dist_ts/opsserver/handlers/admin.handler.js +61 -14
- package/dist_ts/opsserver/handlers/email-settings.handler.js +14 -2
- package/dist_ts/opsserver/handlers/index.d.ts +1 -0
- package/dist_ts/opsserver/handlers/index.js +2 -1
- package/dist_ts/opsserver/handlers/security.handler.js +21 -3
- package/dist_ts/opsserver/handlers/smtp-account.handler.d.ts +20 -0
- package/dist_ts/opsserver/handlers/smtp-account.handler.js +155 -0
- package/dist_ts/opsserver/handlers/stats.handler.js +27 -10
- package/dist_ts/security/classes.authentication-event-manager.d.ts +35 -0
- package/dist_ts/security/classes.authentication-event-manager.js +185 -0
- package/dist_ts/security/index.d.ts +1 -0
- package/dist_ts/security/index.js +2 -1
- package/dist_ts_interfaces/data/email-settings.d.ts +10 -0
- package/dist_ts_interfaces/data/index.d.ts +1 -0
- package/dist_ts_interfaces/data/index.js +2 -1
- package/dist_ts_interfaces/data/route-management.d.ts +1 -1
- package/dist_ts_interfaces/data/route-management.js +3 -1
- package/dist_ts_interfaces/data/smtp-account.d.ts +50 -0
- package/dist_ts_interfaces/data/smtp-account.js +9 -0
- package/dist_ts_interfaces/data/stats.d.ts +16 -0
- package/dist_ts_interfaces/requests/index.d.ts +1 -0
- package/dist_ts_interfaces/requests/index.js +2 -1
- package/dist_ts_interfaces/requests/smtp-accounts.d.ts +125 -0
- package/dist_ts_interfaces/requests/smtp-accounts.js +2 -0
- package/dist_ts_migrations/index.js +186 -13
- package/dist_ts_web/00_commitinfo_data.js +1 -1
- package/dist_ts_web/appstate/smtp-accounts.d.ts +38 -0
- package/dist_ts_web/appstate/smtp-accounts.js +94 -0
- package/dist_ts_web/appstate.d.ts +1 -0
- package/dist_ts_web/appstate.js +2 -1
- package/dist_ts_web/elements/email/index.d.ts +1 -0
- package/dist_ts_web/elements/email/index.js +2 -1
- package/dist_ts_web/elements/email/ops-view-smtp-accounts.d.ts +25 -0
- package/dist_ts_web/elements/email/ops-view-smtp-accounts.js +598 -0
- package/dist_ts_web/elements/ops-dashboard.js +3 -1
- package/dist_ts_web/elements/overview/ops-view-overview.js +3 -2
- package/dist_ts_web/elements/security/ops-view-security-authentication.js +20 -13
- package/dist_ts_web/elements/security/ops-view-security-overview.js +8 -11
- package/dist_ts_web/router.js +2 -2
- package/package.json +2 -2
- package/readme.md +20 -0
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.dcrouter.ts +66 -8
- package/ts/db/documents/classes.authentication-event.doc.ts +248 -0
- package/ts/db/documents/classes.email-traffic-bucket.doc.ts +161 -0
- package/ts/db/documents/classes.smtp-account.doc.ts +103 -0
- package/ts/db/documents/index.ts +3 -0
- package/ts/email/classes.accepted-email-spool.ts +8 -0
- package/ts/email/classes.email-settings.manager.ts +34 -0
- package/ts/email/classes.smtp-account.manager.ts +601 -0
- package/ts/email/classes.workapp-mail-manager.ts +53 -55
- package/ts/email/index.ts +2 -0
- package/ts/email/smtp-scram.ts +24 -0
- package/ts/monitoring/classes.metricsmanager.ts +287 -50
- package/ts/opsserver/classes.opsserver.ts +2 -0
- package/ts/opsserver/handlers/admin.handler.ts +74 -15
- package/ts/opsserver/handlers/email-settings.handler.ts +13 -1
- package/ts/opsserver/handlers/index.ts +1 -0
- package/ts/opsserver/handlers/security.handler.ts +23 -2
- package/ts/opsserver/handlers/smtp-account.handler.ts +199 -0
- package/ts/opsserver/handlers/stats.handler.ts +29 -9
- package/ts/security/classes.authentication-event-manager.ts +221 -0
- package/ts/security/index.ts +1 -0
- package/ts_web/00_commitinfo_data.ts +1 -1
- package/ts_web/appstate/smtp-accounts.ts +143 -0
- package/ts_web/appstate.ts +1 -0
- package/ts_web/elements/email/index.ts +1 -0
- package/ts_web/elements/email/ops-view-smtp-accounts.ts +586 -0
- package/ts_web/elements/ops-dashboard.ts +2 -0
- package/ts_web/elements/overview/ops-view-overview.ts +2 -1
- package/ts_web/elements/security/ops-view-security-authentication.ts +20 -13
- package/ts_web/elements/security/ops-view-security-overview.ts +7 -12
- package/ts_web/router.ts +1 -1
|
@@ -18,6 +18,11 @@ type TAdminUser = {
|
|
|
18
18
|
authSources?: Array<'local' | 'idp.global'>;
|
|
19
19
|
};
|
|
20
20
|
|
|
21
|
+
type TAuthenticatedAdmin = {
|
|
22
|
+
user: TAdminUser;
|
|
23
|
+
authSource: 'local' | 'idp.global';
|
|
24
|
+
};
|
|
25
|
+
|
|
21
26
|
export class AdminHandler {
|
|
22
27
|
public typedrouter = new plugins.typedrequest.TypedRouter();
|
|
23
28
|
|
|
@@ -330,25 +335,59 @@ export class AdminHandler {
|
|
|
330
335
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_AdminLoginWithUsernameAndPassword>(
|
|
331
336
|
'adminLoginWithUsernameAndPassword',
|
|
332
337
|
async (dataArg) => {
|
|
338
|
+
const requestedAuthSource = dataArg.authSource || 'auto';
|
|
339
|
+
let authenticatedAdmin: TAuthenticatedAdmin | null;
|
|
333
340
|
try {
|
|
334
|
-
|
|
341
|
+
authenticatedAdmin = await this.authenticateUser({
|
|
335
342
|
username: dataArg.username,
|
|
336
343
|
password: dataArg.password,
|
|
337
|
-
authSource:
|
|
344
|
+
authSource: requestedAuthSource,
|
|
338
345
|
});
|
|
339
|
-
if (!user) {
|
|
340
|
-
throw new plugins.typedrequest.TypedResponseError('login failed');
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
return {
|
|
344
|
-
identity: await this.createIdentityForUser(user),
|
|
345
|
-
};
|
|
346
346
|
} catch (error) {
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
347
|
+
await this.recordAuthenticationAttempt({
|
|
348
|
+
username: dataArg.username,
|
|
349
|
+
success: false,
|
|
350
|
+
requestedAuthSource,
|
|
351
|
+
failureReason: String((error as Error).message || '').includes('database is not ready')
|
|
352
|
+
? 'serviceUnavailable'
|
|
353
|
+
: 'internalError',
|
|
354
|
+
});
|
|
350
355
|
throw new plugins.typedrequest.TypedResponseError('login failed');
|
|
351
356
|
}
|
|
357
|
+
|
|
358
|
+
if (!authenticatedAdmin) {
|
|
359
|
+
await this.recordAuthenticationAttempt({
|
|
360
|
+
username: dataArg.username,
|
|
361
|
+
success: false,
|
|
362
|
+
requestedAuthSource,
|
|
363
|
+
failureReason: 'invalidCredentials',
|
|
364
|
+
});
|
|
365
|
+
throw new plugins.typedrequest.TypedResponseError('login failed');
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
let identity: interfaces.data.IIdentity;
|
|
369
|
+
try {
|
|
370
|
+
identity = await this.createIdentityForUser(authenticatedAdmin.user);
|
|
371
|
+
} catch {
|
|
372
|
+
await this.recordAuthenticationAttempt({
|
|
373
|
+
username: dataArg.username,
|
|
374
|
+
userId: authenticatedAdmin.user.id,
|
|
375
|
+
success: false,
|
|
376
|
+
requestedAuthSource,
|
|
377
|
+
resolvedAuthSource: authenticatedAdmin.authSource,
|
|
378
|
+
failureReason: 'identityIssuanceFailed',
|
|
379
|
+
});
|
|
380
|
+
throw new plugins.typedrequest.TypedResponseError('login failed');
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
await this.recordAuthenticationAttempt({
|
|
384
|
+
username: dataArg.username,
|
|
385
|
+
userId: authenticatedAdmin.user.id,
|
|
386
|
+
success: true,
|
|
387
|
+
requestedAuthSource,
|
|
388
|
+
resolvedAuthSource: authenticatedAdmin.authSource,
|
|
389
|
+
});
|
|
390
|
+
return { identity };
|
|
352
391
|
}
|
|
353
392
|
)
|
|
354
393
|
);
|
|
@@ -492,7 +531,7 @@ export class AdminHandler {
|
|
|
492
531
|
username: string;
|
|
493
532
|
password: string;
|
|
494
533
|
authSource?: interfaces.requests.TAdminLoginAuthSource;
|
|
495
|
-
}): Promise<
|
|
534
|
+
}): Promise<TAuthenticatedAdmin | null> {
|
|
496
535
|
const accountState = await this.getPersistentAccountState();
|
|
497
536
|
if (accountState.dbEnabled && !accountState.dbReady) {
|
|
498
537
|
throw new plugins.typedrequest.TypedResponseError('database is not ready');
|
|
@@ -508,17 +547,37 @@ export class AdminHandler {
|
|
|
508
547
|
password: optionsArg.password,
|
|
509
548
|
authSource: optionsArg.authSource || 'auto',
|
|
510
549
|
});
|
|
511
|
-
return result
|
|
550
|
+
return result
|
|
551
|
+
? { user: this.accountToUser(result.account), authSource: result.authSource }
|
|
552
|
+
: null;
|
|
512
553
|
}
|
|
513
554
|
|
|
514
555
|
for (const [_, userData] of this.users) {
|
|
515
556
|
if (userData.username === optionsArg.username && userData.password === optionsArg.password) {
|
|
516
|
-
return userData;
|
|
557
|
+
return { user: userData, authSource: 'local' };
|
|
517
558
|
}
|
|
518
559
|
}
|
|
519
560
|
return null;
|
|
520
561
|
}
|
|
521
562
|
|
|
563
|
+
private async recordAuthenticationAttempt(optionsArg: {
|
|
564
|
+
username: string;
|
|
565
|
+
userId?: string;
|
|
566
|
+
success: boolean;
|
|
567
|
+
requestedAuthSource: interfaces.requests.TAdminLoginAuthSource;
|
|
568
|
+
resolvedAuthSource?: 'local' | 'idp.global';
|
|
569
|
+
failureReason?: interfaces.data.TAuthenticationFailureReason;
|
|
570
|
+
}): Promise<void> {
|
|
571
|
+
const eventManager = this.opsServerRef.dcRouterRef.authenticationEventManager;
|
|
572
|
+
if (!eventManager) return;
|
|
573
|
+
try {
|
|
574
|
+
await eventManager.recordAttempt(optionsArg);
|
|
575
|
+
} catch (error) {
|
|
576
|
+
// Authentication availability must not depend on telemetry persistence.
|
|
577
|
+
console.warn(`Authentication event recording unavailable: ${(error as Error).message}`);
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
|
|
522
581
|
private async resolveUser(userIdArg: string): Promise<TAdminUser | null> {
|
|
523
582
|
const accountState = await this.getPersistentAccountState();
|
|
524
583
|
if (accountState.dbEnabled && !accountState.dbReady) {
|
|
@@ -54,9 +54,19 @@ export class EmailSettingsHandler {
|
|
|
54
54
|
const mailerBridgeState = dcRouter.emailServer?.getBridgeState() as
|
|
55
55
|
| interfaces.data.TEmailBridgeState
|
|
56
56
|
| undefined;
|
|
57
|
+
const smtpAccountCount = dcRouter.smtpAccountManager?.getAccountCount() ?? 0;
|
|
58
|
+
const smtpListenerActive = dcRouter.emailServer
|
|
59
|
+
? dcRouter.emailServer.isSmtpListenerActive()
|
|
60
|
+
: undefined;
|
|
57
61
|
const manager = dcRouter.emailSettingsManager;
|
|
58
62
|
if (manager) {
|
|
59
|
-
return {
|
|
63
|
+
return {
|
|
64
|
+
...manager.getPublicSettings(),
|
|
65
|
+
outboundEgress,
|
|
66
|
+
mailerBridgeState,
|
|
67
|
+
smtpAccountCount,
|
|
68
|
+
...(smtpListenerActive !== undefined ? { smtpListenerActive } : {}),
|
|
69
|
+
};
|
|
60
70
|
}
|
|
61
71
|
const emailConfig = dcRouter.options.emailConfig;
|
|
62
72
|
const emailPortConfig = dcRouter.options.emailPortConfig;
|
|
@@ -75,6 +85,8 @@ export class EmailSettingsHandler {
|
|
|
75
85
|
domainCount: emailConfig?.domains?.length || 0,
|
|
76
86
|
routeCount: emailConfig?.routes?.length || 0,
|
|
77
87
|
authUserCount: emailConfig?.auth?.users?.length || 0,
|
|
88
|
+
smtpAccountCount,
|
|
89
|
+
...(smtpListenerActive !== undefined ? { smtpListenerActive } : {}),
|
|
78
90
|
updatedAt: 0,
|
|
79
91
|
updatedBy: 'runtime-options',
|
|
80
92
|
};
|
|
@@ -21,6 +21,7 @@ export * from './dns-authority.handler.js';
|
|
|
21
21
|
export * from './dns-record.handler.js';
|
|
22
22
|
export * from './acme-config.handler.js';
|
|
23
23
|
export * from './email-domain.handler.js';
|
|
24
|
+
export * from './smtp-account.handler.js';
|
|
24
25
|
export * from './gatewayclient.handler.js';
|
|
25
26
|
export * from './realtime.handler.js';
|
|
26
27
|
export * from './config-event.handler.js';
|
|
@@ -27,6 +27,9 @@ export class SecurityHandler {
|
|
|
27
27
|
malwareDetected: metrics.malwareDetected,
|
|
28
28
|
phishingDetected: metrics.phishingDetected,
|
|
29
29
|
authenticationFailures: metrics.authFailures,
|
|
30
|
+
authenticationSuccesses: metrics.authSuccesses,
|
|
31
|
+
authenticationWindowStart: metrics.authWindowStart,
|
|
32
|
+
authenticationEvents: metrics.authEvents,
|
|
30
33
|
suspiciousActivities: metrics.suspiciousActivities,
|
|
31
34
|
},
|
|
32
35
|
trends: dataArg.includeDetails ? {
|
|
@@ -309,6 +312,9 @@ export class SecurityHandler {
|
|
|
309
312
|
malwareDetected: number;
|
|
310
313
|
phishingDetected: number;
|
|
311
314
|
authFailures: number;
|
|
315
|
+
authSuccesses: number;
|
|
316
|
+
authWindowStart: number;
|
|
317
|
+
authEvents: interfaces.data.IAuthenticationEvent[];
|
|
312
318
|
suspiciousActivities: number;
|
|
313
319
|
trends: {
|
|
314
320
|
spam: Array<{ timestamp: number; value: number }>;
|
|
@@ -316,6 +322,15 @@ export class SecurityHandler {
|
|
|
316
322
|
phishing: Array<{ timestamp: number; value: number }>;
|
|
317
323
|
};
|
|
318
324
|
}> {
|
|
325
|
+
const authenticationEventManager = this.opsServerRef.dcRouterRef.authenticationEventManager;
|
|
326
|
+
const authentication = authenticationEventManager
|
|
327
|
+
? await authenticationEventManager.getWindowStats()
|
|
328
|
+
: {
|
|
329
|
+
windowStart: Date.now() - 86_400_000,
|
|
330
|
+
successes: 0,
|
|
331
|
+
failures: 0,
|
|
332
|
+
events: [],
|
|
333
|
+
};
|
|
319
334
|
// Get metrics from MetricsManager if available
|
|
320
335
|
if (this.opsServerRef.dcRouterRef.metricsManager) {
|
|
321
336
|
const securityStats = await this.opsServerRef.dcRouterRef.metricsManager.getSecurityStats();
|
|
@@ -328,7 +343,10 @@ export class SecurityHandler {
|
|
|
328
343
|
},
|
|
329
344
|
malwareDetected: securityStats.malwareDetected,
|
|
330
345
|
phishingDetected: securityStats.phishingDetected,
|
|
331
|
-
authFailures:
|
|
346
|
+
authFailures: authentication.failures,
|
|
347
|
+
authSuccesses: authentication.successes,
|
|
348
|
+
authWindowStart: authentication.windowStart,
|
|
349
|
+
authEvents: authentication.events,
|
|
332
350
|
suspiciousActivities: 0,
|
|
333
351
|
trends: {
|
|
334
352
|
spam: [],
|
|
@@ -348,7 +366,10 @@ export class SecurityHandler {
|
|
|
348
366
|
},
|
|
349
367
|
malwareDetected: 0,
|
|
350
368
|
phishingDetected: 0,
|
|
351
|
-
authFailures:
|
|
369
|
+
authFailures: authentication.failures,
|
|
370
|
+
authSuccesses: authentication.successes,
|
|
371
|
+
authWindowStart: authentication.windowStart,
|
|
372
|
+
authEvents: authentication.events,
|
|
352
373
|
suspiciousActivities: 0,
|
|
353
374
|
trends: {
|
|
354
375
|
spam: [],
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import * as plugins from '../../plugins.js';
|
|
2
|
+
import type { OpsServer } from '../classes.opsserver.js';
|
|
3
|
+
import * as interfaces from '../../../ts_interfaces/index.js';
|
|
4
|
+
import { requireOpsAuth } from '../helpers/auth.js';
|
|
5
|
+
import { logger } from '../../logger.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* CRUD handler for operator-managed SMTP submission accounts, plus the
|
|
9
|
+
* legacy-route retirement lever.
|
|
10
|
+
*
|
|
11
|
+
* Auth: admin JWT or API token with `smtp-accounts:read` / `smtp-accounts:write`
|
|
12
|
+
* scope; every write additionally requires an admin identity when a JWT is
|
|
13
|
+
* used. Successful admin actions are audit-logged to the ops log.
|
|
14
|
+
*/
|
|
15
|
+
export class SmtpAccountHandler {
|
|
16
|
+
public typedrouter = new plugins.typedrequest.TypedRouter();
|
|
17
|
+
|
|
18
|
+
constructor(private opsServerRef: OpsServer) {
|
|
19
|
+
this.opsServerRef.typedrouter.addTypedRouter(this.typedrouter);
|
|
20
|
+
this.registerHandlers();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
private async requireAuth(
|
|
24
|
+
request: { identity?: interfaces.data.IIdentity; apiToken?: string },
|
|
25
|
+
requiredScope: interfaces.data.TApiTokenScope,
|
|
26
|
+
): Promise<string> {
|
|
27
|
+
const auth = await requireOpsAuth(this.opsServerRef, request, {
|
|
28
|
+
scope: requiredScope,
|
|
29
|
+
requireAdminIdentity: requiredScope.endsWith(':write'),
|
|
30
|
+
});
|
|
31
|
+
return auth.userId;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
private get manager() {
|
|
35
|
+
return this.opsServerRef.dcRouterRef.smtpAccountManager;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
private failure(errorArg: unknown, fallbackMessageArg: string): interfaces.requests.ISmtpAccountActionResult {
|
|
39
|
+
return {
|
|
40
|
+
success: false,
|
|
41
|
+
message: errorArg instanceof Error ? errorArg.message : fallbackMessageArg,
|
|
42
|
+
code: 'SMTP_ACCOUNT_ACTION_FAILED',
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
private registerHandlers(): void {
|
|
47
|
+
this.typedrouter.addTypedHandler(
|
|
48
|
+
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetSmtpAccounts>(
|
|
49
|
+
'getSmtpAccounts',
|
|
50
|
+
async (dataArg) => {
|
|
51
|
+
await this.requireAuth(dataArg, 'smtp-accounts:read');
|
|
52
|
+
if (!this.manager?.isStarted()) return { accounts: [] };
|
|
53
|
+
return { accounts: await this.manager.listAccounts() };
|
|
54
|
+
},
|
|
55
|
+
),
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
this.typedrouter.addTypedHandler(
|
|
59
|
+
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_CreateSmtpAccount>(
|
|
60
|
+
'createSmtpAccount',
|
|
61
|
+
async (dataArg) => {
|
|
62
|
+
const userId = await this.requireAuth(dataArg, 'smtp-accounts:write');
|
|
63
|
+
if (!this.manager?.isStarted()) {
|
|
64
|
+
return { success: false, message: 'SmtpAccountManager not initialized', code: 'SMTP_ACCOUNT_MANAGER_UNAVAILABLE' };
|
|
65
|
+
}
|
|
66
|
+
try {
|
|
67
|
+
const result = await this.manager.createAccount({
|
|
68
|
+
username: dataArg.username,
|
|
69
|
+
description: dataArg.description,
|
|
70
|
+
senderScope: dataArg.senderScope,
|
|
71
|
+
recipientScope: dataArg.recipientScope,
|
|
72
|
+
mailPolicy: dataArg.mailPolicy,
|
|
73
|
+
createdBy: userId,
|
|
74
|
+
});
|
|
75
|
+
return {
|
|
76
|
+
success: true,
|
|
77
|
+
warnings: result.warnings.length > 0 ? result.warnings : undefined,
|
|
78
|
+
account: result.account,
|
|
79
|
+
password: result.password,
|
|
80
|
+
};
|
|
81
|
+
} catch (err: unknown) {
|
|
82
|
+
return this.failure(err, 'Failed to create SMTP account');
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
),
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
this.typedrouter.addTypedHandler(
|
|
89
|
+
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_UpdateSmtpAccount>(
|
|
90
|
+
'updateSmtpAccount',
|
|
91
|
+
async (dataArg) => {
|
|
92
|
+
const userId = await this.requireAuth(dataArg, 'smtp-accounts:write');
|
|
93
|
+
if (!this.manager?.isStarted()) {
|
|
94
|
+
return { success: false, message: 'SmtpAccountManager not initialized', code: 'SMTP_ACCOUNT_MANAGER_UNAVAILABLE' };
|
|
95
|
+
}
|
|
96
|
+
try {
|
|
97
|
+
const result = await this.manager.updateAccount(dataArg.id, {
|
|
98
|
+
description: dataArg.description,
|
|
99
|
+
senderScope: dataArg.senderScope,
|
|
100
|
+
recipientScope: dataArg.recipientScope,
|
|
101
|
+
mailPolicy: dataArg.mailPolicy,
|
|
102
|
+
}, userId);
|
|
103
|
+
return {
|
|
104
|
+
success: true,
|
|
105
|
+
warnings: result.warnings.length > 0 ? result.warnings : undefined,
|
|
106
|
+
account: result.account,
|
|
107
|
+
};
|
|
108
|
+
} catch (err: unknown) {
|
|
109
|
+
return this.failure(err, 'Failed to update SMTP account');
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
),
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
this.typedrouter.addTypedHandler(
|
|
116
|
+
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_ToggleSmtpAccount>(
|
|
117
|
+
'toggleSmtpAccount',
|
|
118
|
+
async (dataArg) => {
|
|
119
|
+
const userId = await this.requireAuth(dataArg, 'smtp-accounts:write');
|
|
120
|
+
if (!this.manager?.isStarted()) {
|
|
121
|
+
return { success: false, message: 'SmtpAccountManager not initialized', code: 'SMTP_ACCOUNT_MANAGER_UNAVAILABLE' };
|
|
122
|
+
}
|
|
123
|
+
try {
|
|
124
|
+
const result = await this.manager.toggleAccount(dataArg.id, dataArg.enabled, userId);
|
|
125
|
+
return { success: true, account: result.account };
|
|
126
|
+
} catch (err: unknown) {
|
|
127
|
+
return this.failure(err, 'Failed to toggle SMTP account');
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
),
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
this.typedrouter.addTypedHandler(
|
|
134
|
+
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_RotateSmtpAccountPassword>(
|
|
135
|
+
'rotateSmtpAccountPassword',
|
|
136
|
+
async (dataArg) => {
|
|
137
|
+
const userId = await this.requireAuth(dataArg, 'smtp-accounts:write');
|
|
138
|
+
if (!this.manager?.isStarted()) {
|
|
139
|
+
return { success: false, message: 'SmtpAccountManager not initialized', code: 'SMTP_ACCOUNT_MANAGER_UNAVAILABLE' };
|
|
140
|
+
}
|
|
141
|
+
try {
|
|
142
|
+
const result = await this.manager.rotatePassword(dataArg.id, userId);
|
|
143
|
+
return { success: true, account: result.account, password: result.password };
|
|
144
|
+
} catch (err: unknown) {
|
|
145
|
+
return this.failure(err, 'Failed to rotate SMTP account password');
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
),
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
this.typedrouter.addTypedHandler(
|
|
152
|
+
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_DeleteSmtpAccount>(
|
|
153
|
+
'deleteSmtpAccount',
|
|
154
|
+
async (dataArg) => {
|
|
155
|
+
const userId = await this.requireAuth(dataArg, 'smtp-accounts:write');
|
|
156
|
+
if (!this.manager?.isStarted()) {
|
|
157
|
+
return { success: false, message: 'SmtpAccountManager not initialized', code: 'SMTP_ACCOUNT_MANAGER_UNAVAILABLE' };
|
|
158
|
+
}
|
|
159
|
+
try {
|
|
160
|
+
await this.manager.deleteAccount(dataArg.id, userId);
|
|
161
|
+
return { success: true };
|
|
162
|
+
} catch (err: unknown) {
|
|
163
|
+
return this.failure(err, 'Failed to delete SMTP account');
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
),
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
// Internal-relay retirement lever: removes one persisted legacy route by
|
|
170
|
+
// exact name through the standard settings restart path. Guarded in
|
|
171
|
+
// EmailSettingsManager (unknown names and generated managed names fail).
|
|
172
|
+
this.typedrouter.addTypedHandler(
|
|
173
|
+
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_RemoveLegacyEmailRoute>(
|
|
174
|
+
'removeLegacyEmailRoute',
|
|
175
|
+
async (dataArg) => {
|
|
176
|
+
const userId = await this.requireAuth(dataArg, 'smtp-accounts:write');
|
|
177
|
+
const routeName = (dataArg.routeName || '').trim();
|
|
178
|
+
logger.log('info', `removeLegacyEmailRoute invoked by ${userId} for route '${routeName}'`);
|
|
179
|
+
if (!routeName) {
|
|
180
|
+
return { success: false, message: 'routeName is required', code: 'SMTP_ACCOUNT_ACTION_FAILED' };
|
|
181
|
+
}
|
|
182
|
+
if (!this.opsServerRef.dcRouterRef.emailSettingsManager) {
|
|
183
|
+
return { success: false, message: 'EmailSettingsManager not initialized', code: 'EMAIL_SETTINGS_MANAGER_UNAVAILABLE' };
|
|
184
|
+
}
|
|
185
|
+
try {
|
|
186
|
+
await this.opsServerRef.dcRouterRef.updateEmailServerSettings(
|
|
187
|
+
{ removeRouteNames: [routeName] },
|
|
188
|
+
userId,
|
|
189
|
+
);
|
|
190
|
+
logger.log('info', `Legacy email route '${routeName}' removed from persisted settings by ${userId}`);
|
|
191
|
+
return { success: true, message: `Route '${routeName}' removed` };
|
|
192
|
+
} catch (err: unknown) {
|
|
193
|
+
return this.failure(err, 'Failed to remove legacy email route');
|
|
194
|
+
}
|
|
195
|
+
},
|
|
196
|
+
),
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
@@ -66,10 +66,10 @@ export class StatsHandler {
|
|
|
66
66
|
stats: {
|
|
67
67
|
sent: stats.sentToday,
|
|
68
68
|
received: stats.receivedToday,
|
|
69
|
-
bounced:
|
|
69
|
+
bounced: stats.bouncedToday,
|
|
70
70
|
queued: stats.queueSize,
|
|
71
|
-
failed:
|
|
72
|
-
averageDeliveryTime:
|
|
71
|
+
failed: stats.failedToday,
|
|
72
|
+
averageDeliveryTime: stats.averageDeliveryTime,
|
|
73
73
|
deliveryRate: stats.deliveryRate,
|
|
74
74
|
bounceRate: stats.bounceRate,
|
|
75
75
|
},
|
|
@@ -219,10 +219,10 @@ export class StatsHandler {
|
|
|
219
219
|
metrics.email = {
|
|
220
220
|
sent: stats.sentToday,
|
|
221
221
|
received: stats.receivedToday,
|
|
222
|
-
bounced:
|
|
222
|
+
bounced: stats.bouncedToday,
|
|
223
223
|
queued: stats.queueSize,
|
|
224
|
-
failed:
|
|
225
|
-
averageDeliveryTime:
|
|
224
|
+
failed: stats.failedToday,
|
|
225
|
+
averageDeliveryTime: stats.averageDeliveryTime,
|
|
226
226
|
deliveryRate: stats.deliveryRate,
|
|
227
227
|
bounceRate: stats.bounceRate,
|
|
228
228
|
timeSeries,
|
|
@@ -256,7 +256,15 @@ export class StatsHandler {
|
|
|
256
256
|
|
|
257
257
|
if (sections.security && this.opsServerRef.dcRouterRef.metricsManager) {
|
|
258
258
|
promises.push(
|
|
259
|
-
|
|
259
|
+
Promise.all([
|
|
260
|
+
this.opsServerRef.dcRouterRef.metricsManager.getSecurityStats(),
|
|
261
|
+
this.opsServerRef.dcRouterRef.authenticationEventManager
|
|
262
|
+
? this.opsServerRef.dcRouterRef.authenticationEventManager.getWindowStats()
|
|
263
|
+
: Promise.resolve({
|
|
264
|
+
windowStart: Date.now() - 86_400_000,
|
|
265
|
+
successes: 0, failures: 0, events: [],
|
|
266
|
+
}),
|
|
267
|
+
]).then(([stats, authentication]) => {
|
|
260
268
|
// Get recent events from the SecurityLogger singleton
|
|
261
269
|
const securityLogger = SecurityLogger.getInstance();
|
|
262
270
|
const recentEvents = securityLogger.getRecentEvents(50).map((evt) => ({
|
|
@@ -276,7 +284,10 @@ export class StatsHandler {
|
|
|
276
284
|
spamDetected: stats.spamDetected,
|
|
277
285
|
malwareDetected: stats.malwareDetected,
|
|
278
286
|
phishingDetected: stats.phishingDetected,
|
|
279
|
-
authenticationFailures:
|
|
287
|
+
authenticationFailures: authentication.failures,
|
|
288
|
+
authenticationSuccesses: authentication.successes,
|
|
289
|
+
authenticationWindowStart: authentication.windowStart,
|
|
290
|
+
authenticationEvents: authentication.events,
|
|
280
291
|
suspiciousActivities: stats.totalThreatsBlocked,
|
|
281
292
|
recentEvents,
|
|
282
293
|
};
|
|
@@ -463,9 +474,12 @@ export class StatsHandler {
|
|
|
463
474
|
sentToday: number;
|
|
464
475
|
receivedToday: number;
|
|
465
476
|
bounceRate: number;
|
|
477
|
+
failedToday: number;
|
|
478
|
+
bouncedToday: number;
|
|
466
479
|
deliveryRate: number;
|
|
467
480
|
queueSize: number;
|
|
468
481
|
domainBreakdown?: { [domain: string]: interfaces.data.IEmailStats };
|
|
482
|
+
averageDeliveryTime: number;
|
|
469
483
|
}> {
|
|
470
484
|
// Get metrics from MetricsManager if available
|
|
471
485
|
if (this.opsServerRef.dcRouterRef.metricsManager) {
|
|
@@ -474,8 +488,11 @@ export class StatsHandler {
|
|
|
474
488
|
sentToday: emailStats.sentToday,
|
|
475
489
|
receivedToday: emailStats.receivedToday,
|
|
476
490
|
bounceRate: emailStats.bounceRate,
|
|
491
|
+
failedToday: emailStats.failedToday,
|
|
492
|
+
bouncedToday: emailStats.bouncedToday,
|
|
477
493
|
deliveryRate: emailStats.deliveryRate,
|
|
478
494
|
queueSize: emailStats.queueSize,
|
|
495
|
+
averageDeliveryTime: emailStats.averageDeliveryTime,
|
|
479
496
|
};
|
|
480
497
|
}
|
|
481
498
|
|
|
@@ -484,8 +501,11 @@ export class StatsHandler {
|
|
|
484
501
|
sentToday: 0,
|
|
485
502
|
receivedToday: 0,
|
|
486
503
|
bounceRate: 0,
|
|
487
|
-
deliveryRate:
|
|
504
|
+
deliveryRate: 1,
|
|
505
|
+
failedToday: 0,
|
|
506
|
+
bouncedToday: 0,
|
|
488
507
|
queueSize: 0,
|
|
508
|
+
averageDeliveryTime: 0,
|
|
489
509
|
};
|
|
490
510
|
}
|
|
491
511
|
|