@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.
Files changed (103) hide show
  1. package/deno.json +1 -1
  2. package/dist_serve/bundle.js +1529 -1382
  3. package/dist_ts/00_commitinfo_data.js +1 -1
  4. package/dist_ts/classes.dcrouter.d.ts +7 -2
  5. package/dist_ts/classes.dcrouter.js +61 -9
  6. package/dist_ts/db/documents/classes.authentication-event.doc.d.ts +25 -0
  7. package/dist_ts/db/documents/classes.authentication-event.doc.js +273 -0
  8. package/dist_ts/db/documents/classes.email-traffic-bucket.doc.d.ts +25 -0
  9. package/dist_ts/db/documents/classes.email-traffic-bucket.doc.js +200 -0
  10. package/dist_ts/db/documents/classes.smtp-account.doc.d.ts +31 -0
  11. package/dist_ts/db/documents/classes.smtp-account.doc.js +176 -0
  12. package/dist_ts/db/documents/index.d.ts +3 -0
  13. package/dist_ts/db/documents/index.js +4 -1
  14. package/dist_ts/email/classes.accepted-email-spool.d.ts +1 -0
  15. package/dist_ts/email/classes.accepted-email-spool.js +9 -1
  16. package/dist_ts/email/classes.email-settings.manager.d.ts +7 -0
  17. package/dist_ts/email/classes.email-settings.manager.js +30 -1
  18. package/dist_ts/email/classes.smtp-account.manager.d.ts +110 -0
  19. package/dist_ts/email/classes.smtp-account.manager.js +507 -0
  20. package/dist_ts/email/classes.workapp-mail-manager.d.ts +23 -4
  21. package/dist_ts/email/classes.workapp-mail-manager.js +39 -46
  22. package/dist_ts/email/index.d.ts +2 -0
  23. package/dist_ts/email/index.js +3 -1
  24. package/dist_ts/email/smtp-scram.d.ts +13 -0
  25. package/dist_ts/email/smtp-scram.js +19 -0
  26. package/dist_ts/monitoring/classes.metricsmanager.d.ts +19 -0
  27. package/dist_ts/monitoring/classes.metricsmanager.js +240 -44
  28. package/dist_ts/opsserver/classes.opsserver.d.ts +1 -0
  29. package/dist_ts/opsserver/classes.opsserver.js +3 -1
  30. package/dist_ts/opsserver/handlers/admin.handler.d.ts +1 -0
  31. package/dist_ts/opsserver/handlers/admin.handler.js +61 -14
  32. package/dist_ts/opsserver/handlers/email-settings.handler.js +14 -2
  33. package/dist_ts/opsserver/handlers/index.d.ts +1 -0
  34. package/dist_ts/opsserver/handlers/index.js +2 -1
  35. package/dist_ts/opsserver/handlers/security.handler.js +21 -3
  36. package/dist_ts/opsserver/handlers/smtp-account.handler.d.ts +20 -0
  37. package/dist_ts/opsserver/handlers/smtp-account.handler.js +155 -0
  38. package/dist_ts/opsserver/handlers/stats.handler.js +27 -10
  39. package/dist_ts/security/classes.authentication-event-manager.d.ts +35 -0
  40. package/dist_ts/security/classes.authentication-event-manager.js +185 -0
  41. package/dist_ts/security/index.d.ts +1 -0
  42. package/dist_ts/security/index.js +2 -1
  43. package/dist_ts_interfaces/data/email-settings.d.ts +10 -0
  44. package/dist_ts_interfaces/data/index.d.ts +1 -0
  45. package/dist_ts_interfaces/data/index.js +2 -1
  46. package/dist_ts_interfaces/data/route-management.d.ts +1 -1
  47. package/dist_ts_interfaces/data/route-management.js +3 -1
  48. package/dist_ts_interfaces/data/smtp-account.d.ts +50 -0
  49. package/dist_ts_interfaces/data/smtp-account.js +9 -0
  50. package/dist_ts_interfaces/data/stats.d.ts +16 -0
  51. package/dist_ts_interfaces/requests/index.d.ts +1 -0
  52. package/dist_ts_interfaces/requests/index.js +2 -1
  53. package/dist_ts_interfaces/requests/smtp-accounts.d.ts +125 -0
  54. package/dist_ts_interfaces/requests/smtp-accounts.js +2 -0
  55. package/dist_ts_migrations/index.js +186 -13
  56. package/dist_ts_web/00_commitinfo_data.js +1 -1
  57. package/dist_ts_web/appstate/smtp-accounts.d.ts +38 -0
  58. package/dist_ts_web/appstate/smtp-accounts.js +94 -0
  59. package/dist_ts_web/appstate.d.ts +1 -0
  60. package/dist_ts_web/appstate.js +2 -1
  61. package/dist_ts_web/elements/email/index.d.ts +1 -0
  62. package/dist_ts_web/elements/email/index.js +2 -1
  63. package/dist_ts_web/elements/email/ops-view-smtp-accounts.d.ts +25 -0
  64. package/dist_ts_web/elements/email/ops-view-smtp-accounts.js +598 -0
  65. package/dist_ts_web/elements/ops-dashboard.js +3 -1
  66. package/dist_ts_web/elements/overview/ops-view-overview.js +3 -2
  67. package/dist_ts_web/elements/security/ops-view-security-authentication.js +20 -13
  68. package/dist_ts_web/elements/security/ops-view-security-overview.js +8 -11
  69. package/dist_ts_web/router.js +2 -2
  70. package/package.json +2 -2
  71. package/readme.md +20 -0
  72. package/ts/00_commitinfo_data.ts +1 -1
  73. package/ts/classes.dcrouter.ts +66 -8
  74. package/ts/db/documents/classes.authentication-event.doc.ts +248 -0
  75. package/ts/db/documents/classes.email-traffic-bucket.doc.ts +161 -0
  76. package/ts/db/documents/classes.smtp-account.doc.ts +103 -0
  77. package/ts/db/documents/index.ts +3 -0
  78. package/ts/email/classes.accepted-email-spool.ts +8 -0
  79. package/ts/email/classes.email-settings.manager.ts +34 -0
  80. package/ts/email/classes.smtp-account.manager.ts +601 -0
  81. package/ts/email/classes.workapp-mail-manager.ts +53 -55
  82. package/ts/email/index.ts +2 -0
  83. package/ts/email/smtp-scram.ts +24 -0
  84. package/ts/monitoring/classes.metricsmanager.ts +287 -50
  85. package/ts/opsserver/classes.opsserver.ts +2 -0
  86. package/ts/opsserver/handlers/admin.handler.ts +74 -15
  87. package/ts/opsserver/handlers/email-settings.handler.ts +13 -1
  88. package/ts/opsserver/handlers/index.ts +1 -0
  89. package/ts/opsserver/handlers/security.handler.ts +23 -2
  90. package/ts/opsserver/handlers/smtp-account.handler.ts +199 -0
  91. package/ts/opsserver/handlers/stats.handler.ts +29 -9
  92. package/ts/security/classes.authentication-event-manager.ts +221 -0
  93. package/ts/security/index.ts +1 -0
  94. package/ts_web/00_commitinfo_data.ts +1 -1
  95. package/ts_web/appstate/smtp-accounts.ts +143 -0
  96. package/ts_web/appstate.ts +1 -0
  97. package/ts_web/elements/email/index.ts +1 -0
  98. package/ts_web/elements/email/ops-view-smtp-accounts.ts +586 -0
  99. package/ts_web/elements/ops-dashboard.ts +2 -0
  100. package/ts_web/elements/overview/ops-view-overview.ts +2 -1
  101. package/ts_web/elements/security/ops-view-security-authentication.ts +20 -13
  102. package/ts_web/elements/security/ops-view-security-overview.ts +7 -12
  103. package/ts_web/router.ts +1 -1
