@serve.zone/dcrouter 16.5.0 → 17.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/.smartconfig.json +5 -2
- package/deno.json +1 -1
- package/dist_serve/bundle.js +1316 -1151
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.dcrouter.d.ts +6 -1
- package/dist_ts/classes.dcrouter.js +119 -41
- package/dist_ts/db/classes.cache.cleaner.d.ts +3 -0
- package/dist_ts/db/classes.cache.cleaner.js +7 -3
- package/dist_ts/db/classes.dcrouter-db.js +1 -2
- package/dist_ts/db/documents/classes.cached.email.d.ts +41 -4
- package/dist_ts/db/documents/classes.cached.email.js +36 -5
- package/dist_ts/db/documents/classes.dns-record.doc.js +3 -1
- package/dist_ts/db/documents/classes.smartmta-storage.doc.d.ts +8 -0
- package/dist_ts/db/documents/classes.smartmta-storage.doc.js +91 -0
- package/dist_ts/db/documents/index.d.ts +1 -0
- package/dist_ts/db/documents/index.js +2 -1
- package/dist_ts/dns/classes.dns-server-runtime.d.ts +0 -1
- package/dist_ts/dns/classes.dns-server-runtime.js +4 -36
- package/dist_ts/dns/index.d.ts +1 -0
- package/dist_ts/dns/index.js +2 -1
- package/dist_ts/dns/manager.dns.d.ts +1 -0
- package/dist_ts/dns/manager.dns.js +49 -14
- package/dist_ts/dns/providers/cloudflare.provider.js +19 -4
- package/dist_ts/dns/txt-record-presentation.d.ts +20 -0
- package/dist_ts/dns/txt-record-presentation.js +131 -0
- package/dist_ts/email/classes.accepted-email-spool.d.ts +32 -3
- package/dist_ts/email/classes.accepted-email-spool.js +174 -13
- package/dist_ts/email/classes.email-domain.manager.d.ts +14 -1
- package/dist_ts/email/classes.email-domain.manager.js +151 -8
- package/dist_ts/email/classes.email-settings.manager.js +2 -1
- package/dist_ts/email/classes.mail-dns-sync.d.ts +31 -5
- package/dist_ts/email/classes.mail-dns-sync.js +508 -117
- package/dist_ts/email/classes.smartmta-blob-storage-manager.d.ts +27 -0
- package/dist_ts/email/classes.smartmta-blob-storage-manager.js +110 -0
- package/dist_ts/email/classes.smartmta-storage-manager.d.ts +6 -8
- package/dist_ts/email/classes.smartmta-storage-manager.js +63 -86
- package/dist_ts/email/classes.workapp-mail-manager.d.ts +1 -0
- package/dist_ts/email/classes.workapp-mail-manager.js +31 -4
- package/dist_ts/email/index.d.ts +4 -0
- package/dist_ts/email/index.js +5 -1
- package/dist_ts/email/mail-dns-txt-policy.d.ts +18 -0
- package/dist_ts/email/mail-dns-txt-policy.js +466 -0
- package/dist_ts/email/reconcile-mail-dns-singleton.d.ts +47 -0
- package/dist_ts/email/reconcile-mail-dns-singleton.js +159 -0
- package/dist_ts/email/smartmta-storage-policy.d.ts +9 -0
- package/dist_ts/email/smartmta-storage-policy.js +58 -0
- package/dist_ts/opsserver/classes.opsserver.d.ts +8 -0
- package/dist_ts/opsserver/classes.opsserver.js +34 -1
- package/dist_ts/opsserver/handlers/certificate.handler.js +25 -4
- package/dist_ts/opsserver/handlers/config.handler.js +9 -5
- package/dist_ts/opsserver/handlers/dns-provider.handler.js +19 -2
- package/dist_ts/opsserver/handlers/dns-record.handler.js +25 -4
- package/dist_ts/opsserver/handlers/domain.handler.js +37 -3
- package/dist_ts/opsserver/handlers/email-domain.handler.js +5 -7
- package/dist_ts/opsserver/handlers/email-ops.handler.d.ts +1 -0
- package/dist_ts/opsserver/handlers/email-ops.handler.js +26 -7
- package/dist_ts/opsserver/handlers/index.d.ts +1 -0
- package/dist_ts/opsserver/handlers/index.js +2 -1
- package/dist_ts/opsserver/handlers/logs.handler.d.ts +1 -1
- package/dist_ts/opsserver/handlers/logs.handler.js +3 -3
- package/dist_ts/opsserver/handlers/realtime.handler.d.ts +37 -0
- package/dist_ts/opsserver/handlers/realtime.handler.js +154 -0
- package/dist_ts/opsserver/handlers/remoteingress.handler.js +22 -1
- package/dist_ts/opsserver/handlers/route-management.handler.js +47 -5
- package/dist_ts/opsserver/handlers/stats.handler.js +3 -5
- package/dist_ts/paths.d.ts +0 -11
- package/dist_ts/paths.js +1 -22
- package/dist_ts/plugins.d.ts +2 -35
- package/dist_ts/plugins.js +3 -63
- package/dist_ts_interfaces/data/email-domain.d.ts +14 -0
- package/dist_ts_interfaces/requests/config.d.ts +1 -1
- package/dist_ts_interfaces/requests/email-ops.d.ts +34 -1
- package/dist_ts_interfaces/requests/index.d.ts +1 -0
- package/dist_ts_interfaces/requests/index.js +2 -1
- package/dist_ts_interfaces/requests/realtime.d.ts +38 -0
- package/dist_ts_interfaces/requests/realtime.js +11 -0
- package/dist_ts_migrations/index.d.ts +8 -0
- package/dist_ts_migrations/index.js +489 -12
- package/dist_ts_web/00_commitinfo_data.js +1 -1
- package/dist_ts_web/appstate/certificates.js +9 -2
- package/dist_ts_web/appstate/domains.js +16 -5
- package/dist_ts_web/appstate/email-domains.d.ts +3 -0
- package/dist_ts_web/appstate/email-domains.js +47 -14
- package/dist_ts_web/appstate/email-ops.d.ts +1 -0
- package/dist_ts_web/appstate/email-ops.js +20 -2
- package/dist_ts_web/appstate/realtime.d.ts +27 -0
- package/dist_ts_web/appstate/realtime.js +71 -0
- package/dist_ts_web/appstate/remoteingress.js +9 -3
- package/dist_ts_web/appstate/routes.js +16 -5
- package/dist_ts_web/appstate/runtime.js +135 -7
- package/dist_ts_web/appstate.d.ts +1 -0
- package/dist_ts_web/appstate.js +2 -1
- package/dist_ts_web/elements/email/email-domain-creation-progress.d.ts +33 -0
- package/dist_ts_web/elements/email/email-domain-creation-progress.js +257 -0
- package/dist_ts_web/elements/email/ops-view-email-domains.js +233 -68
- package/dist_ts_web/elements/email/ops-view-emails.d.ts +4 -2
- package/dist_ts_web/elements/email/ops-view-emails.js +25 -13
- package/dist_ts_web/elements/overview/ops-view-config.js +5 -5
- package/package.json +23 -19
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.dcrouter.ts +136 -44
- package/ts/db/classes.cache.cleaner.ts +18 -3
- package/ts/db/classes.dcrouter-db.ts +0 -2
- package/ts/db/documents/classes.cached.email.ts +78 -4
- package/ts/db/documents/classes.dns-record.doc.ts +1 -0
- package/ts/db/documents/classes.smartmta-storage.doc.ts +25 -0
- package/ts/db/documents/index.ts +1 -0
- package/ts/dns/classes.dns-server-runtime.ts +9 -45
- package/ts/dns/index.ts +1 -0
- package/ts/dns/manager.dns.ts +50 -16
- package/ts/dns/providers/cloudflare.provider.ts +21 -3
- package/ts/dns/txt-record-presentation.ts +134 -0
- package/ts/email/classes.accepted-email-spool.ts +220 -14
- package/ts/email/classes.email-domain.manager.ts +201 -7
- package/ts/email/classes.email-settings.manager.ts +1 -0
- package/ts/email/classes.mail-dns-sync.ts +588 -122
- package/ts/email/classes.smartmta-blob-storage-manager.ts +139 -0
- package/ts/email/classes.smartmta-storage-manager.ts +86 -83
- package/ts/email/classes.workapp-mail-manager.ts +47 -4
- package/ts/email/index.ts +4 -0
- package/ts/email/mail-dns-txt-policy.ts +505 -0
- package/ts/email/reconcile-mail-dns-singleton.ts +224 -0
- package/ts/email/smartmta-storage-policy.ts +73 -0
- package/ts/opsserver/classes.opsserver.ts +42 -0
- package/ts/opsserver/handlers/certificate.handler.ts +24 -3
- package/ts/opsserver/handlers/config.handler.ts +9 -5
- package/ts/opsserver/handlers/dns-provider.handler.ts +18 -1
- package/ts/opsserver/handlers/dns-record.handler.ts +24 -3
- package/ts/opsserver/handlers/domain.handler.ts +36 -2
- package/ts/opsserver/handlers/email-domain.handler.ts +4 -5
- package/ts/opsserver/handlers/email-ops.handler.ts +29 -6
- package/ts/opsserver/handlers/index.ts +1 -0
- package/ts/opsserver/handlers/logs.handler.ts +2 -2
- package/ts/opsserver/handlers/realtime.handler.ts +222 -0
- package/ts/opsserver/handlers/remoteingress.handler.ts +21 -0
- package/ts/opsserver/handlers/route-management.handler.ts +46 -4
- package/ts/opsserver/handlers/stats.handler.ts +2 -4
- package/ts/paths.ts +0 -25
- package/ts/plugins.ts +2 -69
- package/ts_web/00_commitinfo_data.ts +1 -1
- package/ts_web/appstate/certificates.ts +6 -1
- package/ts_web/appstate/domains.ts +11 -4
- package/ts_web/appstate/email-domains.ts +68 -14
- package/ts_web/appstate/email-ops.ts +20 -1
- package/ts_web/appstate/realtime.ts +109 -0
- package/ts_web/appstate/remoteingress.ts +6 -2
- package/ts_web/appstate/routes.ts +11 -4
- package/ts_web/appstate/runtime.ts +176 -7
- package/ts_web/appstate.ts +1 -0
- package/ts_web/elements/email/email-domain-creation-progress.ts +340 -0
- package/ts_web/elements/email/ops-view-email-domains.ts +273 -74
- package/ts_web/elements/email/ops-view-emails.ts +23 -16
- package/ts_web/elements/overview/ops-view-config.ts +4 -4
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { DnsRecordDoc } from '../db/documents/classes.dns-record.doc.js';
|
|
2
|
+
import type {
|
|
3
|
+
IEmailDnsRecordIntent,
|
|
4
|
+
IEmailDomainOperationError,
|
|
5
|
+
TEmailDnsVerificationOutcome,
|
|
6
|
+
} from '../../ts_interfaces/data/email-domain.js';
|
|
7
|
+
import {
|
|
8
|
+
classifyMailTxtRecord,
|
|
9
|
+
type IMailDnsTxtPolicyResult,
|
|
10
|
+
} from './mail-dns-txt-policy.js';
|
|
11
|
+
|
|
12
|
+
export interface IMailDnsMutationClient {
|
|
13
|
+
createRecord(args: {
|
|
14
|
+
domainId: string;
|
|
15
|
+
name: string;
|
|
16
|
+
type: 'MX' | 'TXT' | 'A' | 'AAAA';
|
|
17
|
+
value: string;
|
|
18
|
+
ttl: number;
|
|
19
|
+
createdBy: string;
|
|
20
|
+
managedBy: string;
|
|
21
|
+
managedOwnerId: string;
|
|
22
|
+
managedRecordKey: string;
|
|
23
|
+
}): Promise<{ success: boolean; id?: string; message?: string }>;
|
|
24
|
+
updateRecord(args: {
|
|
25
|
+
id: string;
|
|
26
|
+
name?: string;
|
|
27
|
+
value?: string;
|
|
28
|
+
ttl?: number;
|
|
29
|
+
}): Promise<{ success: boolean; message?: string }>;
|
|
30
|
+
deleteRecord(id: string): Promise<{ success: boolean; message?: string }>;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface IReconcileMailDnsSingletonOptions {
|
|
34
|
+
intent: IEmailDnsRecordIntent;
|
|
35
|
+
rrset: DnsRecordDoc[];
|
|
36
|
+
dnsManager: IMailDnsMutationClient;
|
|
37
|
+
managedBy: string;
|
|
38
|
+
createdBy: string;
|
|
39
|
+
evaluate: (
|
|
40
|
+
intent: IEmailDnsRecordIntent,
|
|
41
|
+
values: string[],
|
|
42
|
+
) => Promise<IMailDnsTxtPolicyResult>;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const outcomeError = (
|
|
46
|
+
intent: IEmailDnsRecordIntent,
|
|
47
|
+
outcome: Exclude<TEmailDnsVerificationOutcome, 'valid'>,
|
|
48
|
+
message: string,
|
|
49
|
+
): IEmailDomainOperationError => {
|
|
50
|
+
let code: string;
|
|
51
|
+
if (outcome === 'missing') code = 'DNS_RECORD_MISSING';
|
|
52
|
+
else if (outcome === 'propagation') code = 'DNS_PROPAGATION_PENDING';
|
|
53
|
+
else if (outcome === 'duplicate') code = 'DNS_PUBLIC_SINGLETON_DUPLICATE';
|
|
54
|
+
else if (outcome === 'conflict') code = 'DNS_SINGLETON_CONFLICT';
|
|
55
|
+
else if (outcome === 'invalid') code = 'DNS_SINGLETON_INVALID';
|
|
56
|
+
else {
|
|
57
|
+
code = intent.value.trim().toLowerCase().startsWith('v=spf1')
|
|
58
|
+
? 'DNS_SPF_AUTHORIZATION_LOOKUP_FAILED'
|
|
59
|
+
: 'DNS_LOOKUP_FAILED';
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
code,
|
|
63
|
+
message,
|
|
64
|
+
recordKey: intent.key,
|
|
65
|
+
retryable: outcome === 'missing' || outcome === 'propagation' || outcome === 'lookup-error',
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export function applyMailDnsVerificationResult(
|
|
70
|
+
intent: IEmailDnsRecordIntent,
|
|
71
|
+
result: IMailDnsTxtPolicyResult,
|
|
72
|
+
phase: 'reconcile' | 'verify',
|
|
73
|
+
): void {
|
|
74
|
+
intent.verificationOutcome = result.outcome;
|
|
75
|
+
if (result.outcome === 'valid') {
|
|
76
|
+
intent.error = undefined;
|
|
77
|
+
intent.effectiveValue = result.effectiveValue;
|
|
78
|
+
intent.status = phase === 'verify' ? 'valid' : 'satisfied';
|
|
79
|
+
if (phase === 'verify') intent.validatedAt = new Date().toISOString();
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
intent.status = result.outcome === 'conflict' || result.outcome === 'duplicate'
|
|
83
|
+
? 'conflict'
|
|
84
|
+
: result.outcome === 'invalid'
|
|
85
|
+
? 'failed'
|
|
86
|
+
: 'pending';
|
|
87
|
+
intent.error = outcomeError(
|
|
88
|
+
intent,
|
|
89
|
+
result.outcome,
|
|
90
|
+
result.reason || `${intent.type} ${intent.name} did not validate`,
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function isSingletonAuthenticationIntent(intent: IEmailDnsRecordIntent): boolean {
|
|
95
|
+
if (intent.type !== 'TXT') return false;
|
|
96
|
+
const normalized = intent.value.trim().toLowerCase();
|
|
97
|
+
return normalized.startsWith('v=spf1')
|
|
98
|
+
|| normalized.startsWith('v=dmarc1')
|
|
99
|
+
|| normalized.startsWith('v=dkim1');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function exactIntentOwner(
|
|
103
|
+
record: DnsRecordDoc,
|
|
104
|
+
intent: IEmailDnsRecordIntent,
|
|
105
|
+
managedBy: string,
|
|
106
|
+
): boolean {
|
|
107
|
+
return record.managedBy === managedBy
|
|
108
|
+
&& record.managedOwnerId === intent.ownerId
|
|
109
|
+
&& record.managedRecordKey === intent.key;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async function deleteOwnedRecords(
|
|
113
|
+
records: DnsRecordDoc[],
|
|
114
|
+
options: IReconcileMailDnsSingletonOptions,
|
|
115
|
+
): Promise<boolean> {
|
|
116
|
+
for (const record of records) {
|
|
117
|
+
const result = await options.dnsManager.deleteRecord(record.id);
|
|
118
|
+
if (!result.success) {
|
|
119
|
+
options.intent.status = 'pending';
|
|
120
|
+
options.intent.verificationOutcome = 'duplicate';
|
|
121
|
+
options.intent.error = {
|
|
122
|
+
code: 'DNS_SINGLETON_DUPLICATE_CLEANUP_FAILED',
|
|
123
|
+
message: result.message || `Failed to remove duplicate ${options.intent.type} ${options.intent.name}`,
|
|
124
|
+
recordKey: options.intent.key,
|
|
125
|
+
retryable: true,
|
|
126
|
+
};
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Reconcile SPF, DMARC, and DKIM singleton TXT records without adopting or
|
|
135
|
+
* mutating operator policy. Returns false for non-singleton intents.
|
|
136
|
+
*/
|
|
137
|
+
export async function reconcileMailDnsSingleton(
|
|
138
|
+
options: IReconcileMailDnsSingletonOptions,
|
|
139
|
+
): Promise<boolean> {
|
|
140
|
+
const { intent, rrset, managedBy } = options;
|
|
141
|
+
if (!isSingletonAuthenticationIntent(intent)) return false;
|
|
142
|
+
|
|
143
|
+
const exactOwned = rrset
|
|
144
|
+
.filter((record) => exactIntentOwner(record, intent, managedBy))
|
|
145
|
+
.sort((left, right) => left.id.localeCompare(right.id));
|
|
146
|
+
const otherManaged = rrset.filter((record) => (
|
|
147
|
+
record.managedBy === managedBy && !exactIntentOwner(record, intent, managedBy)
|
|
148
|
+
));
|
|
149
|
+
const operator = rrset.filter((record) => record.managedBy !== managedBy);
|
|
150
|
+
const relevantOtherManaged = otherManaged.filter((record) => (
|
|
151
|
+
classifyMailTxtRecord(intent, record.value).relevant
|
|
152
|
+
));
|
|
153
|
+
const relevantOperator = operator.filter((record) => (
|
|
154
|
+
classifyMailTxtRecord(intent, record.value).relevant
|
|
155
|
+
));
|
|
156
|
+
|
|
157
|
+
if (relevantOperator.length > 0) {
|
|
158
|
+
if (!await deleteOwnedRecords(exactOwned, options)) return true;
|
|
159
|
+
if (relevantOtherManaged.length > 0) {
|
|
160
|
+
applyMailDnsVerificationResult(intent, {
|
|
161
|
+
outcome: 'conflict',
|
|
162
|
+
reason: `Another managed owner controls ${intent.type} ${intent.name}`,
|
|
163
|
+
}, 'reconcile');
|
|
164
|
+
return true;
|
|
165
|
+
}
|
|
166
|
+
const result = await options.evaluate(intent, relevantOperator.map((record) => record.value));
|
|
167
|
+
applyMailDnsVerificationResult(intent, result, 'reconcile');
|
|
168
|
+
if (result.outcome === 'valid') intent.satisfactionSource = 'operator';
|
|
169
|
+
return true;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (relevantOtherManaged.length > 0) {
|
|
173
|
+
if (!await deleteOwnedRecords(exactOwned, options)) return true;
|
|
174
|
+
applyMailDnsVerificationResult(intent, {
|
|
175
|
+
outcome: 'conflict',
|
|
176
|
+
reason: `Another managed owner controls ${intent.type} ${intent.name}`,
|
|
177
|
+
}, 'reconcile');
|
|
178
|
+
return true;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const managed = exactOwned[0];
|
|
182
|
+
if (exactOwned.length > 1 && !await deleteOwnedRecords(exactOwned.slice(1), options)) {
|
|
183
|
+
return true;
|
|
184
|
+
}
|
|
185
|
+
if (managed) {
|
|
186
|
+
if (managed.value.trim() !== intent.value.trim() || managed.name !== intent.name) {
|
|
187
|
+
const result = await options.dnsManager.updateRecord({
|
|
188
|
+
id: managed.id,
|
|
189
|
+
name: intent.name,
|
|
190
|
+
value: intent.value,
|
|
191
|
+
ttl: intent.ttl,
|
|
192
|
+
});
|
|
193
|
+
if (!result.success) throw new Error(result.message || `Failed to update ${intent.key}`);
|
|
194
|
+
}
|
|
195
|
+
intent.providerRecordId = managed.providerRecordId;
|
|
196
|
+
intent.status = 'satisfied';
|
|
197
|
+
intent.satisfactionSource = 'managed';
|
|
198
|
+
intent.effectiveValue = intent.value;
|
|
199
|
+
intent.verificationOutcome = undefined;
|
|
200
|
+
intent.error = undefined;
|
|
201
|
+
intent.lastAttemptAt = new Date().toISOString();
|
|
202
|
+
return true;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const result = await options.dnsManager.createRecord({
|
|
206
|
+
domainId: intent.domainId,
|
|
207
|
+
name: intent.name,
|
|
208
|
+
type: intent.type,
|
|
209
|
+
value: intent.value,
|
|
210
|
+
ttl: intent.ttl,
|
|
211
|
+
createdBy: options.createdBy,
|
|
212
|
+
managedBy,
|
|
213
|
+
managedOwnerId: intent.ownerId,
|
|
214
|
+
managedRecordKey: intent.key,
|
|
215
|
+
});
|
|
216
|
+
if (!result.success) throw new Error(result.message || `Failed to create ${intent.key}`);
|
|
217
|
+
intent.status = 'satisfied';
|
|
218
|
+
intent.satisfactionSource = 'managed';
|
|
219
|
+
intent.effectiveValue = intent.value;
|
|
220
|
+
intent.verificationOutcome = undefined;
|
|
221
|
+
intent.error = undefined;
|
|
222
|
+
intent.lastAttemptAt = new Date().toISOString();
|
|
223
|
+
return true;
|
|
224
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import * as plugins from '../plugins.js';
|
|
2
|
+
|
|
3
|
+
export interface ISmartMtaTextNamespace {
|
|
4
|
+
prefix: string;
|
|
5
|
+
maxBytes: number;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const smartMtaTextNamespaces: readonly ISmartMtaTextNamespace[] = Object.freeze([
|
|
9
|
+
{ prefix: '/email/dkim/', maxBytes: 128 * 1024 },
|
|
10
|
+
{ prefix: '/email/dns/', maxBytes: 1024 * 1024 },
|
|
11
|
+
{ prefix: '/email/bounces/', maxBytes: 8 * 1024 * 1024 },
|
|
12
|
+
{ prefix: '/email/templates/', maxBytes: 4 * 1024 * 1024 },
|
|
13
|
+
{ prefix: '/email/routes/config.json', maxBytes: 4 * 1024 * 1024 },
|
|
14
|
+
{ prefix: '/security/ip-reputation-cache.json', maxBytes: 8 * 1024 * 1024 },
|
|
15
|
+
{ prefix: '/workhosters/mail-identities.json', maxBytes: 4 * 1024 * 1024 },
|
|
16
|
+
]);
|
|
17
|
+
|
|
18
|
+
export function normalizeSmartMtaStorageKey(key: string): string {
|
|
19
|
+
if (typeof key !== 'string' || key.length === 0) {
|
|
20
|
+
throw new Error('SmartMTA storage key must be a non-empty string');
|
|
21
|
+
}
|
|
22
|
+
if (key.includes('\\') || key.includes('\0')) {
|
|
23
|
+
throw new Error(`Invalid SmartMTA storage key: ${key}`);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const withLeadingSlash = key.startsWith('/') ? key : `/${key}`;
|
|
27
|
+
const segments = withLeadingSlash.split('/').slice(1);
|
|
28
|
+
if (
|
|
29
|
+
segments.length === 0
|
|
30
|
+
|| segments.some((segment, index) => segment === '.' || segment === '..' || (segment === '' && index < segments.length - 1))
|
|
31
|
+
) {
|
|
32
|
+
throw new Error(`Invalid SmartMTA storage key: ${key}`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const normalized = `/${segments.filter(Boolean).join('/')}`;
|
|
36
|
+
if (normalized.length > 1024) {
|
|
37
|
+
throw new Error('SmartMTA storage key exceeds 1024 characters');
|
|
38
|
+
}
|
|
39
|
+
return normalized;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function getSmartMtaTextNamespace(key: string): ISmartMtaTextNamespace {
|
|
43
|
+
const normalizedKey = normalizeSmartMtaStorageKey(key);
|
|
44
|
+
const namespace = smartMtaTextNamespaces.find((candidate) => {
|
|
45
|
+
return candidate.prefix.endsWith('/')
|
|
46
|
+
? normalizedKey.startsWith(candidate.prefix)
|
|
47
|
+
: normalizedKey === candidate.prefix;
|
|
48
|
+
});
|
|
49
|
+
if (!namespace) {
|
|
50
|
+
throw new Error(`Unsupported SmartMTA text storage namespace: ${normalizedKey}`);
|
|
51
|
+
}
|
|
52
|
+
return namespace;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function validateSmartMtaTextValue(key: string, value: string): void {
|
|
56
|
+
if (typeof value !== 'string') {
|
|
57
|
+
throw new Error('SmartMTA text storage only accepts string values');
|
|
58
|
+
}
|
|
59
|
+
const namespace = getSmartMtaTextNamespace(key);
|
|
60
|
+
const size = Buffer.byteLength(value, 'utf8');
|
|
61
|
+
if (size > namespace.maxBytes) {
|
|
62
|
+
throw new Error(
|
|
63
|
+
`SmartMTA storage value for ${normalizeSmartMtaStorageKey(key)} exceeds ${namespace.maxBytes} bytes`,
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function hashSmartMtaStorageKey(key: string): string {
|
|
69
|
+
return plugins.crypto
|
|
70
|
+
.createHash('sha256')
|
|
71
|
+
.update(normalizeSmartMtaStorageKey(key))
|
|
72
|
+
.digest('hex');
|
|
73
|
+
}
|
|
@@ -18,6 +18,7 @@ export class OpsServer {
|
|
|
18
18
|
|
|
19
19
|
// Handler instances
|
|
20
20
|
public adminHandler!: handlers.AdminHandler;
|
|
21
|
+
public realtimeHandler!: handlers.RealtimeHandler;
|
|
21
22
|
private configHandler!: handlers.ConfigHandler;
|
|
22
23
|
private logsHandler!: handlers.LogsHandler;
|
|
23
24
|
private securityHandler!: handlers.SecurityHandler;
|
|
@@ -40,6 +41,8 @@ export class OpsServer {
|
|
|
40
41
|
private acmeConfigHandler!: handlers.AcmeConfigHandler;
|
|
41
42
|
private emailDomainHandler!: handlers.EmailDomainHandler;
|
|
42
43
|
private gatewayClientHandler!: handlers.GatewayClientHandler;
|
|
44
|
+
private smtpRealtimeUnsubscribe?: () => void;
|
|
45
|
+
private emailQueueRealtimeUnsubscribe?: () => void;
|
|
43
46
|
|
|
44
47
|
constructor(dcRouterRefArg: DcRouter) {
|
|
45
48
|
this.dcRouterRef = dcRouterRefArg;
|
|
@@ -79,6 +82,7 @@ export class OpsServer {
|
|
|
79
82
|
this.typedrouter.addTypedRouter(this.adminRouter);
|
|
80
83
|
|
|
81
84
|
// Instantiate all handlers — they self-register with the appropriate router
|
|
85
|
+
this.realtimeHandler = new handlers.RealtimeHandler(this);
|
|
82
86
|
this.configHandler = new handlers.ConfigHandler(this);
|
|
83
87
|
this.logsHandler = new handlers.LogsHandler(this);
|
|
84
88
|
this.securityHandler = new handlers.SecurityHandler(this);
|
|
@@ -101,7 +105,21 @@ export class OpsServer {
|
|
|
101
105
|
this.acmeConfigHandler = new handlers.AcmeConfigHandler(this);
|
|
102
106
|
this.emailDomainHandler = new handlers.EmailDomainHandler(this);
|
|
103
107
|
this.gatewayClientHandler = new handlers.GatewayClientHandler(this);
|
|
108
|
+
this.smtpRealtimeUnsubscribe =
|
|
109
|
+
this.dcRouterRef.acceptedEmailSpool?.onSmtpTransactionPersisted?.((eventArg) => {
|
|
110
|
+
this.invalidateRealtime(['smtpAttempts', 'emailQueue'], {
|
|
111
|
+
changedIds: [eventArg.cachedEmailId],
|
|
112
|
+
reason: 'smtp-transaction-persisted',
|
|
113
|
+
});
|
|
114
|
+
});
|
|
104
115
|
|
|
116
|
+
this.emailQueueRealtimeUnsubscribe =
|
|
117
|
+
this.dcRouterRef.acceptedEmailSpool?.onEmailQueuePersisted?.((eventArg) => {
|
|
118
|
+
this.invalidateRealtime('emailQueue', {
|
|
119
|
+
changedIds: [eventArg.cachedEmailId],
|
|
120
|
+
reason: eventArg.reason,
|
|
121
|
+
});
|
|
122
|
+
});
|
|
105
123
|
console.log('✅ OpsServer TypedRequest handlers initialized');
|
|
106
124
|
}
|
|
107
125
|
|
|
@@ -114,10 +132,34 @@ export class OpsServer {
|
|
|
114
132
|
);
|
|
115
133
|
}
|
|
116
134
|
|
|
135
|
+
public invalidateRealtime(
|
|
136
|
+
resourcesArg:
|
|
137
|
+
| interfaces.requests.TOpsRealtimeResource
|
|
138
|
+
| interfaces.requests.TOpsRealtimeResource[],
|
|
139
|
+
optionsArg: { changedIds?: string[]; reason?: string } = {},
|
|
140
|
+
): void {
|
|
141
|
+
this.realtimeHandler?.invalidate(resourcesArg, optionsArg);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
public async getAuthenticatedRealtimeConnections(): Promise<
|
|
145
|
+
plugins.typedsocket.ISmartServeConnectionWrapper[]
|
|
146
|
+
> {
|
|
147
|
+
return this.realtimeHandler
|
|
148
|
+
? await this.realtimeHandler.getAuthenticatedConnections()
|
|
149
|
+
: [];
|
|
150
|
+
}
|
|
151
|
+
|
|
117
152
|
public async stop() {
|
|
118
153
|
if (this.adminHandler) {
|
|
119
154
|
await this.adminHandler.stop();
|
|
120
155
|
}
|
|
156
|
+
this.smtpRealtimeUnsubscribe?.();
|
|
157
|
+
this.smtpRealtimeUnsubscribe = undefined;
|
|
158
|
+
this.emailQueueRealtimeUnsubscribe?.();
|
|
159
|
+
this.emailQueueRealtimeUnsubscribe = undefined;
|
|
160
|
+
if (this.realtimeHandler) {
|
|
161
|
+
this.realtimeHandler.cleanup();
|
|
162
|
+
}
|
|
121
163
|
// Clean up log handler streams and push destination before stopping the server
|
|
122
164
|
if (this.logsHandler) {
|
|
123
165
|
this.logsHandler.cleanup();
|
|
@@ -67,7 +67,14 @@ export class CertificateHandler {
|
|
|
67
67
|
'reprovisionCertificateDomain',
|
|
68
68
|
async (dataArg) => {
|
|
69
69
|
await this.requireAuth(dataArg, 'certificates:write');
|
|
70
|
-
|
|
70
|
+
const result = await this.reprovisionCertificateDomain(dataArg.domain, dataArg.forceRenew);
|
|
71
|
+
if (result.success) {
|
|
72
|
+
this.opsServerRef.invalidateRealtime?.('certificates', {
|
|
73
|
+
changedIds: [dataArg.domain],
|
|
74
|
+
reason: 'certificate-reprovisioned',
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
return result;
|
|
71
78
|
}
|
|
72
79
|
)
|
|
73
80
|
);
|
|
@@ -78,7 +85,14 @@ export class CertificateHandler {
|
|
|
78
85
|
'deleteCertificate',
|
|
79
86
|
async (dataArg) => {
|
|
80
87
|
await this.requireAuth(dataArg, 'certificates:write');
|
|
81
|
-
|
|
88
|
+
const result = await this.deleteCertificate(dataArg.domain);
|
|
89
|
+
if (result.success) {
|
|
90
|
+
this.opsServerRef.invalidateRealtime?.('certificates', {
|
|
91
|
+
changedIds: [dataArg.domain],
|
|
92
|
+
reason: 'certificate-deleted',
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
return result;
|
|
82
96
|
}
|
|
83
97
|
)
|
|
84
98
|
);
|
|
@@ -100,7 +114,14 @@ export class CertificateHandler {
|
|
|
100
114
|
'importCertificate',
|
|
101
115
|
async (dataArg) => {
|
|
102
116
|
await this.requireAuth(dataArg, 'certificates:write');
|
|
103
|
-
|
|
117
|
+
const result = await this.importCertificate(dataArg.cert);
|
|
118
|
+
if (result.success) {
|
|
119
|
+
this.opsServerRef.invalidateRealtime?.('certificates', {
|
|
120
|
+
changedIds: [dataArg.cert.domainName],
|
|
121
|
+
reason: 'certificate-imported',
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
return result;
|
|
104
125
|
}
|
|
105
126
|
)
|
|
106
127
|
);
|
|
@@ -35,9 +35,9 @@ export class ConfigHandler {
|
|
|
35
35
|
const resolvedPaths = dcRouter.resolvedPaths;
|
|
36
36
|
|
|
37
37
|
// --- System ---
|
|
38
|
-
const storageBackend: '
|
|
39
|
-
? '
|
|
40
|
-
: '
|
|
38
|
+
const storageBackend: 'smartdata-external' | 'smartdata-local' = opts.dbConfig?.mongoDbUrl
|
|
39
|
+
? 'smartdata-external'
|
|
40
|
+
: 'smartdata-local';
|
|
41
41
|
|
|
42
42
|
const proxyIps = opts.proxyIps || [];
|
|
43
43
|
|
|
@@ -48,7 +48,9 @@ export class ConfigHandler {
|
|
|
48
48
|
proxyIps,
|
|
49
49
|
uptime: Math.floor(process.uptime()),
|
|
50
50
|
storageBackend,
|
|
51
|
-
storagePath: opts.dbConfig?.
|
|
51
|
+
storagePath: opts.dbConfig?.mongoDbUrl
|
|
52
|
+
? null
|
|
53
|
+
: opts.dbConfig?.storagePath || resolvedPaths.defaultTsmDbPath,
|
|
52
54
|
};
|
|
53
55
|
|
|
54
56
|
// --- SmartProxy ---
|
|
@@ -161,7 +163,9 @@ export class ConfigHandler {
|
|
|
161
163
|
const dbConfig = opts.dbConfig;
|
|
162
164
|
const cache: interfaces.requests.IConfigData['cache'] = {
|
|
163
165
|
enabled: dbConfig?.enabled !== false,
|
|
164
|
-
storagePath: dbConfig?.
|
|
166
|
+
storagePath: dbConfig?.mongoDbUrl
|
|
167
|
+
? null
|
|
168
|
+
: dbConfig?.storagePath || resolvedPaths.defaultTsmDbPath,
|
|
165
169
|
dbName: dbConfig?.dbName || 'dcrouter',
|
|
166
170
|
defaultTTLDays: 30,
|
|
167
171
|
cleanupIntervalHours: dbConfig?.cleanupIntervalHours || 1,
|
|
@@ -90,6 +90,10 @@ export class DnsProviderHandler {
|
|
|
90
90
|
credentials: dataArg.credentials,
|
|
91
91
|
createdBy: userId,
|
|
92
92
|
});
|
|
93
|
+
this.opsServerRef.invalidateRealtime?.('dns', {
|
|
94
|
+
changedIds: [id],
|
|
95
|
+
reason: 'dns-provider-created',
|
|
96
|
+
});
|
|
93
97
|
return { success: true, id };
|
|
94
98
|
},
|
|
95
99
|
),
|
|
@@ -110,6 +114,12 @@ export class DnsProviderHandler {
|
|
|
110
114
|
name: dataArg.name,
|
|
111
115
|
credentials: dataArg.credentials,
|
|
112
116
|
});
|
|
117
|
+
if (ok) {
|
|
118
|
+
this.opsServerRef.invalidateRealtime?.('dns', {
|
|
119
|
+
changedIds: [dataArg.id],
|
|
120
|
+
reason: 'dns-provider-updated',
|
|
121
|
+
});
|
|
122
|
+
}
|
|
113
123
|
return ok ? { success: true } : { success: false, message: 'Provider not found' };
|
|
114
124
|
},
|
|
115
125
|
),
|
|
@@ -126,7 +136,14 @@ export class DnsProviderHandler {
|
|
|
126
136
|
}
|
|
127
137
|
const dnsManager = this.opsServerRef.dcRouterRef.dnsManager;
|
|
128
138
|
if (!dnsManager) return { success: false, message: 'DnsManager not initialized' };
|
|
129
|
-
|
|
139
|
+
const result = await dnsManager.deleteProvider(dataArg.id, dataArg.force ?? false);
|
|
140
|
+
if (result.success) {
|
|
141
|
+
this.opsServerRef.invalidateRealtime?.(['dns', 'emailDomains'], {
|
|
142
|
+
changedIds: [dataArg.id],
|
|
143
|
+
reason: 'dns-provider-deleted',
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
return result;
|
|
130
147
|
},
|
|
131
148
|
),
|
|
132
149
|
);
|
|
@@ -62,7 +62,7 @@ export class DnsRecordHandler {
|
|
|
62
62
|
const userId = await this.requireAuth(dataArg, 'dns-records:write');
|
|
63
63
|
const dnsManager = this.opsServerRef.dcRouterRef.dnsManager;
|
|
64
64
|
if (!dnsManager) return { success: false, message: 'DnsManager not initialized' };
|
|
65
|
-
|
|
65
|
+
const result = await dnsManager.createRecord({
|
|
66
66
|
domainId: dataArg.domainId,
|
|
67
67
|
name: dataArg.name,
|
|
68
68
|
type: dataArg.type,
|
|
@@ -71,6 +71,13 @@ export class DnsRecordHandler {
|
|
|
71
71
|
proxied: dataArg.proxied,
|
|
72
72
|
createdBy: userId,
|
|
73
73
|
});
|
|
74
|
+
if (result.success) {
|
|
75
|
+
this.opsServerRef.invalidateRealtime?.(['dns', 'emailDomains'], {
|
|
76
|
+
changedIds: result.id ? [result.id] : undefined,
|
|
77
|
+
reason: 'dns-record-created',
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
return result;
|
|
74
81
|
},
|
|
75
82
|
),
|
|
76
83
|
);
|
|
@@ -83,13 +90,20 @@ export class DnsRecordHandler {
|
|
|
83
90
|
await this.requireAuth(dataArg, 'dns-records:write');
|
|
84
91
|
const dnsManager = this.opsServerRef.dcRouterRef.dnsManager;
|
|
85
92
|
if (!dnsManager) return { success: false, message: 'DnsManager not initialized' };
|
|
86
|
-
|
|
93
|
+
const result = await dnsManager.updateRecord({
|
|
87
94
|
id: dataArg.id,
|
|
88
95
|
name: dataArg.name,
|
|
89
96
|
value: dataArg.value,
|
|
90
97
|
ttl: dataArg.ttl,
|
|
91
98
|
proxied: dataArg.proxied,
|
|
92
99
|
});
|
|
100
|
+
if (result.success) {
|
|
101
|
+
this.opsServerRef.invalidateRealtime?.(['dns', 'emailDomains'], {
|
|
102
|
+
changedIds: [dataArg.id],
|
|
103
|
+
reason: 'dns-record-updated',
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
return result;
|
|
93
107
|
},
|
|
94
108
|
),
|
|
95
109
|
);
|
|
@@ -102,7 +116,14 @@ export class DnsRecordHandler {
|
|
|
102
116
|
await this.requireAuth(dataArg, 'dns-records:write');
|
|
103
117
|
const dnsManager = this.opsServerRef.dcRouterRef.dnsManager;
|
|
104
118
|
if (!dnsManager) return { success: false, message: 'DnsManager not initialized' };
|
|
105
|
-
|
|
119
|
+
const result = await dnsManager.deleteRecord(dataArg.id);
|
|
120
|
+
if (result.success) {
|
|
121
|
+
this.opsServerRef.invalidateRealtime?.(['dns', 'emailDomains'], {
|
|
122
|
+
changedIds: [dataArg.id],
|
|
123
|
+
reason: 'dns-record-deleted',
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
return result;
|
|
106
127
|
},
|
|
107
128
|
),
|
|
108
129
|
);
|
|
@@ -68,6 +68,10 @@ export class DomainHandler {
|
|
|
68
68
|
description: dataArg.description,
|
|
69
69
|
createdBy: userId,
|
|
70
70
|
});
|
|
71
|
+
this.opsServerRef.invalidateRealtime?.(['dns', 'emailDomains'], {
|
|
72
|
+
changedIds: [id],
|
|
73
|
+
reason: 'domain-created',
|
|
74
|
+
});
|
|
71
75
|
return { success: true, id };
|
|
72
76
|
} catch (err: unknown) {
|
|
73
77
|
return { success: false, message: (err as Error).message };
|
|
@@ -90,6 +94,10 @@ export class DomainHandler {
|
|
|
90
94
|
domainNames: dataArg.domainNames,
|
|
91
95
|
createdBy: userId,
|
|
92
96
|
});
|
|
97
|
+
this.opsServerRef.invalidateRealtime?.(['dns', 'emailDomains'], {
|
|
98
|
+
changedIds: importedIds,
|
|
99
|
+
reason: 'domains-imported',
|
|
100
|
+
});
|
|
93
101
|
return { success: true, importedIds };
|
|
94
102
|
} catch (err: unknown) {
|
|
95
103
|
return { success: false, message: (err as Error).message };
|
|
@@ -109,6 +117,12 @@ export class DomainHandler {
|
|
|
109
117
|
const ok = await dnsManager.updateDomain(dataArg.id, {
|
|
110
118
|
description: dataArg.description,
|
|
111
119
|
});
|
|
120
|
+
if (ok) {
|
|
121
|
+
this.opsServerRef.invalidateRealtime?.(['dns', 'emailDomains'], {
|
|
122
|
+
changedIds: [dataArg.id],
|
|
123
|
+
reason: 'domain-updated',
|
|
124
|
+
});
|
|
125
|
+
}
|
|
112
126
|
return ok ? { success: true } : { success: false, message: 'Domain not found' };
|
|
113
127
|
},
|
|
114
128
|
),
|
|
@@ -123,6 +137,12 @@ export class DomainHandler {
|
|
|
123
137
|
const dnsManager = this.opsServerRef.dcRouterRef.dnsManager;
|
|
124
138
|
if (!dnsManager) return { success: false, message: 'DnsManager not initialized' };
|
|
125
139
|
const ok = await dnsManager.deleteDomain(dataArg.id);
|
|
140
|
+
if (ok) {
|
|
141
|
+
this.opsServerRef.invalidateRealtime?.(['dns', 'emailDomains'], {
|
|
142
|
+
changedIds: [dataArg.id],
|
|
143
|
+
reason: 'domain-deleted',
|
|
144
|
+
});
|
|
145
|
+
}
|
|
126
146
|
return ok ? { success: true } : { success: false, message: 'Domain not found' };
|
|
127
147
|
},
|
|
128
148
|
),
|
|
@@ -136,7 +156,14 @@ export class DomainHandler {
|
|
|
136
156
|
await this.requireAuth(dataArg, 'domains:write');
|
|
137
157
|
const dnsManager = this.opsServerRef.dcRouterRef.dnsManager;
|
|
138
158
|
if (!dnsManager) return { success: false, message: 'DnsManager not initialized' };
|
|
139
|
-
|
|
159
|
+
const result = await dnsManager.syncDomain(dataArg.id);
|
|
160
|
+
if (result.success) {
|
|
161
|
+
this.opsServerRef.invalidateRealtime?.(['dns', 'emailDomains'], {
|
|
162
|
+
changedIds: [dataArg.id],
|
|
163
|
+
reason: 'domain-synced',
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
return result;
|
|
140
167
|
},
|
|
141
168
|
),
|
|
142
169
|
);
|
|
@@ -149,12 +176,19 @@ export class DomainHandler {
|
|
|
149
176
|
await this.requireAuth(dataArg, 'domains:write');
|
|
150
177
|
const dnsManager = this.opsServerRef.dcRouterRef.dnsManager;
|
|
151
178
|
if (!dnsManager) return { success: false, message: 'DnsManager not initialized' };
|
|
152
|
-
|
|
179
|
+
const result = await dnsManager.migrateDomain({
|
|
153
180
|
id: dataArg.id,
|
|
154
181
|
targetSource: dataArg.targetSource,
|
|
155
182
|
targetProviderId: dataArg.targetProviderId,
|
|
156
183
|
deleteExistingProviderRecords: dataArg.deleteExistingProviderRecords,
|
|
157
184
|
});
|
|
185
|
+
if (result.success) {
|
|
186
|
+
this.opsServerRef.invalidateRealtime?.(['dns', 'emailDomains'], {
|
|
187
|
+
changedIds: [dataArg.id],
|
|
188
|
+
reason: 'domain-migrated',
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
return result;
|
|
158
192
|
},
|
|
159
193
|
),
|
|
160
194
|
);
|
|
@@ -178,8 +178,6 @@ export class EmailDomainHandler {
|
|
|
178
178
|
remoteIngress: dataArg.remoteIngress,
|
|
179
179
|
});
|
|
180
180
|
const result = normalizeEmailDomainActionResult(rawResult);
|
|
181
|
-
if (!result.success) return result;
|
|
182
|
-
|
|
183
181
|
const resultRecord = isRecord(rawResult) ? rawResult : undefined;
|
|
184
182
|
const domain = isRecord(resultRecord?.domain)
|
|
185
183
|
? resultRecord.domain as unknown as interfaces.data.IEmailDomain
|
|
@@ -210,7 +208,8 @@ export class EmailDomainHandler {
|
|
|
210
208
|
rateLimits: dataArg.rateLimits,
|
|
211
209
|
remoteIngress: dataArg.remoteIngress,
|
|
212
210
|
});
|
|
213
|
-
|
|
211
|
+
const result = normalizeEmailDomainActionResult(rawResult);
|
|
212
|
+
return result;
|
|
214
213
|
} catch (err: unknown) {
|
|
215
214
|
return normalizeEmailDomainActionFailure(err, 'Failed to update email domain');
|
|
216
215
|
}
|
|
@@ -229,7 +228,8 @@ export class EmailDomainHandler {
|
|
|
229
228
|
}
|
|
230
229
|
try {
|
|
231
230
|
const rawResult: unknown = await this.manager.deleteEmailDomain(dataArg.id);
|
|
232
|
-
|
|
231
|
+
const result = normalizeEmailDomainActionResult(rawResult);
|
|
232
|
+
return result;
|
|
233
233
|
} catch (err: unknown) {
|
|
234
234
|
return normalizeEmailDomainActionFailure(err, 'Failed to delete email domain');
|
|
235
235
|
}
|
|
@@ -249,7 +249,6 @@ export class EmailDomainHandler {
|
|
|
249
249
|
try {
|
|
250
250
|
const rawResult: unknown = await this.manager.validateDns(dataArg.id);
|
|
251
251
|
const result = normalizeEmailDomainActionResult(rawResult);
|
|
252
|
-
if (!result.success) return result;
|
|
253
252
|
|
|
254
253
|
const resultRecord = isRecord(rawResult) ? rawResult : undefined;
|
|
255
254
|
const records = (
|