@serve.zone/dcrouter 18.0.1 → 18.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.
Files changed (71) hide show
  1. package/deno.json +1 -1
  2. package/dist_serve/bundle.js +1522 -1375
  3. package/dist_ts/00_commitinfo_data.js +1 -1
  4. package/dist_ts/classes.dcrouter.d.ts +3 -1
  5. package/dist_ts/classes.dcrouter.js +32 -4
  6. package/dist_ts/db/documents/classes.smtp-account.doc.d.ts +31 -0
  7. package/dist_ts/db/documents/classes.smtp-account.doc.js +176 -0
  8. package/dist_ts/db/documents/index.d.ts +1 -0
  9. package/dist_ts/db/documents/index.js +2 -1
  10. package/dist_ts/email/classes.email-settings.manager.d.ts +7 -0
  11. package/dist_ts/email/classes.email-settings.manager.js +30 -1
  12. package/dist_ts/email/classes.smtp-account.manager.d.ts +110 -0
  13. package/dist_ts/email/classes.smtp-account.manager.js +507 -0
  14. package/dist_ts/email/classes.workapp-mail-manager.d.ts +23 -4
  15. package/dist_ts/email/classes.workapp-mail-manager.js +39 -46
  16. package/dist_ts/email/index.d.ts +2 -0
  17. package/dist_ts/email/index.js +3 -1
  18. package/dist_ts/email/smtp-scram.d.ts +13 -0
  19. package/dist_ts/email/smtp-scram.js +19 -0
  20. package/dist_ts/opsserver/classes.opsserver.d.ts +1 -0
  21. package/dist_ts/opsserver/classes.opsserver.js +3 -1
  22. package/dist_ts/opsserver/handlers/email-settings.handler.js +14 -2
  23. package/dist_ts/opsserver/handlers/index.d.ts +1 -0
  24. package/dist_ts/opsserver/handlers/index.js +2 -1
  25. package/dist_ts/opsserver/handlers/smtp-account.handler.d.ts +20 -0
  26. package/dist_ts/opsserver/handlers/smtp-account.handler.js +155 -0
  27. package/dist_ts_interfaces/data/email-settings.d.ts +10 -0
  28. package/dist_ts_interfaces/data/index.d.ts +1 -0
  29. package/dist_ts_interfaces/data/index.js +2 -1
  30. package/dist_ts_interfaces/data/route-management.d.ts +1 -1
  31. package/dist_ts_interfaces/data/route-management.js +3 -1
  32. package/dist_ts_interfaces/data/smtp-account.d.ts +50 -0
  33. package/dist_ts_interfaces/data/smtp-account.js +9 -0
  34. package/dist_ts_interfaces/requests/index.d.ts +1 -0
  35. package/dist_ts_interfaces/requests/index.js +2 -1
  36. package/dist_ts_interfaces/requests/smtp-accounts.d.ts +125 -0
  37. package/dist_ts_interfaces/requests/smtp-accounts.js +2 -0
  38. package/dist_ts_migrations/index.js +198 -9
  39. package/dist_ts_web/00_commitinfo_data.js +1 -1
  40. package/dist_ts_web/appstate/smtp-accounts.d.ts +38 -0
  41. package/dist_ts_web/appstate/smtp-accounts.js +94 -0
  42. package/dist_ts_web/appstate.d.ts +1 -0
  43. package/dist_ts_web/appstate.js +2 -1
  44. package/dist_ts_web/elements/email/index.d.ts +1 -0
  45. package/dist_ts_web/elements/email/index.js +2 -1
  46. package/dist_ts_web/elements/email/ops-view-smtp-accounts.d.ts +25 -0
  47. package/dist_ts_web/elements/email/ops-view-smtp-accounts.js +598 -0
  48. package/dist_ts_web/elements/ops-dashboard.js +3 -1
  49. package/dist_ts_web/router.js +2 -2
  50. package/package.json +2 -2
  51. package/readme.md +12 -0
  52. package/ts/00_commitinfo_data.ts +1 -1
  53. package/ts/classes.dcrouter.ts +35 -3
  54. package/ts/db/documents/classes.smtp-account.doc.ts +103 -0
  55. package/ts/db/documents/index.ts +1 -0
  56. package/ts/email/classes.email-settings.manager.ts +34 -0
  57. package/ts/email/classes.smtp-account.manager.ts +601 -0
  58. package/ts/email/classes.workapp-mail-manager.ts +53 -55
  59. package/ts/email/index.ts +2 -0
  60. package/ts/email/smtp-scram.ts +24 -0
  61. package/ts/opsserver/classes.opsserver.ts +2 -0
  62. package/ts/opsserver/handlers/email-settings.handler.ts +13 -1
  63. package/ts/opsserver/handlers/index.ts +1 -0
  64. package/ts/opsserver/handlers/smtp-account.handler.ts +199 -0
  65. package/ts_web/00_commitinfo_data.ts +1 -1
  66. package/ts_web/appstate/smtp-accounts.ts +143 -0
  67. package/ts_web/appstate.ts +1 -0
  68. package/ts_web/elements/email/index.ts +1 -0
  69. package/ts_web/elements/email/ops-view-smtp-accounts.ts +586 -0
  70. package/ts_web/elements/ops-dashboard.ts +2 -0
  71. package/ts_web/router.ts +1 -1