@@ -0,0 +1,221 @@
1
+ import * as plugins from '../plugins.js';
2
+ import { logger } from '../logger.js';
3
+ import type { DcRouter } from '../classes.dcrouter.js';
4
+ import { AuthenticationEventDoc } from '../db/documents/classes.authentication-event.doc.js';
5
+ import type {
6
+ IAuthenticationEvent,
7
+ TAuthenticationFailureReason,
8
+ TAuthenticationSource,
9
+ TResolvedAuthenticationSource,
10
+ } from '../../ts_interfaces/data/stats.js';
11
+ import {
12
+ SecurityEventType,
13
+ SecurityLogLevel,
14
+ SecurityLogger,
15
+ } from './classes.securitylogger.js';
16
+
17
+ const MAX_IN_MEMORY_EVENTS = 10_000;
18
+ const MAX_PERSIST_BATCH_SIZE = 500;
19
+
20
+ export interface IAuthenticationWindowStats {
21
+ windowStart: number;
22
+ successes: number;
23
+ failures: number;
24
+ events: IAuthenticationEvent[];
25
+ }
26
+
27
+ export class AuthenticationEventManager {
28
+ private pendingEvents = new Map<string, IAuthenticationEvent>();
29
+ private recentEvents = new Map<string, IAuthenticationEvent>();
30
+ private flushInFlight?: Promise<void>;
31
+ private pruneInFlight?: Promise<number>;
32
+ private windowStatsInFlight = new Map<string, Promise<IAuthenticationWindowStats>>();
33
+
34
+ constructor(private dcRouterRef: DcRouter) {}
35
+
36
+ private canPersist(): boolean {
37
+ return this.dcRouterRef.options.dbConfig?.enabled !== false
38
+ && Boolean(this.dcRouterRef.dcRouterDb?.isReady());
39
+ }
40
+
41
+ private rememberEvent(eventArg: IAuthenticationEvent): void {
42
+ this.pendingEvents.set(eventArg.id, eventArg);
43
+ this.recentEvents.set(eventArg.id, eventArg);
44
+ while (this.pendingEvents.size > MAX_IN_MEMORY_EVENTS) {
45
+ const oldestId = this.pendingEvents.keys().next().value as string | undefined;
46
+ if (!oldestId) break;
47
+ this.pendingEvents.delete(oldestId);
48
+ logger.log('error', 'Authentication event persistence buffer reached its limit; oldest event dropped');
49
+ }
50
+ while (this.recentEvents.size > MAX_IN_MEMORY_EVENTS) {
51
+ const oldestId = this.recentEvents.keys().next().value as string | undefined;
52
+ if (!oldestId) break;
53
+ this.recentEvents.delete(oldestId);
54
+ }
55
+ }
56
+
57
+ private async flushBufferedUnlocked(): Promise<void> {
58
+ if (!this.canPersist() || this.pendingEvents.size === 0) return;
59
+ const events = [...this.pendingEvents.values()];
60
+ for (let offset = 0; offset < events.length; offset += MAX_PERSIST_BATCH_SIZE) {
61
+ const batch = events.slice(offset, offset + MAX_PERSIST_BATCH_SIZE);
62
+ await AuthenticationEventDoc.upsertMany(batch);
63
+ for (const event of batch) {
64
+ if (this.pendingEvents.get(event.id) === event) {
65
+ this.pendingEvents.delete(event.id);
66
+ }
67
+ }
68
+ }
69
+ }
70
+
71
+ public async recordAttempt(optionsArg: {
72
+ username: string;
73
+ userId?: string;
74
+ success: boolean;
75
+ requestedAuthSource: TAuthenticationSource;
76
+ resolvedAuthSource?: TResolvedAuthenticationSource;
77
+ failureReason?: TAuthenticationFailureReason;
78
+ }): Promise<IAuthenticationEvent> {
79
+ const event: IAuthenticationEvent = {
80
+ id: `auth-${plugins.uuid.v4()}`,
81
+ timestamp: Date.now(),
82
+ username: String(optionsArg.username || '').trim().toLowerCase() || 'unknown',
83
+ success: optionsArg.success,
84
+ requestedAuthSource: optionsArg.requestedAuthSource,
85
+ ...(optionsArg.userId ? { userId: optionsArg.userId } : {}),
86
+ ...(optionsArg.resolvedAuthSource
87
+ ? { resolvedAuthSource: optionsArg.resolvedAuthSource }
88
+ : {}),
89
+ ...(optionsArg.failureReason ? { failureReason: optionsArg.failureReason } : {}),
90
+ };
91
+ // Buffer before any asynchronous persistence so authentication cannot be
92
+ // held behind a slow database and the bounded drop policy remains effective.
93
+ this.rememberEvent(event);
94
+ try {
95
+ SecurityLogger.getInstance().logEvent({
96
+ level: event.success ? SecurityLogLevel.INFO : SecurityLogLevel.WARN,
97
+ type: SecurityEventType.AUTHENTICATION,
98
+ message: event.success ? 'Authentication succeeded' : 'Authentication failed',
99
+ userId: event.userId,
100
+ success: event.success,
101
+ details: { authenticationEvent: event },
102
+ });
103
+ } catch (error) {
104
+ logger.log('warn', `Unable to mirror authentication event to the security log: ${(error as Error).message}`);
105
+ }
106
+ return event;
107
+ }
108
+
109
+ public flushBuffered(): Promise<void> {
110
+ if (this.flushInFlight) return this.flushInFlight;
111
+ const run = this.flushBufferedUnlocked();
112
+ this.flushInFlight = run;
113
+ run.then(
114
+ () => { if (this.flushInFlight === run) this.flushInFlight = undefined; },
115
+ () => { if (this.flushInFlight === run) this.flushInFlight = undefined; },
116
+ );
117
+ return run;
118
+ }
119
+
120
+ public pruneBefore(cutoffArg: number): Promise<number> {
121
+ if (this.pruneInFlight) return this.pruneInFlight;
122
+ const run = this.canPersist()
123
+ ? AuthenticationEventDoc.pruneBefore(cutoffArg)
124
+ : Promise.resolve(0);
125
+ this.pruneInFlight = run;
126
+ run.then(
127
+ () => { if (this.pruneInFlight === run) this.pruneInFlight = undefined; },
128
+ () => { if (this.pruneInFlight === run) this.pruneInFlight = undefined; },
129
+ );
130
+ return run;
131
+ }
132
+
133
+ private getInMemoryWindow(cutoffArg: number, limitArg: number): IAuthenticationWindowStats {
134
+ const events = [...this.recentEvents.values()]
135
+ .filter((event) => event.timestamp >= cutoffArg)
136
+ .sort((a, b) => b.timestamp - a.timestamp || b.id.localeCompare(a.id));
137
+ return {
138
+ windowStart: cutoffArg,
139
+ successes: events.filter((event) => event.success).length,
140
+ failures: events.filter((event) => !event.success).length,
141
+ events: events.slice(0, limitArg),
142
+ };
143
+ }
144
+
145
+ private async getWindowStatsUnlocked(
146
+ windowMsArg = 24 * 60 * 60 * 1000,
147
+ limitArg = 100,
148
+ ): Promise<IAuthenticationWindowStats> {
149
+ const cutoff = Date.now() - windowMsArg;
150
+ if (!this.canPersist()) {
151
+ return this.getInMemoryWindow(cutoff, limitArg);
152
+ }
153
+
154
+ try {
155
+ await this.flushBuffered();
156
+ } catch (error) {
157
+ logger.log('warn', `Unable to flush authentication event buffer: ${(error as Error).message}`);
158
+ }
159
+
160
+ try {
161
+ const durable = await AuthenticationEventDoc.getWindowSummary(cutoff, limitArg);
162
+ const buffered = [...this.pendingEvents.values()]
163
+ .filter((event) => event.timestamp >= cutoff);
164
+ const existingIds = new Set<string>();
165
+ const bufferedIds = buffered.map((event) => event.id);
166
+ for (let offset = 0; offset < bufferedIds.length; offset += MAX_PERSIST_BATCH_SIZE) {
167
+ const existingBatch = await AuthenticationEventDoc.findExistingIds(
168
+ bufferedIds.slice(offset, offset + MAX_PERSIST_BATCH_SIZE),
169
+ );
170
+ existingBatch.forEach((id) => existingIds.add(id));
171
+ }
172
+ const unpersisted = buffered.filter((event) => !existingIds.has(event.id));
173
+ const mergedById = new Map<string, IAuthenticationEvent>();
174
+ for (const event of durable.events) mergedById.set(event.id, event);
175
+ for (const event of unpersisted) {
176
+ if (!mergedById.has(event.id)) mergedById.set(event.id, event);
177
+ }
178
+ const mergedEvents = [...mergedById.values()]
179
+ .sort((a, b) => b.timestamp - a.timestamp || b.id.localeCompare(a.id))
180
+ .slice(0, limitArg);
181
+ return {
182
+ windowStart: cutoff,
183
+ successes: durable.successes + unpersisted.filter((event) => event.success).length,
184
+ failures: durable.failures + unpersisted.filter((event) => !event.success).length,
185
+ events: mergedEvents,
186
+ };
187
+ } catch (error) {
188
+ logger.log('warn', `Unable to query durable authentication events: ${(error as Error).message}`);
189
+ return this.getInMemoryWindow(cutoff, limitArg);
190
+ }
191
+ }
192
+
193
+ public getWindowStats(
194
+ windowMsArg = 24 * 60 * 60 * 1000,
195
+ limitArg = 100,
196
+ ): Promise<IAuthenticationWindowStats> {
197
+ if (!Number.isSafeInteger(windowMsArg) || windowMsArg <= 0) {
198
+ return Promise.reject(new Error('Authentication window must be a positive integer'));
199
+ }
200
+ if (!Number.isSafeInteger(limitArg) || limitArg <= 0 || limitArg > 500) {
201
+ return Promise.reject(new Error('Authentication event limit must be from 1 to 500'));
202
+ }
203
+ const key = `${windowMsArg}:${limitArg}`;
204
+ const existing = this.windowStatsInFlight.get(key);
205
+ if (existing) return existing;
206
+ const run = this.getWindowStatsUnlocked(windowMsArg, limitArg);
207
+ this.windowStatsInFlight.set(key, run);
208
+ run.then(
209
+ () => { if (this.windowStatsInFlight.get(key) === run) this.windowStatsInFlight.delete(key); },
210
+ () => { if (this.windowStatsInFlight.get(key) === run) this.windowStatsInFlight.delete(key); },
211
+ );
212
+ return run;
213
+ }
214
+
215
+ /** Test-only reset for the DcRouter-owned in-process buffers. */
216
+ public async resetForTests(): Promise<void> {
217
+ await this.flushInFlight?.catch(() => undefined);
218
+ this.pendingEvents.clear();
219
+ this.recentEvents.clear();
220
+ }
221
+ }
@@ -26,3 +26,4 @@ export {
26
26
  type IRemoteIngressFirewallSnapshot,
27
27
  } from './classes.security-policy-manager.js';