@@ -0,0 +1,507 @@
1
+ import { assertValidAuthAccounts } from '@push.rocks/smartmta';
2
+ import * as plugins from '../plugins.js';
3
+ import { logger } from '../logger.js';
4
+ import { SmtpAccountDoc } from '../db/index.js';
5
+ import { isWorkAppManagedMailRouteName, isWorkAppManagedSmtpUsername, } from './classes.workapp-mail-manager.js';
6
+ import { deriveSmtpScramVerifier, generateSmtpAccountPassword } from './smtp-scram.js';
7
+ /** Route names generated (and owned wholesale) by SmtpAccountManager. */
8
+ export function isSmtpAccountRouteName(routeName) {
9
+ return routeName.startsWith('smtp-account-');
10
+ }
11
+ const SMTP_ACCOUNT_USERNAME_PATTERN = /^[a-z0-9][a-z0-9._-]{2,63}$/;
12
+ /** Literal domain label chain — no wildcards, at least one dot. */
13
+ const SMTP_ACCOUNT_DOMAIN_PATTERN = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)+$/;
14
+ /** Address pattern: optional '*' glob in the local part only, literal domain. */
15
+ const SMTP_ACCOUNT_ADDRESS_LOCAL_PATTERN = /^[a-z0-9*][a-z0-9.*_+-]*$/;
16
+ /**
17
+ * SmtpAccountManager — operator-managed authenticated SMTP submission
18
+ * accounts, backed by SmtpAccountDoc rows holding hashed SCRAM verifiers only.
19
+ *
20
+ * Single composition owner of the email runtime `auth` block and route list:
21
+ * every runtime application of auth/routes goes through composeEmailConfig /
22
+ * applyToRuntime here. WorkAppMailManager identities feed in as an explicit
23
+ * contribution; nothing else writes `auth` wholesale.
24
+ *
25
+ * Accounts are served from an in-memory map loaded at start() — the SMTP auth
26
+ * path (smartmta's Rust bridge, 5s callback timeout) never touches the DB.
27
+ */
28
+ export class SmtpAccountManager {
29
+ dcRouterRef;
30
+ accounts = new Map();
31
+ started = false;
32
+ mutationChain = Promise.resolve();
33
+ /** Explicit upstream capability marker; absence is deliberately fail-closed. */
34
+ get authAccountsCapability() {
35
+ return plugins.smartmta.smartMtaCapabilities?.smtpAuthAccountsAndScopes === true;
36
+ }
37
+ constructor(dcRouterRef) {
38
+ this.dcRouterRef = dcRouterRef;
39
+ }
40
+ async start() {
41
+ const docs = await SmtpAccountDoc.findAll();
42
+ this.accounts = new Map(docs.map((doc) => [doc.id, doc]));
43
+ this.started = true;
44
+ if (this.accounts.size > 0) {
45
+ logger.log('info', `SmtpAccountManager loaded ${this.accounts.size} SMTP account(s)`);
46
+ }
47
+ }
48
+ async stop() {
49
+ this.accounts.clear();
50
+ this.started = false;
51
+ }
52
+ isStarted() {
53
+ return this.started;
54
+ }
55
+ getAccountCount() {
56
+ return this.accounts.size;
57
+ }
58
+ // ==========================================================================
59
+ // Composition — the single owner of auth + route-list assembly
60
+ // ==========================================================================
61
+ /**
62
+ * Compose the runtime email config: operator-configured routes/users plus
63
+ * the workapp identity contribution plus DB-backed SMTP accounts and their
64
+ * generated relay routes. Idempotent — previously generated entries are
65
+ * stripped by their reserved name prefixes before re-adding.
66
+ */
67
+ async composeEmailConfig(emailConfig, workappContribution) {
68
+ const contribution = workappContribution
69
+ ?? await this.dcRouterRef.workAppMailManager.getStoredIdentityContribution(emailConfig);
70
+ if (!this.authAccountsCapability) {
71
+ // Fail closed: without the smartmta capability the accounts field would
72
+ // be silently ignored (or rejected), so never pretend they are active —
73
+ // any pre-existing accounts entry is dropped from the composed auth too.
74
+ if (this.accounts.size > 0) {
75
+ logger.log('error', `SmartMTA is missing the smtpAuthAccountsAndScopes capability — ${this.accounts.size} stored SMTP account(s) are NOT active`);
76
+ }
77
+ const authWithoutAccounts = { ...(emailConfig.auth || {}) };
78
+ delete authWithoutAccounts.accounts;
79
+ return {
80
+ ...emailConfig,
81
+ routes: [
82
+ ...(emailConfig.routes || [])
83
+ .filter((route) => !isWorkAppManagedMailRouteName(route.name) && !isSmtpAccountRouteName(route.name)),
84
+ ...contribution.routes,
85
+ ],
86
+ auth: {
87
+ ...authWithoutAccounts,
88
+ users: [
89
+ ...(emailConfig.auth?.users || []).filter((user) => !isWorkAppManagedSmtpUsername(user.username)),
90
+ ...contribution.users,
91
+ ],
92
+ },
93
+ };
94
+ }
95
+ const accountDocs = [...this.accounts.values()]
96
+ .sort((a, b) => a.username.localeCompare(b.username));
97
+ const smtpAccounts = [];
98
+ const accountRoutes = [];
99
+ for (const doc of accountDocs) {
100
+ const account = this.buildAuthAccount(doc);
101
+ try {
102
+ // smartmta's own validator — construction and updateOptions throw on
103
+ // invalid accounts, so a bad DB row must never reach the push.
104
+ // Validated per account so one row cannot take the whole auth block
105
+ // down.
106
+ assertValidAuthAccounts([account]);
107
+ }
108
+ catch (error) {
109
+ const message = error.message;
110
+ logger.log('error', `SMTP account ${doc.username} excluded from runtime auth (invalid stored configuration): ${message}`);
111
+ this.recordAccountConflictEvent(doc.username, message);
112
+ continue;
113
+ }
114
+ smtpAccounts.push(account);
115
+ if (doc.enabled) {
116
+ accountRoutes.push(...this.buildAccountRoutes(doc));
117
+ }
118
+ }
119
+ const configuredRoutes = (emailConfig.routes || [])
120
+ .filter((route) => !isWorkAppManagedMailRouteName(route.name) && !isSmtpAccountRouteName(route.name));
121
+ const configuredUsers = (emailConfig.auth?.users || [])
122
+ .filter((user) => !isWorkAppManagedSmtpUsername(user.username));
123
+ return {
124
+ ...emailConfig,
125
+ routes: [...configuredRoutes, ...contribution.routes, ...accountRoutes],
126
+ auth: {
127
+ ...(emailConfig.auth || {}),
128
+ users: [...configuredUsers, ...contribution.users],
129
+ accounts: smtpAccounts,
130
+ },
131
+ };
132
+ }
133
+ /**
134
+ * Recompose from the live runtime options and push the result: replace the
135
+ * whole `auth` block (smartmta toggles listener AUTH live) and re-apply the
136
+ * route list.
137
+ */
138
+ async applyToRuntime(workappContribution) {
139
+ const emailConfig = this.dcRouterRef.options.emailConfig;
140
+ if (!emailConfig)
141
+ return;
142
+ const nextConfig = await this.composeEmailConfig(emailConfig, workappContribution);
143
+ this.dcRouterRef.options.emailConfig = nextConfig;
144
+ if (this.dcRouterRef.emailServer) {
145
+ this.dcRouterRef.emailServer.updateOptions({ auth: nextConfig.auth });
146
+ await this.dcRouterRef.updateEmailRoutes(nextConfig.routes);
147
+ }
148
+ }
149
+ buildAuthAccount(doc) {
150
+ const senders = [
151
+ ...(doc.senderScope?.addresses || []),
152
+ ...(doc.senderScope?.domains || []).map((domain) => `*@${domain}`),
153
+ ];
154
+ const recipientsRestricted = doc.recipientScope?.mode === 'restricted';
155
+ const recipientPatterns = [
156
+ ...(doc.recipientScope?.addresses || []),
157
+ ...(doc.recipientScope?.domains || []).map((domain) => `*@${domain}`),
158
+ ];
159
+ let scope;
160
+ if (senders.length > 0 || recipientsRestricted) {
161
+ scope = {
162
+ // Unrestricted senders with restricted recipients still needs a scope
163
+ // block; '*' matches every sender including the null sender.
164
+ senders: senders.length > 0 ? senders : ['*'],
165
+ ...(recipientsRestricted
166
+ ? { recipients: { mode: 'patterns', patterns: recipientPatterns } }
167
+ : {}),
168
+ };
169
+ }
170
+ return {
171
+ username: doc.username,
172
+ enabled: doc.enabled,
173
+ credential: { verifier: doc.credentialVerifier },
174
+ ...(scope ? { scope } : {}),
175
+ };
176
+ }
177
+ /**
178
+ * One generated relay route per (account × sender-scope domain), name
179
+ * `smtp-account-<id>-<domainHash>`, priority 850 — below workapp
180
+ * per-address routes (900), above operator-configured DB routes.
181
+ *
182
+ * Accounts without a sender scope generate no routes: their relay
183
+ * permission stays exactly whatever operator-configured routes grant
184
+ * (this is what keeps the legacy-user migration from widening relay).
185
+ *
186
+ * DKIM policy rides on `process.dkim` only — smartmta ≥9.1 resolves the
187
+ * active selector per sender domain from its domain registry, so no
188
+ * selector is snapshotted into routes and rotation stays owned by
189
+ * EmailDomainManager/mail-dns-sync.
190
+ */
191
+ buildAccountRoutes(doc) {
192
+ const senderDomains = this.collectSenderDomainPatterns(doc.senderScope);
193
+ const routes = [];
194
+ for (const [domain, patterns] of senderDomains) {
195
+ routes.push({
196
+ name: `smtp-account-${doc.id}-${this.hashDomain(domain)}`,
197
+ priority: 850,
198
+ match: {
199
+ authenticated: true,
200
+ authenticatedUser: doc.username,
201
+ senders: patterns,
202
+ },
203
+ action: {
204
+ type: 'process',
205
+ allowRelay: true,
206
+ process: {
207
+ dkim: Boolean(doc.mailPolicy?.dkimSign),
208
+ queue: doc.mailPolicy?.queue ?? 'normal',
209
+ },
210
+ },
211
+ });
212
+ }
213
+ return routes;
214
+ }
215
+ /** Map of sender-scope domain -> the scope patterns belonging to it. */
216
+ collectSenderDomainPatterns(senderScope) {
217
+ const byDomain = new Map();
218
+ for (const domain of senderScope?.domains || []) {
219
+ const key = domain.toLowerCase();
220
+ byDomain.set(key, [...(byDomain.get(key) || []), `*@${key}`]);
221
+ }
222
+ for (const address of senderScope?.addresses || []) {
223
+ const domainPart = address.split('@')[1]?.toLowerCase();
224
+ if (!domainPart)
225
+ continue;
226
+ byDomain.set(domainPart, [...(byDomain.get(domainPart) || []), address.toLowerCase()]);
227
+ }
228
+ return byDomain;
229
+ }
230
+ hashDomain(domain) {
231
+ return plugins.crypto.createHash('sha256').update(domain.toLowerCase()).digest('hex').slice(0, 8);
232
+ }
233
+ recordAccountConflictEvent(username, message) {
234
+ const opsEventManager = this.dcRouterRef.opsEventManager;
235
+ if (!opsEventManager)
236
+ return;
237
+ opsEventManager.recordEvent({
238
+ severity: 'error',
239
+ category: 'smtp-accounts',
240
+ title: 'SMTP account excluded from runtime authentication',
241
+ detail: `Stored SMTP account '${username}' failed validation and was excluded from the composed auth block: ${message}`,
242
+ context: { recordName: username },
243
+ }).catch((error) => {
244
+ logger.log('warn', `Failed to record SMTP account conflict event for ${username}: ${error.message}`);
245
+ });
246
+ }
247
+ // ==========================================================================
248
+ // CRUD
249
+ // ==========================================================================
250
+ async listAccounts() {
251
+ const docs = [...this.accounts.values()]
252
+ .sort((a, b) => a.username.localeCompare(b.username));
253
+ const accounts = [];
254
+ for (const doc of docs) {
255
+ accounts.push(await this.decorateWithDomainReadiness(doc));
256
+ }
257
+ return accounts;
258
+ }
259
+ async createAccount(options) {
260
+ return await this.runMutationExclusive(async () => {
261
+ if (!this.authAccountsCapability) {
262
+ throw new Error('SmartMTA runtime does not support SMTP auth accounts (smtpAuthAccountsAndScopes capability missing)');
263
+ }
264
+ const username = this.normalizeUsername(options.username);
265
+ const senderScope = this.normalizeSenderScope(options.senderScope);
266
+ const recipientScope = this.normalizeRecipientScope(options.recipientScope);
267
+ const mailPolicy = this.normalizeMailPolicy(options.mailPolicy);
268
+ await this.assertDkimPolicyAllowed(mailPolicy, senderScope, username);
269
+ if ([...this.accounts.values()].some((doc) => doc.username === username)
270
+ || await SmtpAccountDoc.findByUsername(username)) {
271
+ throw new Error(`SMTP account username is already taken: ${username}`);
272
+ }
273
+ const password = generateSmtpAccountPassword();
274
+ const now = Date.now();
275
+ const doc = new SmtpAccountDoc();
276
+ doc.id = `smtpacct_${now.toString(36)}_${plugins.crypto.randomBytes(6).toString('hex')}`;
277
+ doc.username = username;
278
+ doc.description = (options.description || '').trim();
279
+ doc.enabled = true;
280
+ doc.credentialVerifier = deriveSmtpScramVerifier(password);
281
+ doc.senderScope = senderScope;
282
+ doc.recipientScope = recipientScope;
283
+ doc.mailPolicy = mailPolicy;
284
+ doc.createdAt = now;
285
+ doc.updatedAt = now;
286
+ doc.createdBy = options.createdBy;
287
+ await doc.save();
288
+ this.accounts.set(doc.id, doc);
289
+ await this.applyToRuntime();
290
+ const warnings = await this.collectSenderDomainWarnings(senderScope);
291
+ logger.log('info', `SMTP account '${username}' created by ${options.createdBy} (id: ${doc.id})`);
292
+ return { account: await this.decorateWithDomainReadiness(doc), password, warnings };
293
+ });
294
+ }
295
+ async updateAccount(id, updates, updatedBy) {
296
+ return await this.runMutationExclusive(async () => {
297
+ const doc = this.requireAccount(id);
298
+ const senderScope = updates.senderScope !== undefined
299
+ ? this.normalizeSenderScope(updates.senderScope)
300
+ : doc.senderScope;
301
+ const recipientScope = updates.recipientScope !== undefined
302
+ ? this.normalizeRecipientScope(updates.recipientScope)
303
+ : doc.recipientScope;
304
+ const mailPolicy = updates.mailPolicy !== undefined
305
+ ? this.normalizeMailPolicy(updates.mailPolicy)
306
+ : doc.mailPolicy;
307
+ await this.assertDkimPolicyAllowed(mailPolicy, senderScope, doc.username);
308
+ if (updates.description !== undefined) {
309
+ doc.description = updates.description.trim();
310
+ }
311
+ doc.senderScope = senderScope;
312
+ doc.recipientScope = recipientScope;
313
+ doc.mailPolicy = mailPolicy;
314
+ doc.updatedAt = Date.now();
315
+ await doc.save();
316
+ this.accounts.set(doc.id, doc);
317
+ await this.applyToRuntime();
318
+ const warnings = await this.collectSenderDomainWarnings(senderScope);
319
+ logger.log('info', `SMTP account '${doc.username}' updated by ${updatedBy}`);
320
+ return { account: await this.decorateWithDomainReadiness(doc), warnings };
321
+ });
322
+ }
323
+ async toggleAccount(id, enabled, updatedBy) {
324
+ return await this.runMutationExclusive(async () => {
325
+ const doc = this.requireAccount(id);
326
+ doc.enabled = enabled;
327
+ doc.updatedAt = Date.now();
328
+ await doc.save();
329
+ this.accounts.set(doc.id, doc);
330
+ await this.applyToRuntime();
331
+ logger.log('info', `SMTP account '${doc.username}' ${enabled ? 'enabled' : 'disabled'} by ${updatedBy}`);
332
+ return { account: await this.decorateWithDomainReadiness(doc), warnings: [] };
333
+ });
334
+ }
335
+ async rotatePassword(id, rotatedBy) {
336
+ return await this.runMutationExclusive(async () => {
337
+ const doc = this.requireAccount(id);
338
+ const password = generateSmtpAccountPassword();
339
+ doc.credentialVerifier = deriveSmtpScramVerifier(password);
340
+ doc.lastRotatedAt = Date.now();
341
+ doc.updatedAt = doc.lastRotatedAt;
342
+ await doc.save();
343
+ this.accounts.set(doc.id, doc);
344
+ await this.applyToRuntime();
345
+ logger.log('info', `SMTP account '${doc.username}' password rotated by ${rotatedBy}`);
346
+ return { account: await this.decorateWithDomainReadiness(doc), password, warnings: [] };
347
+ });
348
+ }
349
+ async deleteAccount(id, deletedBy) {
350
+ return await this.runMutationExclusive(async () => {
351
+ const doc = this.requireAccount(id);
352
+ await doc.delete();
353
+ this.accounts.delete(id);
354
+ await this.applyToRuntime();
355
+ logger.log('info', `SMTP account '${doc.username}' deleted by ${deletedBy}`);
356
+ });
357
+ }
358
+ // ==========================================================================
359
+ // Validation / normalization
360
+ // ==========================================================================
361
+ requireAccount(id) {
362
+ const doc = this.accounts.get(id);
363
+ if (!doc) {
364
+ throw new Error(`SMTP account not found: ${id}`);
365
+ }
366
+ return doc;
367
+ }
368
+ normalizeUsername(usernameArg) {
369
+ const username = (usernameArg || '').trim().toLowerCase();
370
+ if (!SMTP_ACCOUNT_USERNAME_PATTERN.test(username)) {
371
+ throw new Error('SMTP account username must be 3-64 characters of a-z, 0-9, dot, underscore, or dash, starting alphanumeric');
372
+ }
373
+ if (isWorkAppManagedSmtpUsername(username)) {
374
+ throw new Error(`SMTP account usernames may not use the reserved 'workapp-' prefix: ${username}`);
375
+ }
376
+ return username;
377
+ }
378
+ normalizeDomain(domainArg, context) {
379
+ const domain = (domainArg || '').trim().toLowerCase();
380
+ if (!SMTP_ACCOUNT_DOMAIN_PATTERN.test(domain)) {
381
+ throw new Error(`Invalid ${context} domain (wildcards are not allowed in domains): ${domainArg}`);
382
+ }
383
+ return domain;
384
+ }
385
+ normalizeAddressPattern(addressArg, context) {
386
+ const address = (addressArg || '').trim().toLowerCase();
387
+ const parts = address.split('@');
388
+ if (parts.length !== 2
389
+ || !SMTP_ACCOUNT_ADDRESS_LOCAL_PATTERN.test(parts[0])
390
+ || !SMTP_ACCOUNT_DOMAIN_PATTERN.test(parts[1])) {
391
+ throw new Error(`Invalid ${context} address pattern ('local@domain', '*' allowed in the local part only): ${addressArg}`);
392
+ }
393
+ return address;
394
+ }
395
+ normalizeSenderScope(scopeArg) {
396
+ return {
397
+ addresses: this.dedupe((scopeArg?.addresses || []).map((address) => this.normalizeAddressPattern(address, 'sender scope'))),
398
+ domains: this.dedupe((scopeArg?.domains || []).map((domain) => this.normalizeDomain(domain, 'sender scope'))),
399
+ };
400
+ }
401
+ normalizeRecipientScope(scopeArg) {
402
+ const mode = scopeArg?.mode === 'restricted' ? 'restricted' : 'any';
403
+ const addresses = this.dedupe((scopeArg?.addresses || []).map((address) => this.normalizeAddressPattern(address, 'recipient scope')));
404
+ const domains = this.dedupe((scopeArg?.domains || []).map((domain) => this.normalizeDomain(domain, 'recipient scope')));
405
+ if (mode === 'restricted' && addresses.length === 0 && domains.length === 0) {
406
+ throw new Error('A restricted recipient scope requires at least one address or domain (an empty list would silently deny all mail)');
407
+ }
408
+ return { mode, addresses, domains };
409
+ }
410
+ normalizeMailPolicy(policyArg) {
411
+ const queue = policyArg?.queue;
412
+ if (queue !== undefined && !['normal', 'priority', 'bulk'].includes(queue)) {
413
+ throw new Error(`Invalid mail policy queue: ${queue}`);
414
+ }
415
+ return {
416
+ dkimSign: Boolean(policyArg?.dkimSign),
417
+ ...(queue ? { queue } : {}),
418
+ };
419
+ }
420
+ /**
421
+ * DKIM signing fails at configuration time, never at delivery time: it is
422
+ * only allowed when every sender-scope domain is a managed email domain
423
+ * that is outbound-ready with active DKIM material.
424
+ */
425
+ async assertDkimPolicyAllowed(mailPolicy, senderScope, username) {
426
+ if (!mailPolicy.dkimSign)
427
+ return;
428
+ const senderDomains = [...this.collectSenderDomainPatterns(senderScope).keys()];
429
+ if (senderDomains.length === 0) {
430
+ throw new Error(`DKIM signing for SMTP account '${username}' requires an explicit sender scope naming the domain(s) to sign for`);
431
+ }
432
+ const emailDomainManager = this.dcRouterRef.emailDomainManager;
433
+ if (!emailDomainManager) {
434
+ throw new Error('DKIM signing requires the email domain manager, which is not available');
435
+ }
436
+ for (const domain of senderDomains) {
437
+ if (!await emailDomainManager.getByDomain(domain)) {
438
+ throw new Error(`DKIM signing requires managed email domains; '${domain}' is not managed by dcrouter`);
439
+ }
440
+ const readiness = await emailDomainManager.getOutboundReadiness(domain);
441
+ if (!readiness.ready || !readiness.selector) {
442
+ throw new Error(`DKIM signing requires outbound-ready DKIM material for '${domain}': ${readiness.reason || 'no active DKIM selector'}`);
443
+ }
444
+ }
445
+ }
446
+ /** Warn-and-allow: sender domains outside managed email domains are permitted but flagged. */
447
+ async collectSenderDomainWarnings(senderScope) {
448
+ const warnings = [];
449
+ const emailDomainManager = this.dcRouterRef.emailDomainManager;
450
+ if (!emailDomainManager)
451
+ return warnings;
452
+ for (const domain of this.collectSenderDomainPatterns(senderScope).keys()) {
453
+ try {
454
+ if (!await emailDomainManager.getByDomain(domain)) {
455
+ warnings.push(`Sender domain '${domain}' is not a managed email domain — outbound DNS alignment (SPF/DKIM/DMARC) is not managed by dcrouter`);
456
+ }
457
+ }
458
+ catch (error) {
459
+ warnings.push(`Could not verify sender domain '${domain}': ${error.message}`);
460
+ }
461
+ }
462
+ return warnings;
463
+ }
464
+ async decorateWithDomainReadiness(doc) {
465
+ const info = doc.toApiObject();
466
+ const emailDomainManager = this.dcRouterRef.emailDomainManager;
467
+ const senderDomains = [...this.collectSenderDomainPatterns(doc.senderScope).keys()];
468
+ if (!emailDomainManager || senderDomains.length === 0)
469
+ return info;
470
+ const domainReadiness = [];
471
+ for (const domain of senderDomains) {
472
+ try {
473
+ if (!await emailDomainManager.getByDomain(domain)) {
474
+ domainReadiness.push({
475
+ domain,
476
+ ready: !doc.mailPolicy?.dkimSign,
477
+ reason: 'not a managed email domain',
478
+ });
479
+ continue;
480
+ }
481
+ const readiness = await emailDomainManager.getOutboundReadiness(domain);
482
+ domainReadiness.push({
483
+ domain,
484
+ ready: readiness.ready,
485
+ ...(readiness.reason ? { reason: readiness.reason } : {}),
486
+ });
487
+ }
488
+ catch (error) {
489
+ domainReadiness.push({
490
+ domain,
491
+ ready: false,
492
+ reason: `readiness check failed: ${error.message}`,
493
+ });
494
+ }
495
+ }
496
+ return { ...info, domainReadiness };
497
+ }
498
+ dedupe(values) {
499
+ return [...new Set(values)];
500
+ }
501
+ async runMutationExclusive(action) {
502
+ const run = this.mutationChain.then(action, action);
503
+ this.mutationChain = run.catch(() => undefined);
504
+ return await run;
505
+ }
506
+ }
507
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5zbXRwLWFjY291bnQubWFuYWdlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3RzL2VtYWlsL2NsYXNzZXMuc210cC1hY2NvdW50Lm1hbmFnZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBS0EsT0FBTyxFQUFFLHVCQUF1QixFQUFFLE1BQU0sc0JBQXNCLENBQUM7QUFDL0QsT0FBTyxLQUFLLE9BQU8sTUFBTSxlQUFlLENBQUM7QUFDekMsT0FBTyxFQUFFLE1BQU0sRUFBRSxNQUFNLGNBQWMsQ0FBQztBQUN0QyxPQUFPLEVBQUUsY0FBYyxFQUFFLE1BQU0sZ0JBQWdCLENBQUM7QUFTaEQsT0FBTyxFQUNMLDZCQUE2QixFQUM3Qiw0QkFBNEIsR0FFN0IsTUFBTSxtQ0FBbUMsQ0FBQztBQUMzQyxPQUFPLEVBQUUsdUJBQXVCLEVBQUUsMkJBQTJCLEVBQUUsTUFBTSxpQkFBaUIsQ0FBQztBQUV2Rix5RUFBeUU7QUFDekUsTUFBTSxVQUFVLHNCQUFzQixDQUFDLFNBQWlCO0lBQ3RELE9BQU8sU0FBUyxDQUFDLFVBQVUsQ0FBQyxlQUFlLENBQUMsQ0FBQztBQUMvQyxDQUFDO0FBRUQsTUFBTSw2QkFBNkIsR0FBRyw2QkFBNkIsQ0FBQztBQUNwRSxtRUFBbUU7QUFDbkUsTUFBTSwyQkFBMkIsR0FBRyxtRUFBbUUsQ0FBQztBQUN4RyxpRkFBaUY7QUFDakYsTUFBTSxrQ0FBa0MsR0FBRywyQkFBMkIsQ0FBQztBQTRCdkU7Ozs7Ozs7Ozs7O0dBV0c7QUFDSCxNQUFNLE9BQU8sa0JBQWtCO0lBVVQ7SUFUWixRQUFRLEdBQUcsSUFBSSxHQUFHLEVBQTBCLENBQUM7SUFDN0MsT0FBTyxHQUFHLEtBQUssQ0FBQztJQUNoQixhQUFhLEdBQXFCLE9BQU8sQ0FBQyxPQUFPLEVBQUUsQ0FBQztJQUU1RCxnRkFBZ0Y7SUFDaEYsSUFBWSxzQkFBc0I7UUFDaEMsT0FBUSxPQUFPLENBQUMsUUFBZ0IsQ0FBQyxvQkFBb0IsRUFBRSx5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
@@ -1,4 +1,4 @@
1
- import type { IExtendedSmtpSession, IMessageAcceptanceContext, IMessageAcceptanceDecision, IUnifiedEmailServerOptions } from '@push.rocks/smartmta';
1
+ import type { IEmailRoute, IExtendedSmtpSession, IMessageAcceptanceContext, IMessageAcceptanceDecision, IUnifiedEmailServerOptions } from '@push.rocks/smartmta';
2
2
  import type { Buffer } from 'node:buffer';
3
3
  import * as plugins from '../plugins.js';
4
4
  import { CachedEmail } from '../db/index.js';
@@ -47,6 +47,22 @@ interface IStoredWorkAppMailIdentity {
47
47
  smtpPassword: string;
48
48
  smtpLastRotatedAt?: number;
49
49
  }
50
+ /**
51
+ * WorkApp-generated routes and SMTP users contributed to the composed email
52
+ * runtime config. Consumed by SmtpAccountManager, the single owner of the
53
+ * final `auth` block and route-list composition.
54
+ */
55
+ export interface IWorkAppMailRuntimeContribution {
56
+ routes: IEmailRoute[];
57
+ users: Array<{
58
+ username: string;
59
+ password: string;
60
+ }>;
61
+ }
62
+ /** Route names generated (and owned wholesale) by WorkAppMailManager. */
63
+ export declare function isWorkAppManagedMailRouteName(routeName: string): boolean;
64
+ /** SMTP usernames generated (and owned wholesale) by WorkAppMailManager. */
65
+ export declare function isWorkAppManagedSmtpUsername(username: string): boolean;
50
66
  export declare class WorkAppMailManager {
51
67
  private dcRouterRef;
52
68
  private readonly storageKey;
@@ -79,12 +95,16 @@ export declare class WorkAppMailManager {
79
95
  deliverCachedEmailToTypedEndpoint(cachedEmailArg: CachedEmail, rawMessageArg: Buffer | plugins.buffer.Buffer, sessionArg: IExtendedSmtpSession): Promise<boolean>;
80
96
  deleteMailAddressBinding(id: string, createdBy: string): Promise<TMailAddressBindingDeleteResponse>;
81
97
  private deleteMailAddressBindingInner;
82
- applyStoredIdentitiesToEmailConfig<TConfig extends IUnifiedEmailServerOptions>(emailConfig: TConfig): Promise<TConfig>;
98
+ /**
99
+ * Compute the workapp-generated routes and SMTP users for the given
100
+ * candidate email config. Pure contribution — the final composition of
101
+ * routes and the `auth` block is owned exclusively by SmtpAccountManager.
102
+ */
103
+ getStoredIdentityContribution(emailConfig: IUnifiedEmailServerOptions, identities?: IStoredWorkAppMailIdentity[] | undefined): Promise<IWorkAppMailRuntimeContribution>;
83
104
  applyStoredIdentitiesToRuntime(identities?: IStoredWorkAppMailIdentity[] | undefined): Promise<void>;
84
105
  private readStoredIdentities;
85
106
  private writeStoredIdentities;
86
107
  private getRequiredStorageManager;
87
- private mergeIdentitiesIntoEmailConfig;
88
108
  private buildInboundRoute;
89
109
  private buildOutboundRoute;
90
110
  private ensureEmailDomainConfigured;
@@ -143,7 +163,6 @@ export declare class WorkAppMailManager {
143
163
  private buildOutboundRouteName;
144
164
  private hashExternalKey;
145
165
  private generateSmtpPassword;
146
- private isManagedMailRouteName;
147
166
  private isManagedSmtpUsername;
148
167
  private buildSmtpCredentials;
149
168
  private toMailCredentialPublic;