28
28
  export * from './classes.route-policy-augmenter.js';
29
+ export * from './classes.authentication-event-manager.js';
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@serve.zone/dcrouter',
6
- version: '18.0.2',
6
+ version: '18.1.1',
7
7
  description: 'A multifaceted routing service handling mail and SMS delivery functions.'
8
8
  }
@@ -0,0 +1,143 @@
1
+ import * as plugins from '../plugins.js';
2
+ import * as interfaces from '../../ts_interfaces/index.js';
3
+ import { appState } from './shared.js';
4
+ import { getActionContext } from './login.js';
5
+
6
+ // ============================================================================
7
+ // SMTP Accounts State
8
+ // ============================================================================
9
+
10
+ export interface ISmtpAccountsState {
11
+ accounts: interfaces.data.ISmtpAccountInfo[];
12
+ isLoading: boolean;
13
+ error: string | null;
14
+ lastUpdated: number;
15
+ }
16
+
17
+ export const smtpAccountsStatePart = await appState.getStatePart<ISmtpAccountsState>(
18
+ 'smtpAccounts',
19
+ {
20
+ accounts: [],
21
+ isLoading: false,
22
+ error: null,
23
+ lastUpdated: 0,
24
+ },
25
+ 'soft',
26
+ );
27
+
28
+ export const fetchSmtpAccountsAction = smtpAccountsStatePart.createAction(
29
+ async (statePartArg): Promise<ISmtpAccountsState> => {
30
+ const context = getActionContext();
31
+ const currentState = statePartArg.getState()!;
32
+ if (!context.identity) return currentState;
33
+ try {
34
+ const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
35
+ interfaces.requests.IReq_GetSmtpAccounts
36
+ >('/typedrequest', 'getSmtpAccounts');
37
+ const response = await request.fire({ identity: context.identity });
38
+ return {
39
+ ...statePartArg.getState()!,
40
+ accounts: response.accounts,
41
+ isLoading: false,
42
+ error: null,
43
+ lastUpdated: Date.now(),
44
+ };
45
+ } catch (error: unknown) {
46
+ return {
47
+ ...statePartArg.getState()!,
48
+ isLoading: false,
49
+ error: error instanceof Error ? error.message : 'Failed to fetch SMTP accounts',
50
+ };
51
+ }
52
+ },
53
+ );
54
+
55
+ export const toggleSmtpAccountAction = smtpAccountsStatePart.createAction<{
56
+ id: string;
57
+ enabled: boolean;
58
+ }>(async (statePartArg, dataArg, actionContext): Promise<ISmtpAccountsState> => {
59
+ const context = getActionContext();
60
+ const currentState = statePartArg.getState()!;
61
+ try {
62
+ const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
63
+ interfaces.requests.IReq_ToggleSmtpAccount
64
+ >('/typedrequest', 'toggleSmtpAccount');
65
+ const response = await request.fire({
66
+ identity: context.identity!,
67
+ id: dataArg.id,
68
+ enabled: dataArg.enabled,
69
+ });
70
+ if (!response.success) {
71
+ return { ...currentState, error: response.message || 'Failed to toggle SMTP account' };
72
+ }
73
+ return await actionContext!.dispatch(fetchSmtpAccountsAction, null);
74
+ } catch (error: unknown) {
75
+ return {
76
+ ...currentState,
77
+ error: error instanceof Error ? error.message : 'Failed to toggle SMTP account',
78
+ };
79
+ }
80
+ });
81
+
82
+ export const deleteSmtpAccountAction = smtpAccountsStatePart.createAction<string>(
83
+ async (statePartArg, id, actionContext): Promise<ISmtpAccountsState> => {
84
+ const context = getActionContext();
85
+ const currentState = statePartArg.getState()!;
86
+ try {
87
+ const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
88
+ interfaces.requests.IReq_DeleteSmtpAccount
89
+ >('/typedrequest', 'deleteSmtpAccount');
90
+ const response = await request.fire({ identity: context.identity!, id });
91
+ if (!response.success) {
92
+ return { ...currentState, error: response.message || 'Failed to delete SMTP account' };
93
+ }
94
+ return await actionContext!.dispatch(fetchSmtpAccountsAction, null);
95
+ } catch (error: unknown) {
96
+ return {
97
+ ...currentState,
98
+ error: error instanceof Error ? error.message : 'Failed to delete SMTP account',
99
+ };
100
+ }
101
+ },
102
+ );
103
+
104
+ // ============================================================================
105
+ // Secret-returning calls — standalone functions, deliberately NOT actions:
106
+ // the one-time password must never enter (persisted) app state.
107
+ // ============================================================================
108
+
109
+ export async function createSmtpAccount(options: {
110
+ username: string;
111
+ description?: string;
112
+ senderScope?: interfaces.data.ISmtpAccountSenderScope;
113
+ recipientScope?: interfaces.data.ISmtpAccountRecipientScope;
114
+ mailPolicy?: interfaces.data.ISmtpAccountMailPolicy;
115
+ }) {
116
+ const context = getActionContext();
117
+ const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
118
+ interfaces.requests.IReq_CreateSmtpAccount
119
+ >('/typedrequest', 'createSmtpAccount');
120
+ return request.fire({ identity: context.identity!, ...options });
121
+ }
122
+
123
+ export async function rotateSmtpAccountPassword(id: string) {
124
+ const context = getActionContext();
125
+ const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
126
+ interfaces.requests.IReq_RotateSmtpAccountPassword
127
+ >('/typedrequest', 'rotateSmtpAccountPassword');
128
+ return request.fire({ identity: context.identity!, id });
129
+ }
130
+
131
+ export async function updateSmtpAccount(options: {
132
+ id: string;
133
+ description?: string;
134
+ senderScope?: interfaces.data.ISmtpAccountSenderScope;
135
+ recipientScope?: interfaces.data.ISmtpAccountRecipientScope;
136
+ mailPolicy?: interfaces.data.ISmtpAccountMailPolicy;
137
+ }) {
138
+ const context = getActionContext();
139
+ const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
140
+ interfaces.requests.IReq_UpdateSmtpAccount
141
+ >('/typedrequest', 'updateSmtpAccount');
142
+ return request.fire({ identity: context.identity!, ...options });
143
+ }
@@ -20,6 +20,7 @@ export * from './appstate/domains.js';
20
20
  export * from './appstate/routes.js';
21
21
  export * from './appstate/users.js';
22
22
  export * from './appstate/email-domains.js';
23
+ export * from './appstate/smtp-accounts.js';
23
24
  export * from './appstate/realtime.js';
24
25
 
25
26
  // Side-effect module: auto-refresh scheduling and TypedSocket log streaming.
@@ -1,3 +1,4 @@
1
1
  export * from './ops-view-emails.js';
2
2
  export * from './ops-view-email-security.js';
3
3
  export * from './ops-view-email-domains.js';
4
+ export * from './ops-view-smtp-accounts.js';