@wraps.dev/cli 3.11.0 → 3.11.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/dist/cli.js
CHANGED
|
@@ -24974,668 +24974,147 @@ var init_presets = __esm({
|
|
|
24974
24974
|
}
|
|
24975
24975
|
});
|
|
24976
24976
|
|
|
24977
|
-
// src/utils/
|
|
24978
|
-
var
|
|
24979
|
-
__export(
|
|
24980
|
-
|
|
24981
|
-
createSelectedDNSRecords: () => createSelectedDNSRecords,
|
|
24982
|
-
deleteDNSRecords: () => deleteDNSRecords,
|
|
24983
|
-
findHostedZone: () => findHostedZone,
|
|
24984
|
-
mergeSPFRecord: () => mergeSPFRecord,
|
|
24985
|
-
previewDNSChanges: () => previewDNSChanges
|
|
24977
|
+
// src/utils/dns/cloudflare.ts
|
|
24978
|
+
var cloudflare_exports = {};
|
|
24979
|
+
__export(cloudflare_exports, {
|
|
24980
|
+
CloudflareDNSClient: () => CloudflareDNSClient
|
|
24986
24981
|
});
|
|
24987
|
-
|
|
24988
|
-
|
|
24989
|
-
ListHostedZonesByNameCommand,
|
|
24990
|
-
ListResourceRecordSetsCommand,
|
|
24991
|
-
Route53Client
|
|
24992
|
-
} from "@aws-sdk/client-route-53";
|
|
24993
|
-
async function getExistingTXTRecords(client2, hostedZoneId, domain) {
|
|
24994
|
-
try {
|
|
24995
|
-
const response = await client2.send(
|
|
24996
|
-
new ListResourceRecordSetsCommand({
|
|
24997
|
-
HostedZoneId: hostedZoneId,
|
|
24998
|
-
StartRecordName: domain,
|
|
24999
|
-
StartRecordType: "TXT",
|
|
25000
|
-
MaxItems: 100
|
|
25001
|
-
})
|
|
25002
|
-
);
|
|
25003
|
-
const txtRecordSet = response.ResourceRecordSets?.find(
|
|
25004
|
-
(rs) => rs.Type === "TXT" && (rs.Name === domain || rs.Name === `${domain}.`)
|
|
25005
|
-
);
|
|
25006
|
-
if (!txtRecordSet?.ResourceRecords) {
|
|
25007
|
-
return { allValues: [], spfValue: null, ttl: 1800 };
|
|
25008
|
-
}
|
|
25009
|
-
const allValues = [];
|
|
25010
|
-
let spfValue = null;
|
|
25011
|
-
for (const record of txtRecordSet.ResourceRecords) {
|
|
25012
|
-
const value = record.Value || "";
|
|
25013
|
-
allValues.push(value);
|
|
25014
|
-
const unquoted = value.replace(/^"|"$/g, "");
|
|
25015
|
-
if (unquoted.startsWith("v=spf1")) {
|
|
25016
|
-
spfValue = unquoted;
|
|
25017
|
-
}
|
|
25018
|
-
}
|
|
25019
|
-
return {
|
|
25020
|
-
allValues,
|
|
25021
|
-
spfValue,
|
|
25022
|
-
ttl: txtRecordSet.TTL || 1800
|
|
25023
|
-
};
|
|
25024
|
-
} catch (_error) {
|
|
25025
|
-
return { allValues: [], spfValue: null, ttl: 1800 };
|
|
25026
|
-
}
|
|
25027
|
-
}
|
|
25028
|
-
function mergeSPFRecord(existingSPF) {
|
|
25029
|
-
const sesInclude = "include:amazonses.com";
|
|
25030
|
-
if (existingSPF.includes(sesInclude)) {
|
|
25031
|
-
return existingSPF;
|
|
25032
|
-
}
|
|
25033
|
-
const allMatch = existingSPF.match(/\s([~+?-]?all)$/);
|
|
25034
|
-
if (allMatch) {
|
|
25035
|
-
const beforeAll = existingSPF.slice(0, allMatch.index);
|
|
25036
|
-
const allPart = allMatch[1];
|
|
25037
|
-
return `${beforeAll} ${sesInclude} ${allPart}`;
|
|
25038
|
-
}
|
|
25039
|
-
return `${existingSPF} ${sesInclude}`;
|
|
25040
|
-
}
|
|
25041
|
-
async function getExistingRecords(client2, hostedZoneId) {
|
|
25042
|
-
try {
|
|
25043
|
-
const response = await client2.send(
|
|
25044
|
-
new ListResourceRecordSetsCommand({
|
|
25045
|
-
HostedZoneId: hostedZoneId,
|
|
25046
|
-
MaxItems: 500
|
|
25047
|
-
})
|
|
25048
|
-
);
|
|
25049
|
-
return response.ResourceRecordSets || [];
|
|
25050
|
-
} catch (_error) {
|
|
25051
|
-
return [];
|
|
25052
|
-
}
|
|
25053
|
-
}
|
|
25054
|
-
function findExistingRecord(records, name, type) {
|
|
25055
|
-
const normalizedName = name.endsWith(".") ? name : `${name}.`;
|
|
25056
|
-
return records.find((r6) => r6.Name === normalizedName && r6.Type === type) || null;
|
|
25057
|
-
}
|
|
25058
|
-
function getRecordValue(record) {
|
|
25059
|
-
if (!record?.ResourceRecords?.length) {
|
|
25060
|
-
return null;
|
|
25061
|
-
}
|
|
25062
|
-
return record.ResourceRecords.map((r6) => r6.Value || "").join(", ");
|
|
24982
|
+
function normalizeRecordValue(value) {
|
|
24983
|
+
return value.replace(/^"/, "").replace(/"$/, "").trim();
|
|
25063
24984
|
}
|
|
25064
|
-
|
|
25065
|
-
const
|
|
25066
|
-
const
|
|
25067
|
-
|
|
25068
|
-
|
|
25069
|
-
for (const token of dkimTokens) {
|
|
25070
|
-
const name = `${token}._domainkey.${domain}`;
|
|
25071
|
-
const proposedValue = `${token}.dkim.amazonses.com`;
|
|
25072
|
-
const existing = findExistingRecord(existingRecords, name, "CNAME");
|
|
25073
|
-
const existingValue = getRecordValue(existing);
|
|
25074
|
-
let status2 = "new";
|
|
25075
|
-
if (existingValue === proposedValue) {
|
|
25076
|
-
status2 = "no_change";
|
|
25077
|
-
} else if (existingValue) {
|
|
25078
|
-
status2 = "update";
|
|
25079
|
-
}
|
|
25080
|
-
records.push({
|
|
25081
|
-
name,
|
|
25082
|
-
type: "CNAME",
|
|
25083
|
-
proposedValue,
|
|
25084
|
-
existingValue,
|
|
25085
|
-
status: status2,
|
|
25086
|
-
category: "dkim"
|
|
25087
|
-
});
|
|
25088
|
-
}
|
|
25089
|
-
const proposedSPF = existingTXT.spfValue ? mergeSPFRecord(existingTXT.spfValue) : "v=spf1 include:amazonses.com ~all";
|
|
25090
|
-
let spfStatus = "new";
|
|
25091
|
-
if (existingTXT.spfValue) {
|
|
25092
|
-
if (existingTXT.spfValue === proposedSPF) {
|
|
25093
|
-
spfStatus = "no_change";
|
|
25094
|
-
} else if (existingTXT.spfValue.includes("include:amazonses.com")) {
|
|
25095
|
-
spfStatus = "no_change";
|
|
25096
|
-
} else {
|
|
25097
|
-
spfStatus = "update";
|
|
25098
|
-
}
|
|
25099
|
-
}
|
|
25100
|
-
records.push({
|
|
25101
|
-
name: domain,
|
|
25102
|
-
type: "TXT",
|
|
25103
|
-
proposedValue: `"${proposedSPF}"`,
|
|
25104
|
-
existingValue: existingTXT.spfValue ? `"${existingTXT.spfValue}"` : null,
|
|
25105
|
-
status: spfStatus,
|
|
25106
|
-
category: "spf",
|
|
25107
|
-
conflictReason: void 0
|
|
25108
|
-
});
|
|
25109
|
-
const dmarcName = `_dmarc.${domain}`;
|
|
25110
|
-
const dmarcRuaDomain = mailFromDomain || domain;
|
|
25111
|
-
const proposedDMARC = `"v=DMARC1; p=quarantine; sp=quarantine; np=reject; rua=mailto:postmaster@${dmarcRuaDomain}"`;
|
|
25112
|
-
const existingDMARC = findExistingRecord(existingRecords, dmarcName, "TXT");
|
|
25113
|
-
const existingDMARCValue = getRecordValue(existingDMARC);
|
|
25114
|
-
let dmarcStatus = "new";
|
|
25115
|
-
let dmarcConflictReason;
|
|
25116
|
-
if (existingDMARCValue) {
|
|
25117
|
-
if (existingDMARCValue === proposedDMARC) {
|
|
25118
|
-
dmarcStatus = "no_change";
|
|
25119
|
-
} else {
|
|
25120
|
-
dmarcStatus = "conflict";
|
|
25121
|
-
dmarcConflictReason = "Existing DMARC policy will be replaced";
|
|
25122
|
-
}
|
|
25123
|
-
}
|
|
25124
|
-
records.push({
|
|
25125
|
-
name: dmarcName,
|
|
25126
|
-
type: "TXT",
|
|
25127
|
-
proposedValue: proposedDMARC,
|
|
25128
|
-
existingValue: existingDMARCValue,
|
|
25129
|
-
status: dmarcStatus,
|
|
25130
|
-
category: "dmarc",
|
|
25131
|
-
conflictReason: dmarcConflictReason
|
|
25132
|
-
});
|
|
25133
|
-
if (customTrackingDomain) {
|
|
25134
|
-
const proposedTracking = `r.${region}.awstrack.me`;
|
|
25135
|
-
const existingTracking = findExistingRecord(
|
|
25136
|
-
existingRecords,
|
|
25137
|
-
customTrackingDomain,
|
|
25138
|
-
"CNAME"
|
|
25139
|
-
);
|
|
25140
|
-
const existingTrackingValue = getRecordValue(existingTracking);
|
|
25141
|
-
let trackingStatus = "new";
|
|
25142
|
-
let trackingConflictReason;
|
|
25143
|
-
if (existingTrackingValue === proposedTracking) {
|
|
25144
|
-
trackingStatus = "no_change";
|
|
25145
|
-
} else if (existingTrackingValue) {
|
|
25146
|
-
trackingStatus = "conflict";
|
|
25147
|
-
trackingConflictReason = `Points to ${existingTrackingValue}, will be replaced`;
|
|
25148
|
-
}
|
|
25149
|
-
records.push({
|
|
25150
|
-
name: customTrackingDomain,
|
|
25151
|
-
type: "CNAME",
|
|
25152
|
-
proposedValue: proposedTracking,
|
|
25153
|
-
existingValue: existingTrackingValue,
|
|
25154
|
-
status: trackingStatus,
|
|
25155
|
-
category: "tracking",
|
|
25156
|
-
conflictReason: trackingConflictReason
|
|
25157
|
-
});
|
|
25158
|
-
}
|
|
25159
|
-
if (mailFromDomain) {
|
|
25160
|
-
const proposedMX = `10 feedback-smtp.${region}.amazonses.com`;
|
|
25161
|
-
const existingMX = findExistingRecord(
|
|
25162
|
-
existingRecords,
|
|
25163
|
-
mailFromDomain,
|
|
25164
|
-
"MX"
|
|
25165
|
-
);
|
|
25166
|
-
const existingMXValue = getRecordValue(existingMX);
|
|
25167
|
-
let mxStatus = "new";
|
|
25168
|
-
if (existingMXValue === proposedMX) {
|
|
25169
|
-
mxStatus = "no_change";
|
|
25170
|
-
} else if (existingMXValue) {
|
|
25171
|
-
mxStatus = "update";
|
|
25172
|
-
}
|
|
25173
|
-
records.push({
|
|
25174
|
-
name: mailFromDomain,
|
|
25175
|
-
type: "MX",
|
|
25176
|
-
proposedValue: proposedMX,
|
|
25177
|
-
existingValue: existingMXValue,
|
|
25178
|
-
status: mxStatus,
|
|
25179
|
-
category: "mailfrom_mx"
|
|
25180
|
-
});
|
|
25181
|
-
const mailFromSPF = '"v=spf1 include:amazonses.com ~all"';
|
|
25182
|
-
const existingMailFromTXT = await getExistingTXTRecords(
|
|
25183
|
-
client2,
|
|
25184
|
-
hostedZoneId,
|
|
25185
|
-
mailFromDomain
|
|
25186
|
-
);
|
|
25187
|
-
let mailFromSpfStatus = "new";
|
|
25188
|
-
if (existingMailFromTXT.spfValue?.includes("include:amazonses.com")) {
|
|
25189
|
-
mailFromSpfStatus = "no_change";
|
|
25190
|
-
} else if (existingMailFromTXT.spfValue) {
|
|
25191
|
-
mailFromSpfStatus = "update";
|
|
25192
|
-
}
|
|
25193
|
-
records.push({
|
|
25194
|
-
name: mailFromDomain,
|
|
25195
|
-
type: "TXT",
|
|
25196
|
-
proposedValue: mailFromSPF,
|
|
25197
|
-
existingValue: existingMailFromTXT.spfValue ? `"${existingMailFromTXT.spfValue}"` : null,
|
|
25198
|
-
status: mailFromSpfStatus,
|
|
25199
|
-
category: "mailfrom_spf"
|
|
25200
|
-
});
|
|
24985
|
+
function valuesMatch(type, actual, expected) {
|
|
24986
|
+
const normalizedActual = normalizeRecordValue(actual);
|
|
24987
|
+
const normalizedExpected = normalizeRecordValue(expected);
|
|
24988
|
+
if (type === "MX") {
|
|
24989
|
+
return normalizedActual.toLowerCase() === normalizedExpected.toLowerCase();
|
|
25201
24990
|
}
|
|
25202
|
-
|
|
25203
|
-
const newCount = records.filter((r6) => r6.status === "new").length;
|
|
25204
|
-
const updateCount = records.filter((r6) => r6.status === "update").length;
|
|
25205
|
-
const noChangeCount = records.filter((r6) => r6.status === "no_change").length;
|
|
25206
|
-
return {
|
|
25207
|
-
records,
|
|
25208
|
-
hasConflicts: conflictCount > 0,
|
|
25209
|
-
conflictCount,
|
|
25210
|
-
newCount,
|
|
25211
|
-
updateCount,
|
|
25212
|
-
noChangeCount
|
|
25213
|
-
};
|
|
24991
|
+
return normalizedActual === normalizedExpected;
|
|
25214
24992
|
}
|
|
25215
|
-
|
|
25216
|
-
|
|
25217
|
-
|
|
25218
|
-
|
|
25219
|
-
|
|
25220
|
-
|
|
25221
|
-
|
|
25222
|
-
|
|
25223
|
-
|
|
25224
|
-
|
|
25225
|
-
|
|
25226
|
-
|
|
25227
|
-
}
|
|
25228
|
-
});
|
|
25229
|
-
}
|
|
25230
|
-
}
|
|
25231
|
-
if (selectedCategories.has("spf")) {
|
|
25232
|
-
const existingTXT = await getExistingTXTRecords(
|
|
25233
|
-
client2,
|
|
25234
|
-
hostedZoneId,
|
|
25235
|
-
domain
|
|
25236
|
-
);
|
|
25237
|
-
const newTXTValues = [];
|
|
25238
|
-
if (existingTXT.spfValue) {
|
|
25239
|
-
const mergedSPF = mergeSPFRecord(existingTXT.spfValue);
|
|
25240
|
-
newTXTValues.push(`"${mergedSPF}"`);
|
|
25241
|
-
for (const value of existingTXT.allValues) {
|
|
25242
|
-
const unquoted = value.replace(/^"|"$/g, "");
|
|
25243
|
-
if (!unquoted.startsWith("v=spf1")) {
|
|
25244
|
-
newTXTValues.push(value);
|
|
25245
|
-
}
|
|
25246
|
-
}
|
|
25247
|
-
} else if (existingTXT.allValues.length > 0) {
|
|
25248
|
-
newTXTValues.push('"v=spf1 include:amazonses.com ~all"');
|
|
25249
|
-
newTXTValues.push(...existingTXT.allValues);
|
|
25250
|
-
} else {
|
|
25251
|
-
newTXTValues.push('"v=spf1 include:amazonses.com ~all"');
|
|
25252
|
-
}
|
|
25253
|
-
changes.push({
|
|
25254
|
-
Action: "UPSERT",
|
|
25255
|
-
ResourceRecordSet: {
|
|
25256
|
-
Name: domain,
|
|
25257
|
-
Type: "TXT",
|
|
25258
|
-
TTL: existingTXT.ttl,
|
|
25259
|
-
ResourceRecords: newTXTValues.map((v) => ({ Value: v }))
|
|
24993
|
+
var CLOUDFLARE_API_BASE, CloudflareDNSClient;
|
|
24994
|
+
var init_cloudflare = __esm({
|
|
24995
|
+
"src/utils/dns/cloudflare.ts"() {
|
|
24996
|
+
"use strict";
|
|
24997
|
+
init_esm_shims();
|
|
24998
|
+
CLOUDFLARE_API_BASE = "https://api.cloudflare.com/client/v4";
|
|
24999
|
+
CloudflareDNSClient = class {
|
|
25000
|
+
zoneId;
|
|
25001
|
+
apiToken;
|
|
25002
|
+
constructor(zoneId, apiToken) {
|
|
25003
|
+
this.zoneId = zoneId;
|
|
25004
|
+
this.apiToken = apiToken;
|
|
25260
25005
|
}
|
|
25261
|
-
|
|
25262
|
-
|
|
25263
|
-
|
|
25264
|
-
const dmarcRuaDomain = mailFromDomain || domain;
|
|
25265
|
-
changes.push({
|
|
25266
|
-
Action: "UPSERT",
|
|
25267
|
-
ResourceRecordSet: {
|
|
25268
|
-
Name: `_dmarc.${domain}`,
|
|
25269
|
-
Type: "TXT",
|
|
25270
|
-
TTL: 1800,
|
|
25271
|
-
ResourceRecords: [
|
|
25006
|
+
async request(endpoint, method = "GET", body) {
|
|
25007
|
+
const response = await fetch(
|
|
25008
|
+
`${CLOUDFLARE_API_BASE}/zones/${this.zoneId}${endpoint}`,
|
|
25272
25009
|
{
|
|
25273
|
-
|
|
25010
|
+
method,
|
|
25011
|
+
headers: {
|
|
25012
|
+
Authorization: `Bearer ${this.apiToken}`,
|
|
25013
|
+
"Content-Type": "application/json"
|
|
25014
|
+
},
|
|
25015
|
+
body: body ? JSON.stringify(body) : void 0
|
|
25274
25016
|
}
|
|
25275
|
-
|
|
25017
|
+
);
|
|
25018
|
+
return response.json();
|
|
25276
25019
|
}
|
|
25277
|
-
|
|
25278
|
-
|
|
25279
|
-
|
|
25280
|
-
|
|
25281
|
-
|
|
25282
|
-
|
|
25283
|
-
|
|
25284
|
-
|
|
25285
|
-
|
|
25286
|
-
|
|
25287
|
-
|
|
25288
|
-
|
|
25020
|
+
async createRecord(name, type, content, priority) {
|
|
25021
|
+
const recordContent = type === "TXT" ? `"${content}"` : content;
|
|
25022
|
+
const body = {
|
|
25023
|
+
name,
|
|
25024
|
+
type,
|
|
25025
|
+
content: recordContent,
|
|
25026
|
+
ttl: 1800,
|
|
25027
|
+
proxied: false
|
|
25028
|
+
// Must not be proxied for email records
|
|
25029
|
+
};
|
|
25030
|
+
if (priority !== void 0) {
|
|
25031
|
+
body.priority = priority;
|
|
25032
|
+
}
|
|
25033
|
+
const result = await this.request(
|
|
25034
|
+
"/dns_records",
|
|
25035
|
+
"POST",
|
|
25036
|
+
body
|
|
25037
|
+
);
|
|
25038
|
+
return result.success;
|
|
25289
25039
|
}
|
|
25290
|
-
|
|
25291
|
-
|
|
25292
|
-
|
|
25293
|
-
|
|
25294
|
-
|
|
25295
|
-
|
|
25296
|
-
|
|
25297
|
-
|
|
25298
|
-
TTL: 1800,
|
|
25299
|
-
ResourceRecords: [
|
|
25300
|
-
{ Value: `10 feedback-smtp.${region}.amazonses.com` }
|
|
25301
|
-
]
|
|
25040
|
+
async findRecord(name, type) {
|
|
25041
|
+
const result = await this.request(
|
|
25042
|
+
`/dns_records?name=${encodeURIComponent(name)}&type=${type}`
|
|
25043
|
+
);
|
|
25044
|
+
if (result.success && result.result.length > 0) {
|
|
25045
|
+
return result.result[0];
|
|
25046
|
+
}
|
|
25047
|
+
return null;
|
|
25302
25048
|
}
|
|
25303
|
-
|
|
25304
|
-
|
|
25305
|
-
|
|
25306
|
-
|
|
25307
|
-
|
|
25308
|
-
|
|
25309
|
-
|
|
25310
|
-
|
|
25311
|
-
|
|
25312
|
-
|
|
25049
|
+
/**
|
|
25050
|
+
* All records matching name+type. Unlike `findRecord`, this returns every
|
|
25051
|
+
* match — callers that need to know whether a name ALREADY has mail records
|
|
25052
|
+
* cannot use a single-result lookup.
|
|
25053
|
+
*/
|
|
25054
|
+
async listRecords(name, type) {
|
|
25055
|
+
const result = await this.request(
|
|
25056
|
+
`/dns_records?name=${encodeURIComponent(name)}&type=${type}`
|
|
25057
|
+
);
|
|
25058
|
+
if (!(result.success && result.result)) {
|
|
25059
|
+
return [];
|
|
25060
|
+
}
|
|
25061
|
+
return result.result.map((r6) => ({
|
|
25062
|
+
content: r6.content,
|
|
25063
|
+
priority: r6.priority
|
|
25064
|
+
}));
|
|
25313
25065
|
}
|
|
25314
|
-
|
|
25315
|
-
|
|
25316
|
-
|
|
25317
|
-
|
|
25318
|
-
|
|
25319
|
-
|
|
25320
|
-
new ChangeResourceRecordSetsCommand({
|
|
25321
|
-
HostedZoneId: hostedZoneId,
|
|
25322
|
-
ChangeBatch: {
|
|
25323
|
-
Changes: changes
|
|
25066
|
+
async deleteRecord(recordId) {
|
|
25067
|
+
const result = await this.request(
|
|
25068
|
+
`/dns_records/${recordId}`,
|
|
25069
|
+
"DELETE"
|
|
25070
|
+
);
|
|
25071
|
+
return result.success;
|
|
25324
25072
|
}
|
|
25325
|
-
|
|
25326
|
-
|
|
25327
|
-
|
|
25328
|
-
|
|
25329
|
-
|
|
25330
|
-
|
|
25331
|
-
|
|
25332
|
-
|
|
25333
|
-
|
|
25334
|
-
|
|
25335
|
-
|
|
25336
|
-
);
|
|
25337
|
-
const zone = response.HostedZones?.[0];
|
|
25338
|
-
if (zone && zone.Name === `${domain}.` && zone.Id) {
|
|
25339
|
-
return {
|
|
25340
|
-
id: zone.Id.replace("/hostedzone/", ""),
|
|
25341
|
-
name: zone.Name
|
|
25342
|
-
};
|
|
25343
|
-
}
|
|
25344
|
-
return null;
|
|
25345
|
-
} catch (_error) {
|
|
25346
|
-
return null;
|
|
25347
|
-
}
|
|
25348
|
-
}
|
|
25349
|
-
async function createDNSRecords(hostedZoneId, domain, dkimTokens, region, customTrackingDomain, mailFromDomain, cloudFrontDomain) {
|
|
25350
|
-
const client2 = new Route53Client({ region });
|
|
25351
|
-
const changes = [];
|
|
25352
|
-
for (const token of dkimTokens) {
|
|
25353
|
-
changes.push({
|
|
25354
|
-
Action: "UPSERT",
|
|
25355
|
-
ResourceRecordSet: {
|
|
25356
|
-
Name: `${token}._domainkey.${domain}`,
|
|
25357
|
-
Type: "CNAME",
|
|
25358
|
-
TTL: 1800,
|
|
25359
|
-
ResourceRecords: [{ Value: `${token}.dkim.amazonses.com` }]
|
|
25073
|
+
/**
|
|
25074
|
+
* Delete an existing record by name+type, if one exists. `createRecord`
|
|
25075
|
+
* always POSTs a new record rather than upserting, so callers that need
|
|
25076
|
+
* to change an existing record's value (e.g. swapping the tracking CNAME
|
|
25077
|
+
* target) must delete the old one first to avoid ending up with two.
|
|
25078
|
+
*/
|
|
25079
|
+
async deleteRecordIfExists(name, type) {
|
|
25080
|
+
const existing = await this.findRecord(name, type);
|
|
25081
|
+
if (existing) {
|
|
25082
|
+
await this.deleteRecord(existing.id);
|
|
25083
|
+
}
|
|
25360
25084
|
}
|
|
25361
|
-
|
|
25362
|
-
|
|
25363
|
-
|
|
25364
|
-
|
|
25365
|
-
|
|
25366
|
-
|
|
25367
|
-
|
|
25368
|
-
|
|
25369
|
-
|
|
25370
|
-
|
|
25371
|
-
|
|
25372
|
-
|
|
25373
|
-
|
|
25374
|
-
|
|
25375
|
-
|
|
25376
|
-
|
|
25377
|
-
|
|
25378
|
-
|
|
25379
|
-
|
|
25380
|
-
|
|
25381
|
-
|
|
25382
|
-
|
|
25383
|
-
|
|
25384
|
-
|
|
25385
|
-
|
|
25386
|
-
|
|
25387
|
-
|
|
25388
|
-
|
|
25389
|
-
|
|
25390
|
-
|
|
25391
|
-
|
|
25392
|
-
|
|
25393
|
-
|
|
25394
|
-
Type: "TXT",
|
|
25395
|
-
TTL: 1800,
|
|
25396
|
-
ResourceRecords: [
|
|
25397
|
-
{
|
|
25398
|
-
Value: `"v=DMARC1; p=quarantine; sp=quarantine; np=reject; rua=mailto:postmaster@${dmarcRuaDomain}"`
|
|
25399
|
-
}
|
|
25400
|
-
]
|
|
25401
|
-
}
|
|
25402
|
-
});
|
|
25403
|
-
if (customTrackingDomain) {
|
|
25404
|
-
const targetDomain = cloudFrontDomain || `r.${region}.awstrack.me`;
|
|
25405
|
-
changes.push({
|
|
25406
|
-
Action: "UPSERT",
|
|
25407
|
-
ResourceRecordSet: {
|
|
25408
|
-
Name: customTrackingDomain,
|
|
25409
|
-
Type: "CNAME",
|
|
25410
|
-
TTL: 1800,
|
|
25411
|
-
ResourceRecords: [{ Value: targetDomain }]
|
|
25412
|
-
}
|
|
25413
|
-
});
|
|
25414
|
-
}
|
|
25415
|
-
if (mailFromDomain) {
|
|
25416
|
-
changes.push({
|
|
25417
|
-
Action: "UPSERT",
|
|
25418
|
-
ResourceRecordSet: {
|
|
25419
|
-
Name: mailFromDomain,
|
|
25420
|
-
Type: "MX",
|
|
25421
|
-
TTL: 1800,
|
|
25422
|
-
ResourceRecords: [
|
|
25423
|
-
{ Value: `10 feedback-smtp.${region}.amazonses.com` }
|
|
25424
|
-
]
|
|
25425
|
-
}
|
|
25426
|
-
});
|
|
25427
|
-
changes.push({
|
|
25428
|
-
Action: "UPSERT",
|
|
25429
|
-
ResourceRecordSet: {
|
|
25430
|
-
Name: mailFromDomain,
|
|
25431
|
-
Type: "TXT",
|
|
25432
|
-
TTL: 1800,
|
|
25433
|
-
ResourceRecords: [{ Value: '"v=spf1 include:amazonses.com ~all"' }]
|
|
25434
|
-
}
|
|
25435
|
-
});
|
|
25436
|
-
}
|
|
25437
|
-
await client2.send(
|
|
25438
|
-
new ChangeResourceRecordSetsCommand({
|
|
25439
|
-
HostedZoneId: hostedZoneId,
|
|
25440
|
-
ChangeBatch: {
|
|
25441
|
-
Changes: changes
|
|
25442
|
-
}
|
|
25443
|
-
})
|
|
25444
|
-
);
|
|
25445
|
-
}
|
|
25446
|
-
async function deleteDNSRecords(hostedZoneId, domain, dkimTokens, region, customTrackingDomain, mailFromDomain) {
|
|
25447
|
-
const client2 = new Route53Client({ region });
|
|
25448
|
-
const response = await client2.send(
|
|
25449
|
-
new ListResourceRecordSetsCommand({
|
|
25450
|
-
HostedZoneId: hostedZoneId,
|
|
25451
|
-
MaxItems: 500
|
|
25452
|
-
})
|
|
25453
|
-
);
|
|
25454
|
-
const recordSets = response.ResourceRecordSets || [];
|
|
25455
|
-
const changes = [];
|
|
25456
|
-
const addDeletionIfExists = (name, type) => {
|
|
25457
|
-
const normalizedName = name.endsWith(".") ? name : `${name}.`;
|
|
25458
|
-
const record = recordSets.find(
|
|
25459
|
-
(rs) => rs.Name === normalizedName && rs.Type === type
|
|
25460
|
-
);
|
|
25461
|
-
if (record?.ResourceRecords) {
|
|
25462
|
-
changes.push({
|
|
25463
|
-
Action: "DELETE",
|
|
25464
|
-
ResourceRecordSet: record
|
|
25465
|
-
});
|
|
25466
|
-
}
|
|
25467
|
-
};
|
|
25468
|
-
for (const token of dkimTokens) {
|
|
25469
|
-
addDeletionIfExists(`${token}._domainkey.${domain}`, "CNAME");
|
|
25470
|
-
}
|
|
25471
|
-
addDeletionIfExists(`_dmarc.${domain}`, "TXT");
|
|
25472
|
-
if (customTrackingDomain) {
|
|
25473
|
-
addDeletionIfExists(customTrackingDomain, "CNAME");
|
|
25474
|
-
}
|
|
25475
|
-
if (mailFromDomain) {
|
|
25476
|
-
addDeletionIfExists(mailFromDomain, "MX");
|
|
25477
|
-
addDeletionIfExists(mailFromDomain, "TXT");
|
|
25478
|
-
}
|
|
25479
|
-
if (changes.length === 0) {
|
|
25480
|
-
return;
|
|
25481
|
-
}
|
|
25482
|
-
await client2.send(
|
|
25483
|
-
new ChangeResourceRecordSetsCommand({
|
|
25484
|
-
HostedZoneId: hostedZoneId,
|
|
25485
|
-
ChangeBatch: {
|
|
25486
|
-
Changes: changes
|
|
25487
|
-
}
|
|
25488
|
-
})
|
|
25489
|
-
);
|
|
25490
|
-
}
|
|
25491
|
-
var init_route53 = __esm({
|
|
25492
|
-
"src/utils/route53.ts"() {
|
|
25493
|
-
"use strict";
|
|
25494
|
-
init_esm_shims();
|
|
25495
|
-
}
|
|
25496
|
-
});
|
|
25497
|
-
|
|
25498
|
-
// src/utils/dns/cloudflare.ts
|
|
25499
|
-
var cloudflare_exports = {};
|
|
25500
|
-
__export(cloudflare_exports, {
|
|
25501
|
-
CloudflareDNSClient: () => CloudflareDNSClient
|
|
25502
|
-
});
|
|
25503
|
-
function normalizeRecordValue(value) {
|
|
25504
|
-
return value.replace(/^"/, "").replace(/"$/, "").trim();
|
|
25505
|
-
}
|
|
25506
|
-
function valuesMatch(type, actual, expected) {
|
|
25507
|
-
const normalizedActual = normalizeRecordValue(actual);
|
|
25508
|
-
const normalizedExpected = normalizeRecordValue(expected);
|
|
25509
|
-
if (type === "MX") {
|
|
25510
|
-
return normalizedActual.toLowerCase() === normalizedExpected.toLowerCase();
|
|
25511
|
-
}
|
|
25512
|
-
return normalizedActual === normalizedExpected;
|
|
25513
|
-
}
|
|
25514
|
-
var CLOUDFLARE_API_BASE, CloudflareDNSClient;
|
|
25515
|
-
var init_cloudflare = __esm({
|
|
25516
|
-
"src/utils/dns/cloudflare.ts"() {
|
|
25517
|
-
"use strict";
|
|
25518
|
-
init_esm_shims();
|
|
25519
|
-
CLOUDFLARE_API_BASE = "https://api.cloudflare.com/client/v4";
|
|
25520
|
-
CloudflareDNSClient = class {
|
|
25521
|
-
zoneId;
|
|
25522
|
-
apiToken;
|
|
25523
|
-
constructor(zoneId, apiToken) {
|
|
25524
|
-
this.zoneId = zoneId;
|
|
25525
|
-
this.apiToken = apiToken;
|
|
25526
|
-
}
|
|
25527
|
-
async request(endpoint, method = "GET", body) {
|
|
25528
|
-
const response = await fetch(
|
|
25529
|
-
`${CLOUDFLARE_API_BASE}/zones/${this.zoneId}${endpoint}`,
|
|
25530
|
-
{
|
|
25531
|
-
method,
|
|
25532
|
-
headers: {
|
|
25533
|
-
Authorization: `Bearer ${this.apiToken}`,
|
|
25534
|
-
"Content-Type": "application/json"
|
|
25535
|
-
},
|
|
25536
|
-
body: body ? JSON.stringify(body) : void 0
|
|
25537
|
-
}
|
|
25538
|
-
);
|
|
25539
|
-
return response.json();
|
|
25540
|
-
}
|
|
25541
|
-
async createRecord(name, type, content, priority) {
|
|
25542
|
-
const recordContent = type === "TXT" ? `"${content}"` : content;
|
|
25543
|
-
const body = {
|
|
25544
|
-
name,
|
|
25545
|
-
type,
|
|
25546
|
-
content: recordContent,
|
|
25547
|
-
ttl: 1800,
|
|
25548
|
-
proxied: false
|
|
25549
|
-
// Must not be proxied for email records
|
|
25550
|
-
};
|
|
25551
|
-
if (priority !== void 0) {
|
|
25552
|
-
body.priority = priority;
|
|
25553
|
-
}
|
|
25554
|
-
const result = await this.request(
|
|
25555
|
-
"/dns_records",
|
|
25556
|
-
"POST",
|
|
25557
|
-
body
|
|
25558
|
-
);
|
|
25559
|
-
return result.success;
|
|
25560
|
-
}
|
|
25561
|
-
async findRecord(name, type) {
|
|
25562
|
-
const result = await this.request(
|
|
25563
|
-
`/dns_records?name=${encodeURIComponent(name)}&type=${type}`
|
|
25564
|
-
);
|
|
25565
|
-
if (result.success && result.result.length > 0) {
|
|
25566
|
-
return result.result[0];
|
|
25567
|
-
}
|
|
25568
|
-
return null;
|
|
25569
|
-
}
|
|
25570
|
-
/**
|
|
25571
|
-
* All records matching name+type. Unlike `findRecord`, this returns every
|
|
25572
|
-
* match — callers that need to know whether a name ALREADY has mail records
|
|
25573
|
-
* cannot use a single-result lookup.
|
|
25574
|
-
*/
|
|
25575
|
-
async listRecords(name, type) {
|
|
25576
|
-
const result = await this.request(
|
|
25577
|
-
`/dns_records?name=${encodeURIComponent(name)}&type=${type}`
|
|
25578
|
-
);
|
|
25579
|
-
if (!(result.success && result.result)) {
|
|
25580
|
-
return [];
|
|
25581
|
-
}
|
|
25582
|
-
return result.result.map((r6) => ({
|
|
25583
|
-
content: r6.content,
|
|
25584
|
-
priority: r6.priority
|
|
25585
|
-
}));
|
|
25586
|
-
}
|
|
25587
|
-
async deleteRecord(recordId) {
|
|
25588
|
-
const result = await this.request(
|
|
25589
|
-
`/dns_records/${recordId}`,
|
|
25590
|
-
"DELETE"
|
|
25591
|
-
);
|
|
25592
|
-
return result.success;
|
|
25593
|
-
}
|
|
25594
|
-
/**
|
|
25595
|
-
* Delete an existing record by name+type, if one exists. `createRecord`
|
|
25596
|
-
* always POSTs a new record rather than upserting, so callers that need
|
|
25597
|
-
* to change an existing record's value (e.g. swapping the tracking CNAME
|
|
25598
|
-
* target) must delete the old one first to avoid ending up with two.
|
|
25599
|
-
*/
|
|
25600
|
-
async deleteRecordIfExists(name, type) {
|
|
25601
|
-
const existing = await this.findRecord(name, type);
|
|
25602
|
-
if (existing) {
|
|
25603
|
-
await this.deleteRecord(existing.id);
|
|
25604
|
-
}
|
|
25605
|
-
}
|
|
25606
|
-
/**
|
|
25607
|
-
* Delete records matching name + type + exact value. Value matching is the
|
|
25608
|
-
* point: a name can carry several TXT or MX records, and deleting "the first
|
|
25609
|
-
* one" can remove a record Wraps did not create.
|
|
25610
|
-
*/
|
|
25611
|
-
async deleteRecordsByValue(name, type, value) {
|
|
25612
|
-
let result;
|
|
25613
|
-
try {
|
|
25614
|
-
result = await this.request(
|
|
25615
|
-
`/dns_records?name=${encodeURIComponent(name)}&type=${type}`
|
|
25616
|
-
);
|
|
25617
|
-
} catch (error) {
|
|
25618
|
-
return {
|
|
25619
|
-
deleted: 0,
|
|
25620
|
-
error: error instanceof Error ? error.message : "Unknown error"
|
|
25621
|
-
};
|
|
25622
|
-
}
|
|
25623
|
-
if (!result.success) {
|
|
25624
|
-
return {
|
|
25625
|
-
deleted: 0,
|
|
25626
|
-
error: result.errors?.map((e6) => e6.message).join("; ") || "Failed to list DNS records"
|
|
25627
|
-
};
|
|
25628
|
-
}
|
|
25629
|
-
const matches = result.result.filter(
|
|
25630
|
-
(record) => valuesMatch(type, record.content, value)
|
|
25631
|
-
);
|
|
25632
|
-
let deleted = 0;
|
|
25633
|
-
for (const record of matches) {
|
|
25634
|
-
if (await this.deleteRecord(record.id)) {
|
|
25635
|
-
deleted++;
|
|
25636
|
-
}
|
|
25637
|
-
}
|
|
25638
|
-
return { deleted };
|
|
25085
|
+
/**
|
|
25086
|
+
* Delete records matching name + type + exact value. Value matching is the
|
|
25087
|
+
* point: a name can carry several TXT or MX records, and deleting "the first
|
|
25088
|
+
* one" can remove a record Wraps did not create.
|
|
25089
|
+
*/
|
|
25090
|
+
async deleteRecordsByValue(name, type, value) {
|
|
25091
|
+
let result;
|
|
25092
|
+
try {
|
|
25093
|
+
result = await this.request(
|
|
25094
|
+
`/dns_records?name=${encodeURIComponent(name)}&type=${type}`
|
|
25095
|
+
);
|
|
25096
|
+
} catch (error) {
|
|
25097
|
+
return {
|
|
25098
|
+
deleted: 0,
|
|
25099
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
25100
|
+
};
|
|
25101
|
+
}
|
|
25102
|
+
if (!result.success) {
|
|
25103
|
+
return {
|
|
25104
|
+
deleted: 0,
|
|
25105
|
+
error: result.errors?.map((e6) => e6.message).join("; ") || "Failed to list DNS records"
|
|
25106
|
+
};
|
|
25107
|
+
}
|
|
25108
|
+
const matches = result.result.filter(
|
|
25109
|
+
(record) => valuesMatch(type, record.content, value)
|
|
25110
|
+
);
|
|
25111
|
+
let deleted = 0;
|
|
25112
|
+
for (const record of matches) {
|
|
25113
|
+
if (await this.deleteRecord(record.id)) {
|
|
25114
|
+
deleted++;
|
|
25115
|
+
}
|
|
25116
|
+
}
|
|
25117
|
+
return { deleted };
|
|
25639
25118
|
}
|
|
25640
25119
|
/**
|
|
25641
25120
|
* Create arbitrary DNS records from a list
|
|
@@ -26227,174 +25706,896 @@ var init_vercel = __esm({
|
|
|
26227
25706
|
]
|
|
26228
25707
|
};
|
|
26229
25708
|
}
|
|
26230
|
-
}
|
|
26231
|
-
async deleteEmailRecords(data) {
|
|
26232
|
-
const { domain, dkimTokens, mailFromDomain } = data;
|
|
26233
|
-
const errors2 = [];
|
|
26234
|
-
let recordsCreated = 0;
|
|
26235
|
-
try {
|
|
26236
|
-
for (const token of dkimTokens) {
|
|
26237
|
-
const name = `${token}._domainkey.${domain}`;
|
|
26238
|
-
const record = await this.findRecord(name, "CNAME");
|
|
26239
|
-
if (record) {
|
|
26240
|
-
const success = await this.deleteRecord(record.id);
|
|
26241
|
-
if (success) {
|
|
26242
|
-
recordsCreated++;
|
|
26243
|
-
} else {
|
|
26244
|
-
errors2.push(`Failed to delete DKIM record: ${name}`);
|
|
26245
|
-
}
|
|
26246
|
-
}
|
|
26247
|
-
}
|
|
26248
|
-
const dmarcRecord = await this.findRecord(`_dmarc.${domain}`, "TXT");
|
|
26249
|
-
if (dmarcRecord) {
|
|
26250
|
-
const success = await this.deleteRecord(dmarcRecord.id);
|
|
26251
|
-
if (success) {
|
|
26252
|
-
recordsCreated++;
|
|
26253
|
-
} else {
|
|
26254
|
-
errors2.push("Failed to delete DMARC record");
|
|
26255
|
-
}
|
|
26256
|
-
}
|
|
26257
|
-
if (mailFromDomain) {
|
|
26258
|
-
const mxRecord = await this.findRecord(mailFromDomain, "MX");
|
|
26259
|
-
if (mxRecord) {
|
|
26260
|
-
const success = await this.deleteRecord(mxRecord.id);
|
|
26261
|
-
if (success) {
|
|
26262
|
-
recordsCreated++;
|
|
26263
|
-
} else {
|
|
26264
|
-
errors2.push(`Failed to delete MX record for ${mailFromDomain}`);
|
|
26265
|
-
}
|
|
26266
|
-
}
|
|
26267
|
-
const spfRecord = await this.findRecord(mailFromDomain, "TXT");
|
|
26268
|
-
if (spfRecord) {
|
|
26269
|
-
const success = await this.deleteRecord(spfRecord.id);
|
|
26270
|
-
if (success) {
|
|
26271
|
-
recordsCreated++;
|
|
26272
|
-
} else {
|
|
26273
|
-
errors2.push(`Failed to delete SPF record for ${mailFromDomain}`);
|
|
26274
|
-
}
|
|
26275
|
-
}
|
|
26276
|
-
}
|
|
26277
|
-
return {
|
|
26278
|
-
success: errors2.length === 0,
|
|
26279
|
-
recordsCreated,
|
|
26280
|
-
errors: errors2.length > 0 ? errors2 : void 0
|
|
26281
|
-
};
|
|
26282
|
-
} catch (error) {
|
|
26283
|
-
return {
|
|
26284
|
-
success: false,
|
|
26285
|
-
recordsCreated,
|
|
26286
|
-
errors: [
|
|
26287
|
-
...errors2,
|
|
26288
|
-
error instanceof Error ? error.message : "Unknown error"
|
|
26289
|
-
]
|
|
26290
|
-
};
|
|
25709
|
+
}
|
|
25710
|
+
async deleteEmailRecords(data) {
|
|
25711
|
+
const { domain, dkimTokens, mailFromDomain } = data;
|
|
25712
|
+
const errors2 = [];
|
|
25713
|
+
let recordsCreated = 0;
|
|
25714
|
+
try {
|
|
25715
|
+
for (const token of dkimTokens) {
|
|
25716
|
+
const name = `${token}._domainkey.${domain}`;
|
|
25717
|
+
const record = await this.findRecord(name, "CNAME");
|
|
25718
|
+
if (record) {
|
|
25719
|
+
const success = await this.deleteRecord(record.id);
|
|
25720
|
+
if (success) {
|
|
25721
|
+
recordsCreated++;
|
|
25722
|
+
} else {
|
|
25723
|
+
errors2.push(`Failed to delete DKIM record: ${name}`);
|
|
25724
|
+
}
|
|
25725
|
+
}
|
|
25726
|
+
}
|
|
25727
|
+
const dmarcRecord = await this.findRecord(`_dmarc.${domain}`, "TXT");
|
|
25728
|
+
if (dmarcRecord) {
|
|
25729
|
+
const success = await this.deleteRecord(dmarcRecord.id);
|
|
25730
|
+
if (success) {
|
|
25731
|
+
recordsCreated++;
|
|
25732
|
+
} else {
|
|
25733
|
+
errors2.push("Failed to delete DMARC record");
|
|
25734
|
+
}
|
|
25735
|
+
}
|
|
25736
|
+
if (mailFromDomain) {
|
|
25737
|
+
const mxRecord = await this.findRecord(mailFromDomain, "MX");
|
|
25738
|
+
if (mxRecord) {
|
|
25739
|
+
const success = await this.deleteRecord(mxRecord.id);
|
|
25740
|
+
if (success) {
|
|
25741
|
+
recordsCreated++;
|
|
25742
|
+
} else {
|
|
25743
|
+
errors2.push(`Failed to delete MX record for ${mailFromDomain}`);
|
|
25744
|
+
}
|
|
25745
|
+
}
|
|
25746
|
+
const spfRecord = await this.findRecord(mailFromDomain, "TXT");
|
|
25747
|
+
if (spfRecord) {
|
|
25748
|
+
const success = await this.deleteRecord(spfRecord.id);
|
|
25749
|
+
if (success) {
|
|
25750
|
+
recordsCreated++;
|
|
25751
|
+
} else {
|
|
25752
|
+
errors2.push(`Failed to delete SPF record for ${mailFromDomain}`);
|
|
25753
|
+
}
|
|
25754
|
+
}
|
|
25755
|
+
}
|
|
25756
|
+
return {
|
|
25757
|
+
success: errors2.length === 0,
|
|
25758
|
+
recordsCreated,
|
|
25759
|
+
errors: errors2.length > 0 ? errors2 : void 0
|
|
25760
|
+
};
|
|
25761
|
+
} catch (error) {
|
|
25762
|
+
return {
|
|
25763
|
+
success: false,
|
|
25764
|
+
recordsCreated,
|
|
25765
|
+
errors: [
|
|
25766
|
+
...errors2,
|
|
25767
|
+
error instanceof Error ? error.message : "Unknown error"
|
|
25768
|
+
]
|
|
25769
|
+
};
|
|
25770
|
+
}
|
|
25771
|
+
}
|
|
25772
|
+
/**
|
|
25773
|
+
* Get all CAA records for the domain
|
|
25774
|
+
*/
|
|
25775
|
+
async getCAARecords() {
|
|
25776
|
+
const result = await this.request(
|
|
25777
|
+
`/v4/domains/${this.domain}/records`
|
|
25778
|
+
);
|
|
25779
|
+
if (result.error || !result.records) {
|
|
25780
|
+
return [];
|
|
25781
|
+
}
|
|
25782
|
+
return result.records.filter((r6) => r6.type === "CAA").map((r6) => {
|
|
25783
|
+
const match = r6.value.match(/^(\d+)\s+(\w+)\s+"?([^"]+)"?$/);
|
|
25784
|
+
if (match) {
|
|
25785
|
+
return {
|
|
25786
|
+
flags: Number.parseInt(match[1], 10),
|
|
25787
|
+
tag: match[2],
|
|
25788
|
+
value: match[3]
|
|
25789
|
+
};
|
|
25790
|
+
}
|
|
25791
|
+
return null;
|
|
25792
|
+
}).filter(
|
|
25793
|
+
(r6) => r6 !== null
|
|
25794
|
+
);
|
|
25795
|
+
}
|
|
25796
|
+
/**
|
|
25797
|
+
* Check if Amazon is allowed to issue certificates based on CAA records
|
|
25798
|
+
* Returns true if:
|
|
25799
|
+
* - No CAA records exist (any CA can issue)
|
|
25800
|
+
* - CAA records exist and include amazon.com or amazontrust.com
|
|
25801
|
+
*/
|
|
25802
|
+
async isAmazonCAAAllowed() {
|
|
25803
|
+
const caaRecords = await this.getCAARecords();
|
|
25804
|
+
const issueRecords = caaRecords.filter(
|
|
25805
|
+
(r6) => r6.tag === "issue" || r6.tag === "issuewild"
|
|
25806
|
+
);
|
|
25807
|
+
if (issueRecords.length === 0) {
|
|
25808
|
+
return { allowed: true, hasCAA: false, existingCAs: [] };
|
|
25809
|
+
}
|
|
25810
|
+
const existingCAs = issueRecords.map((r6) => r6.value);
|
|
25811
|
+
const amazonAllowed = existingCAs.some(
|
|
25812
|
+
(ca) => ca.includes("amazon.com") || ca.includes("amazontrust.com")
|
|
25813
|
+
);
|
|
25814
|
+
return { allowed: amazonAllowed, hasCAA: true, existingCAs };
|
|
25815
|
+
}
|
|
25816
|
+
/**
|
|
25817
|
+
* Add a CAA record to allow Amazon to issue certificates
|
|
25818
|
+
*/
|
|
25819
|
+
async addAmazonCAARecord() {
|
|
25820
|
+
const body = {
|
|
25821
|
+
name: "@",
|
|
25822
|
+
// Root domain
|
|
25823
|
+
type: "CAA",
|
|
25824
|
+
value: '0 issue "amazon.com"',
|
|
25825
|
+
ttl: 1800
|
|
25826
|
+
};
|
|
25827
|
+
const result = await this.request(
|
|
25828
|
+
`/v2/domains/${this.domain}/records`,
|
|
25829
|
+
"POST",
|
|
25830
|
+
body
|
|
25831
|
+
);
|
|
25832
|
+
return !result.error;
|
|
25833
|
+
}
|
|
25834
|
+
async verifyRecords(data) {
|
|
25835
|
+
const { domain, dkimTokens, mailFromDomain, region } = data;
|
|
25836
|
+
const missing = [];
|
|
25837
|
+
const incorrect = [];
|
|
25838
|
+
for (const token of dkimTokens) {
|
|
25839
|
+
const name = `${token}._domainkey.${domain}`;
|
|
25840
|
+
const expectedValue = `${token}.dkim.amazonses.com`;
|
|
25841
|
+
const record = await this.findRecord(name, "CNAME");
|
|
25842
|
+
if (!record) {
|
|
25843
|
+
missing.push(`DKIM: ${name}`);
|
|
25844
|
+
} else if (record.value !== expectedValue) {
|
|
25845
|
+
incorrect.push(`DKIM: ${name} (expected ${expectedValue})`);
|
|
25846
|
+
}
|
|
25847
|
+
}
|
|
25848
|
+
const spfRecord = await this.findRecord(domain, "TXT");
|
|
25849
|
+
if (!spfRecord) {
|
|
25850
|
+
missing.push(`SPF: ${domain}`);
|
|
25851
|
+
} else if (!spfRecord.value.includes("include:amazonses.com")) {
|
|
25852
|
+
incorrect.push(`SPF: ${domain} (missing amazonses.com include)`);
|
|
25853
|
+
}
|
|
25854
|
+
const dmarcRecord = await this.findRecord(`_dmarc.${domain}`, "TXT");
|
|
25855
|
+
if (!dmarcRecord) {
|
|
25856
|
+
missing.push(`DMARC: _dmarc.${domain}`);
|
|
25857
|
+
}
|
|
25858
|
+
if (mailFromDomain) {
|
|
25859
|
+
const mxRecord = await this.findRecord(mailFromDomain, "MX");
|
|
25860
|
+
if (!mxRecord) {
|
|
25861
|
+
missing.push(`MX: ${mailFromDomain}`);
|
|
25862
|
+
} else if (!mxRecord.value.includes(`feedback-smtp.${region}.amazonses.com`)) {
|
|
25863
|
+
incorrect.push(`MX: ${mailFromDomain}`);
|
|
25864
|
+
}
|
|
25865
|
+
const mailFromSpf = await this.findRecord(mailFromDomain, "TXT");
|
|
25866
|
+
if (!mailFromSpf) {
|
|
25867
|
+
missing.push(`SPF: ${mailFromDomain}`);
|
|
25868
|
+
}
|
|
25869
|
+
}
|
|
25870
|
+
return {
|
|
25871
|
+
verified: missing.length === 0 && incorrect.length === 0,
|
|
25872
|
+
missing,
|
|
25873
|
+
incorrect
|
|
25874
|
+
};
|
|
25875
|
+
}
|
|
25876
|
+
};
|
|
25877
|
+
}
|
|
25878
|
+
});
|
|
25879
|
+
|
|
25880
|
+
// src/utils/dns/email-dns-cleanup.ts
|
|
25881
|
+
var email_dns_cleanup_exports = {};
|
|
25882
|
+
__export(email_dns_cleanup_exports, {
|
|
25883
|
+
deleteEmailDNSRecordsForProvider: () => deleteEmailDNSRecordsForProvider
|
|
25884
|
+
});
|
|
25885
|
+
import {
|
|
25886
|
+
ChangeResourceRecordSetsCommand,
|
|
25887
|
+
ListResourceRecordSetsCommand,
|
|
25888
|
+
Route53Client
|
|
25889
|
+
} from "@aws-sdk/client-route-53";
|
|
25890
|
+
async function deleteViaValueMatchedClient(client2, records) {
|
|
25891
|
+
const deleted = [];
|
|
25892
|
+
const skipped = [];
|
|
25893
|
+
const errors2 = [];
|
|
25894
|
+
for (const record of records) {
|
|
25895
|
+
const label = `${record.type} ${record.name}`;
|
|
25896
|
+
const result = await client2.deleteRecordsByValue(
|
|
25897
|
+
record.name,
|
|
25898
|
+
record.type,
|
|
25899
|
+
record.value
|
|
25900
|
+
);
|
|
25901
|
+
if (result.error) {
|
|
25902
|
+
errors2.push(`${label}: ${result.error}`);
|
|
25903
|
+
} else if (result.deleted > 0) {
|
|
25904
|
+
for (let i6 = 0; i6 < result.deleted; i6++) {
|
|
25905
|
+
deleted.push(label);
|
|
25906
|
+
}
|
|
25907
|
+
} else {
|
|
25908
|
+
skipped.push({
|
|
25909
|
+
record: label,
|
|
25910
|
+
reason: "no record with the value Wraps created"
|
|
25911
|
+
});
|
|
25912
|
+
}
|
|
25913
|
+
}
|
|
25914
|
+
return { deleted, skipped, supported: true, errors: errors2 };
|
|
25915
|
+
}
|
|
25916
|
+
async function listRoute53RecordSet(client2, hostedZoneId, name, type) {
|
|
25917
|
+
const targetName = normalizeRoute53Name(name);
|
|
25918
|
+
let startRecordName = name;
|
|
25919
|
+
let startRecordType;
|
|
25920
|
+
const MAX_PAGES = 50;
|
|
25921
|
+
for (let page = 0; page < MAX_PAGES; page++) {
|
|
25922
|
+
const response = await client2.send(
|
|
25923
|
+
new ListResourceRecordSetsCommand({
|
|
25924
|
+
HostedZoneId: hostedZoneId,
|
|
25925
|
+
StartRecordName: startRecordName,
|
|
25926
|
+
StartRecordType: startRecordType
|
|
25927
|
+
})
|
|
25928
|
+
);
|
|
25929
|
+
const rrsets = response.ResourceRecordSets ?? [];
|
|
25930
|
+
let movedPastTarget = false;
|
|
25931
|
+
for (const rrset of rrsets) {
|
|
25932
|
+
if (!(rrset.Name && rrset.Type)) {
|
|
25933
|
+
continue;
|
|
25934
|
+
}
|
|
25935
|
+
if (normalizeRoute53Name(rrset.Name) !== targetName) {
|
|
25936
|
+
movedPastTarget = true;
|
|
25937
|
+
break;
|
|
25938
|
+
}
|
|
25939
|
+
if (rrset.Type === type) {
|
|
25940
|
+
return {
|
|
25941
|
+
ttl: rrset.TTL,
|
|
25942
|
+
values: (rrset.ResourceRecords ?? []).map((r6) => r6.Value).filter((v) => Boolean(v))
|
|
25943
|
+
};
|
|
25944
|
+
}
|
|
25945
|
+
}
|
|
25946
|
+
if (movedPastTarget || !response.IsTruncated) {
|
|
25947
|
+
break;
|
|
25948
|
+
}
|
|
25949
|
+
startRecordName = response.NextRecordName;
|
|
25950
|
+
startRecordType = response.NextRecordType;
|
|
25951
|
+
if (!startRecordName) {
|
|
25952
|
+
break;
|
|
25953
|
+
}
|
|
25954
|
+
}
|
|
25955
|
+
}
|
|
25956
|
+
function buildRoute53RemovalChange(params) {
|
|
25957
|
+
const { name, type, sesValue, existing } = params;
|
|
25958
|
+
const values = existing?.values ?? [];
|
|
25959
|
+
if (!values.includes(sesValue)) {
|
|
25960
|
+
return {
|
|
25961
|
+
change: null,
|
|
25962
|
+
skippedReason: `The ${type} record set at ${name} does not contain the value Wraps created \u2014 leaving it in place.`
|
|
25963
|
+
};
|
|
25964
|
+
}
|
|
25965
|
+
const remaining = values.filter((value) => value !== sesValue);
|
|
25966
|
+
if (remaining.length === 0) {
|
|
25967
|
+
return {
|
|
25968
|
+
change: {
|
|
25969
|
+
Action: "DELETE",
|
|
25970
|
+
ResourceRecordSet: {
|
|
25971
|
+
Name: name,
|
|
25972
|
+
Type: type,
|
|
25973
|
+
TTL: existing?.ttl ?? 1800,
|
|
25974
|
+
ResourceRecords: values.map((value) => ({ Value: value }))
|
|
25975
|
+
}
|
|
25976
|
+
}
|
|
25977
|
+
};
|
|
25978
|
+
}
|
|
25979
|
+
return {
|
|
25980
|
+
change: {
|
|
25981
|
+
Action: "UPSERT",
|
|
25982
|
+
ResourceRecordSet: {
|
|
25983
|
+
Name: name,
|
|
25984
|
+
Type: type,
|
|
25985
|
+
TTL: existing?.ttl ?? 1800,
|
|
25986
|
+
ResourceRecords: remaining.map((value) => ({ Value: value }))
|
|
25987
|
+
}
|
|
25988
|
+
}
|
|
25989
|
+
};
|
|
25990
|
+
}
|
|
25991
|
+
function route53Value(record) {
|
|
25992
|
+
if (record.type === "MX") {
|
|
25993
|
+
return `${record.priority} ${record.value}`;
|
|
25994
|
+
}
|
|
25995
|
+
if (record.type === "TXT") {
|
|
25996
|
+
return `"${record.value}"`;
|
|
25997
|
+
}
|
|
25998
|
+
return record.value;
|
|
25999
|
+
}
|
|
26000
|
+
async function deleteRoute53EmailRecords(hostedZoneId, region, records) {
|
|
26001
|
+
const client2 = new Route53Client({ region });
|
|
26002
|
+
const mxTxtNames = /* @__PURE__ */ new Set();
|
|
26003
|
+
const cnameNames = /* @__PURE__ */ new Set();
|
|
26004
|
+
for (const record of records) {
|
|
26005
|
+
if (record.type === "CNAME") {
|
|
26006
|
+
cnameNames.add(record.name);
|
|
26007
|
+
} else {
|
|
26008
|
+
mxTxtNames.add(record.name);
|
|
26009
|
+
}
|
|
26010
|
+
}
|
|
26011
|
+
const mxTxtByName = /* @__PURE__ */ new Map();
|
|
26012
|
+
const cnameByName = /* @__PURE__ */ new Map();
|
|
26013
|
+
try {
|
|
26014
|
+
for (const name of mxTxtNames) {
|
|
26015
|
+
mxTxtByName.set(
|
|
26016
|
+
name,
|
|
26017
|
+
await listExistingRoute53InboundRecords(client2, hostedZoneId, name)
|
|
26018
|
+
);
|
|
26019
|
+
}
|
|
26020
|
+
for (const name of cnameNames) {
|
|
26021
|
+
cnameByName.set(
|
|
26022
|
+
name,
|
|
26023
|
+
await listRoute53RecordSet(client2, hostedZoneId, name, "CNAME")
|
|
26024
|
+
);
|
|
26025
|
+
}
|
|
26026
|
+
} catch (error) {
|
|
26027
|
+
return {
|
|
26028
|
+
deleted: [],
|
|
26029
|
+
skipped: [],
|
|
26030
|
+
supported: true,
|
|
26031
|
+
errors: [error instanceof Error ? error.message : "Unknown error"]
|
|
26032
|
+
};
|
|
26033
|
+
}
|
|
26034
|
+
const changes = [];
|
|
26035
|
+
const deleted = [];
|
|
26036
|
+
const skipped = [];
|
|
26037
|
+
for (const record of records) {
|
|
26038
|
+
const label = `${record.type} ${record.name}`;
|
|
26039
|
+
const existing = record.type === "CNAME" ? cnameByName.get(record.name) : mxTxtByName.get(record.name)?.get(record.type);
|
|
26040
|
+
const result = buildRoute53RemovalChange({
|
|
26041
|
+
name: record.name,
|
|
26042
|
+
type: record.type,
|
|
26043
|
+
sesValue: route53Value(record),
|
|
26044
|
+
existing
|
|
26045
|
+
});
|
|
26046
|
+
if (result.change) {
|
|
26047
|
+
changes.push(result.change);
|
|
26048
|
+
deleted.push(label);
|
|
26049
|
+
} else if (result.skippedReason) {
|
|
26050
|
+
skipped.push({ record: label, reason: result.skippedReason });
|
|
26051
|
+
}
|
|
26052
|
+
}
|
|
26053
|
+
if (changes.length === 0) {
|
|
26054
|
+
return { deleted, skipped, supported: true, errors: [] };
|
|
26055
|
+
}
|
|
26056
|
+
try {
|
|
26057
|
+
await client2.send(
|
|
26058
|
+
new ChangeResourceRecordSetsCommand({
|
|
26059
|
+
HostedZoneId: hostedZoneId,
|
|
26060
|
+
ChangeBatch: { Changes: changes }
|
|
26061
|
+
})
|
|
26062
|
+
);
|
|
26063
|
+
return { deleted, skipped, supported: true, errors: [] };
|
|
26064
|
+
} catch (error) {
|
|
26065
|
+
return {
|
|
26066
|
+
deleted: [],
|
|
26067
|
+
skipped,
|
|
26068
|
+
supported: true,
|
|
26069
|
+
errors: [error instanceof Error ? error.message : "Unknown error"]
|
|
26070
|
+
};
|
|
26071
|
+
}
|
|
26072
|
+
}
|
|
26073
|
+
async function deleteEmailDNSRecordsForProvider(credentials, data) {
|
|
26074
|
+
const records = buildEmailDNSRecords(data).filter(
|
|
26075
|
+
(record) => record.category !== "spf"
|
|
26076
|
+
);
|
|
26077
|
+
switch (credentials.provider) {
|
|
26078
|
+
case "cloudflare": {
|
|
26079
|
+
const client2 = new CloudflareDNSClient(
|
|
26080
|
+
credentials.zoneId,
|
|
26081
|
+
credentials.token
|
|
26082
|
+
);
|
|
26083
|
+
return deleteViaValueMatchedClient(client2, records);
|
|
26084
|
+
}
|
|
26085
|
+
case "vercel": {
|
|
26086
|
+
const client2 = new VercelDNSClient(
|
|
26087
|
+
data.domain,
|
|
26088
|
+
credentials.token,
|
|
26089
|
+
credentials.teamId
|
|
26090
|
+
);
|
|
26091
|
+
return deleteViaValueMatchedClient(client2, records);
|
|
26092
|
+
}
|
|
26093
|
+
case "route53":
|
|
26094
|
+
return deleteRoute53EmailRecords(
|
|
26095
|
+
credentials.hostedZoneId,
|
|
26096
|
+
data.region,
|
|
26097
|
+
records
|
|
26098
|
+
);
|
|
26099
|
+
case "manual":
|
|
26100
|
+
return { deleted: [], skipped: [], supported: false, errors: [] };
|
|
26101
|
+
}
|
|
26102
|
+
}
|
|
26103
|
+
var init_email_dns_cleanup = __esm({
|
|
26104
|
+
"src/utils/dns/email-dns-cleanup.ts"() {
|
|
26105
|
+
"use strict";
|
|
26106
|
+
init_esm_shims();
|
|
26107
|
+
init_cloudflare();
|
|
26108
|
+
init_create_records();
|
|
26109
|
+
init_vercel();
|
|
26110
|
+
}
|
|
26111
|
+
});
|
|
26112
|
+
|
|
26113
|
+
// src/utils/route53.ts
|
|
26114
|
+
var route53_exports = {};
|
|
26115
|
+
__export(route53_exports, {
|
|
26116
|
+
createDNSRecords: () => createDNSRecords,
|
|
26117
|
+
createSelectedDNSRecords: () => createSelectedDNSRecords,
|
|
26118
|
+
deleteDNSRecords: () => deleteDNSRecords,
|
|
26119
|
+
findHostedZone: () => findHostedZone,
|
|
26120
|
+
mergeSPFRecord: () => mergeSPFRecord,
|
|
26121
|
+
previewDNSChanges: () => previewDNSChanges
|
|
26122
|
+
});
|
|
26123
|
+
import {
|
|
26124
|
+
ChangeResourceRecordSetsCommand as ChangeResourceRecordSetsCommand2,
|
|
26125
|
+
ListHostedZonesByNameCommand,
|
|
26126
|
+
ListResourceRecordSetsCommand as ListResourceRecordSetsCommand2,
|
|
26127
|
+
Route53Client as Route53Client2
|
|
26128
|
+
} from "@aws-sdk/client-route-53";
|
|
26129
|
+
async function getExistingTXTRecords(client2, hostedZoneId, domain) {
|
|
26130
|
+
try {
|
|
26131
|
+
const response = await client2.send(
|
|
26132
|
+
new ListResourceRecordSetsCommand2({
|
|
26133
|
+
HostedZoneId: hostedZoneId,
|
|
26134
|
+
StartRecordName: domain,
|
|
26135
|
+
StartRecordType: "TXT",
|
|
26136
|
+
MaxItems: 100
|
|
26137
|
+
})
|
|
26138
|
+
);
|
|
26139
|
+
const txtRecordSet = response.ResourceRecordSets?.find(
|
|
26140
|
+
(rs) => rs.Type === "TXT" && (rs.Name === domain || rs.Name === `${domain}.`)
|
|
26141
|
+
);
|
|
26142
|
+
if (!txtRecordSet?.ResourceRecords) {
|
|
26143
|
+
return { allValues: [], spfValue: null, ttl: 1800 };
|
|
26144
|
+
}
|
|
26145
|
+
const allValues = [];
|
|
26146
|
+
let spfValue = null;
|
|
26147
|
+
for (const record of txtRecordSet.ResourceRecords) {
|
|
26148
|
+
const value = record.Value || "";
|
|
26149
|
+
allValues.push(value);
|
|
26150
|
+
const unquoted = value.replace(/^"|"$/g, "");
|
|
26151
|
+
if (unquoted.startsWith("v=spf1")) {
|
|
26152
|
+
spfValue = unquoted;
|
|
26153
|
+
}
|
|
26154
|
+
}
|
|
26155
|
+
return {
|
|
26156
|
+
allValues,
|
|
26157
|
+
spfValue,
|
|
26158
|
+
ttl: txtRecordSet.TTL || 1800
|
|
26159
|
+
};
|
|
26160
|
+
} catch (_error) {
|
|
26161
|
+
return { allValues: [], spfValue: null, ttl: 1800 };
|
|
26162
|
+
}
|
|
26163
|
+
}
|
|
26164
|
+
function mergeSPFRecord(existingSPF) {
|
|
26165
|
+
const sesInclude = "include:amazonses.com";
|
|
26166
|
+
if (existingSPF.includes(sesInclude)) {
|
|
26167
|
+
return existingSPF;
|
|
26168
|
+
}
|
|
26169
|
+
const allMatch = existingSPF.match(/\s([~+?-]?all)$/);
|
|
26170
|
+
if (allMatch) {
|
|
26171
|
+
const beforeAll = existingSPF.slice(0, allMatch.index);
|
|
26172
|
+
const allPart = allMatch[1];
|
|
26173
|
+
return `${beforeAll} ${sesInclude} ${allPart}`;
|
|
26174
|
+
}
|
|
26175
|
+
return `${existingSPF} ${sesInclude}`;
|
|
26176
|
+
}
|
|
26177
|
+
async function getExistingRecords(client2, hostedZoneId) {
|
|
26178
|
+
try {
|
|
26179
|
+
const response = await client2.send(
|
|
26180
|
+
new ListResourceRecordSetsCommand2({
|
|
26181
|
+
HostedZoneId: hostedZoneId,
|
|
26182
|
+
MaxItems: 500
|
|
26183
|
+
})
|
|
26184
|
+
);
|
|
26185
|
+
return response.ResourceRecordSets || [];
|
|
26186
|
+
} catch (_error) {
|
|
26187
|
+
return [];
|
|
26188
|
+
}
|
|
26189
|
+
}
|
|
26190
|
+
function findExistingRecord(records, name, type) {
|
|
26191
|
+
const normalizedName = name.endsWith(".") ? name : `${name}.`;
|
|
26192
|
+
return records.find((r6) => r6.Name === normalizedName && r6.Type === type) || null;
|
|
26193
|
+
}
|
|
26194
|
+
function getRecordValue(record) {
|
|
26195
|
+
if (!record?.ResourceRecords?.length) {
|
|
26196
|
+
return null;
|
|
26197
|
+
}
|
|
26198
|
+
return record.ResourceRecords.map((r6) => r6.Value || "").join(", ");
|
|
26199
|
+
}
|
|
26200
|
+
async function previewDNSChanges(hostedZoneId, domain, dkimTokens, region, customTrackingDomain, mailFromDomain) {
|
|
26201
|
+
const client2 = new Route53Client2({ region });
|
|
26202
|
+
const existingRecords = await getExistingRecords(client2, hostedZoneId);
|
|
26203
|
+
const existingTXT = await getExistingTXTRecords(client2, hostedZoneId, domain);
|
|
26204
|
+
const records = [];
|
|
26205
|
+
for (const token of dkimTokens) {
|
|
26206
|
+
const name = `${token}._domainkey.${domain}`;
|
|
26207
|
+
const proposedValue = `${token}.dkim.amazonses.com`;
|
|
26208
|
+
const existing = findExistingRecord(existingRecords, name, "CNAME");
|
|
26209
|
+
const existingValue = getRecordValue(existing);
|
|
26210
|
+
let status2 = "new";
|
|
26211
|
+
if (existingValue === proposedValue) {
|
|
26212
|
+
status2 = "no_change";
|
|
26213
|
+
} else if (existingValue) {
|
|
26214
|
+
status2 = "update";
|
|
26215
|
+
}
|
|
26216
|
+
records.push({
|
|
26217
|
+
name,
|
|
26218
|
+
type: "CNAME",
|
|
26219
|
+
proposedValue,
|
|
26220
|
+
existingValue,
|
|
26221
|
+
status: status2,
|
|
26222
|
+
category: "dkim"
|
|
26223
|
+
});
|
|
26224
|
+
}
|
|
26225
|
+
const proposedSPF = existingTXT.spfValue ? mergeSPFRecord(existingTXT.spfValue) : "v=spf1 include:amazonses.com ~all";
|
|
26226
|
+
let spfStatus = "new";
|
|
26227
|
+
if (existingTXT.spfValue) {
|
|
26228
|
+
if (existingTXT.spfValue === proposedSPF) {
|
|
26229
|
+
spfStatus = "no_change";
|
|
26230
|
+
} else if (existingTXT.spfValue.includes("include:amazonses.com")) {
|
|
26231
|
+
spfStatus = "no_change";
|
|
26232
|
+
} else {
|
|
26233
|
+
spfStatus = "update";
|
|
26234
|
+
}
|
|
26235
|
+
}
|
|
26236
|
+
records.push({
|
|
26237
|
+
name: domain,
|
|
26238
|
+
type: "TXT",
|
|
26239
|
+
proposedValue: `"${proposedSPF}"`,
|
|
26240
|
+
existingValue: existingTXT.spfValue ? `"${existingTXT.spfValue}"` : null,
|
|
26241
|
+
status: spfStatus,
|
|
26242
|
+
category: "spf",
|
|
26243
|
+
conflictReason: void 0
|
|
26244
|
+
});
|
|
26245
|
+
const dmarcName = `_dmarc.${domain}`;
|
|
26246
|
+
const dmarcRuaDomain = mailFromDomain || domain;
|
|
26247
|
+
const proposedDMARC = `"v=DMARC1; p=quarantine; sp=quarantine; np=reject; rua=mailto:postmaster@${dmarcRuaDomain}"`;
|
|
26248
|
+
const existingDMARC = findExistingRecord(existingRecords, dmarcName, "TXT");
|
|
26249
|
+
const existingDMARCValue = getRecordValue(existingDMARC);
|
|
26250
|
+
let dmarcStatus = "new";
|
|
26251
|
+
let dmarcConflictReason;
|
|
26252
|
+
if (existingDMARCValue) {
|
|
26253
|
+
if (existingDMARCValue === proposedDMARC) {
|
|
26254
|
+
dmarcStatus = "no_change";
|
|
26255
|
+
} else {
|
|
26256
|
+
dmarcStatus = "conflict";
|
|
26257
|
+
dmarcConflictReason = "Existing DMARC policy will be replaced";
|
|
26258
|
+
}
|
|
26259
|
+
}
|
|
26260
|
+
records.push({
|
|
26261
|
+
name: dmarcName,
|
|
26262
|
+
type: "TXT",
|
|
26263
|
+
proposedValue: proposedDMARC,
|
|
26264
|
+
existingValue: existingDMARCValue,
|
|
26265
|
+
status: dmarcStatus,
|
|
26266
|
+
category: "dmarc",
|
|
26267
|
+
conflictReason: dmarcConflictReason
|
|
26268
|
+
});
|
|
26269
|
+
if (customTrackingDomain) {
|
|
26270
|
+
const proposedTracking = `r.${region}.awstrack.me`;
|
|
26271
|
+
const existingTracking = findExistingRecord(
|
|
26272
|
+
existingRecords,
|
|
26273
|
+
customTrackingDomain,
|
|
26274
|
+
"CNAME"
|
|
26275
|
+
);
|
|
26276
|
+
const existingTrackingValue = getRecordValue(existingTracking);
|
|
26277
|
+
let trackingStatus = "new";
|
|
26278
|
+
let trackingConflictReason;
|
|
26279
|
+
if (existingTrackingValue === proposedTracking) {
|
|
26280
|
+
trackingStatus = "no_change";
|
|
26281
|
+
} else if (existingTrackingValue) {
|
|
26282
|
+
trackingStatus = "conflict";
|
|
26283
|
+
trackingConflictReason = `Points to ${existingTrackingValue}, will be replaced`;
|
|
26284
|
+
}
|
|
26285
|
+
records.push({
|
|
26286
|
+
name: customTrackingDomain,
|
|
26287
|
+
type: "CNAME",
|
|
26288
|
+
proposedValue: proposedTracking,
|
|
26289
|
+
existingValue: existingTrackingValue,
|
|
26290
|
+
status: trackingStatus,
|
|
26291
|
+
category: "tracking",
|
|
26292
|
+
conflictReason: trackingConflictReason
|
|
26293
|
+
});
|
|
26294
|
+
}
|
|
26295
|
+
if (mailFromDomain) {
|
|
26296
|
+
const proposedMX = `10 feedback-smtp.${region}.amazonses.com`;
|
|
26297
|
+
const existingMX = findExistingRecord(
|
|
26298
|
+
existingRecords,
|
|
26299
|
+
mailFromDomain,
|
|
26300
|
+
"MX"
|
|
26301
|
+
);
|
|
26302
|
+
const existingMXValue = getRecordValue(existingMX);
|
|
26303
|
+
let mxStatus = "new";
|
|
26304
|
+
if (existingMXValue === proposedMX) {
|
|
26305
|
+
mxStatus = "no_change";
|
|
26306
|
+
} else if (existingMXValue) {
|
|
26307
|
+
mxStatus = "update";
|
|
26308
|
+
}
|
|
26309
|
+
records.push({
|
|
26310
|
+
name: mailFromDomain,
|
|
26311
|
+
type: "MX",
|
|
26312
|
+
proposedValue: proposedMX,
|
|
26313
|
+
existingValue: existingMXValue,
|
|
26314
|
+
status: mxStatus,
|
|
26315
|
+
category: "mailfrom_mx"
|
|
26316
|
+
});
|
|
26317
|
+
const mailFromSPF = '"v=spf1 include:amazonses.com ~all"';
|
|
26318
|
+
const existingMailFromTXT = await getExistingTXTRecords(
|
|
26319
|
+
client2,
|
|
26320
|
+
hostedZoneId,
|
|
26321
|
+
mailFromDomain
|
|
26322
|
+
);
|
|
26323
|
+
let mailFromSpfStatus = "new";
|
|
26324
|
+
if (existingMailFromTXT.spfValue?.includes("include:amazonses.com")) {
|
|
26325
|
+
mailFromSpfStatus = "no_change";
|
|
26326
|
+
} else if (existingMailFromTXT.spfValue) {
|
|
26327
|
+
mailFromSpfStatus = "update";
|
|
26328
|
+
}
|
|
26329
|
+
records.push({
|
|
26330
|
+
name: mailFromDomain,
|
|
26331
|
+
type: "TXT",
|
|
26332
|
+
proposedValue: mailFromSPF,
|
|
26333
|
+
existingValue: existingMailFromTXT.spfValue ? `"${existingMailFromTXT.spfValue}"` : null,
|
|
26334
|
+
status: mailFromSpfStatus,
|
|
26335
|
+
category: "mailfrom_spf"
|
|
26336
|
+
});
|
|
26337
|
+
}
|
|
26338
|
+
const conflictCount = records.filter((r6) => r6.status === "conflict").length;
|
|
26339
|
+
const newCount = records.filter((r6) => r6.status === "new").length;
|
|
26340
|
+
const updateCount = records.filter((r6) => r6.status === "update").length;
|
|
26341
|
+
const noChangeCount = records.filter((r6) => r6.status === "no_change").length;
|
|
26342
|
+
return {
|
|
26343
|
+
records,
|
|
26344
|
+
hasConflicts: conflictCount > 0,
|
|
26345
|
+
conflictCount,
|
|
26346
|
+
newCount,
|
|
26347
|
+
updateCount,
|
|
26348
|
+
noChangeCount
|
|
26349
|
+
};
|
|
26350
|
+
}
|
|
26351
|
+
async function createSelectedDNSRecords(hostedZoneId, domain, dkimTokens, region, selectedCategories, customTrackingDomain, mailFromDomain, trackingCnameTarget) {
|
|
26352
|
+
const client2 = new Route53Client2({ region });
|
|
26353
|
+
const changes = [];
|
|
26354
|
+
if (selectedCategories.has("dkim")) {
|
|
26355
|
+
for (const token of dkimTokens) {
|
|
26356
|
+
changes.push({
|
|
26357
|
+
Action: "UPSERT",
|
|
26358
|
+
ResourceRecordSet: {
|
|
26359
|
+
Name: `${token}._domainkey.${domain}`,
|
|
26360
|
+
Type: "CNAME",
|
|
26361
|
+
TTL: 1800,
|
|
26362
|
+
ResourceRecords: [{ Value: `${token}.dkim.amazonses.com` }]
|
|
26363
|
+
}
|
|
26364
|
+
});
|
|
26365
|
+
}
|
|
26366
|
+
}
|
|
26367
|
+
if (selectedCategories.has("spf")) {
|
|
26368
|
+
const existingTXT = await getExistingTXTRecords(
|
|
26369
|
+
client2,
|
|
26370
|
+
hostedZoneId,
|
|
26371
|
+
domain
|
|
26372
|
+
);
|
|
26373
|
+
const newTXTValues = [];
|
|
26374
|
+
if (existingTXT.spfValue) {
|
|
26375
|
+
const mergedSPF = mergeSPFRecord(existingTXT.spfValue);
|
|
26376
|
+
newTXTValues.push(`"${mergedSPF}"`);
|
|
26377
|
+
for (const value of existingTXT.allValues) {
|
|
26378
|
+
const unquoted = value.replace(/^"|"$/g, "");
|
|
26379
|
+
if (!unquoted.startsWith("v=spf1")) {
|
|
26380
|
+
newTXTValues.push(value);
|
|
26291
26381
|
}
|
|
26292
26382
|
}
|
|
26293
|
-
|
|
26294
|
-
|
|
26295
|
-
|
|
26296
|
-
|
|
26297
|
-
|
|
26298
|
-
|
|
26299
|
-
|
|
26300
|
-
|
|
26301
|
-
|
|
26302
|
-
|
|
26303
|
-
|
|
26304
|
-
|
|
26305
|
-
|
|
26306
|
-
|
|
26307
|
-
|
|
26308
|
-
|
|
26309
|
-
|
|
26310
|
-
|
|
26383
|
+
} else if (existingTXT.allValues.length > 0) {
|
|
26384
|
+
newTXTValues.push('"v=spf1 include:amazonses.com ~all"');
|
|
26385
|
+
newTXTValues.push(...existingTXT.allValues);
|
|
26386
|
+
} else {
|
|
26387
|
+
newTXTValues.push('"v=spf1 include:amazonses.com ~all"');
|
|
26388
|
+
}
|
|
26389
|
+
changes.push({
|
|
26390
|
+
Action: "UPSERT",
|
|
26391
|
+
ResourceRecordSet: {
|
|
26392
|
+
Name: domain,
|
|
26393
|
+
Type: "TXT",
|
|
26394
|
+
TTL: existingTXT.ttl,
|
|
26395
|
+
ResourceRecords: newTXTValues.map((v) => ({ Value: v }))
|
|
26396
|
+
}
|
|
26397
|
+
});
|
|
26398
|
+
}
|
|
26399
|
+
if (selectedCategories.has("dmarc")) {
|
|
26400
|
+
const dmarcRuaDomain = mailFromDomain || domain;
|
|
26401
|
+
changes.push({
|
|
26402
|
+
Action: "UPSERT",
|
|
26403
|
+
ResourceRecordSet: {
|
|
26404
|
+
Name: `_dmarc.${domain}`,
|
|
26405
|
+
Type: "TXT",
|
|
26406
|
+
TTL: 1800,
|
|
26407
|
+
ResourceRecords: [
|
|
26408
|
+
{
|
|
26409
|
+
Value: `"v=DMARC1; p=quarantine; sp=quarantine; np=reject; rua=mailto:postmaster@${dmarcRuaDomain}"`
|
|
26311
26410
|
}
|
|
26312
|
-
|
|
26313
|
-
|
|
26314
|
-
|
|
26315
|
-
|
|
26411
|
+
]
|
|
26412
|
+
}
|
|
26413
|
+
});
|
|
26414
|
+
}
|
|
26415
|
+
if (selectedCategories.has("tracking") && customTrackingDomain) {
|
|
26416
|
+
changes.push({
|
|
26417
|
+
Action: "UPSERT",
|
|
26418
|
+
ResourceRecordSet: {
|
|
26419
|
+
Name: customTrackingDomain,
|
|
26420
|
+
Type: "CNAME",
|
|
26421
|
+
TTL: 1800,
|
|
26422
|
+
ResourceRecords: [
|
|
26423
|
+
{ Value: trackingCnameTarget ?? `r.${region}.awstrack.me` }
|
|
26424
|
+
]
|
|
26425
|
+
}
|
|
26426
|
+
});
|
|
26427
|
+
}
|
|
26428
|
+
if (selectedCategories.has("mailfrom_mx") && mailFromDomain) {
|
|
26429
|
+
changes.push({
|
|
26430
|
+
Action: "UPSERT",
|
|
26431
|
+
ResourceRecordSet: {
|
|
26432
|
+
Name: mailFromDomain,
|
|
26433
|
+
Type: "MX",
|
|
26434
|
+
TTL: 1800,
|
|
26435
|
+
ResourceRecords: [
|
|
26436
|
+
{ Value: `10 feedback-smtp.${region}.amazonses.com` }
|
|
26437
|
+
]
|
|
26438
|
+
}
|
|
26439
|
+
});
|
|
26440
|
+
}
|
|
26441
|
+
if (selectedCategories.has("mailfrom_spf") && mailFromDomain) {
|
|
26442
|
+
changes.push({
|
|
26443
|
+
Action: "UPSERT",
|
|
26444
|
+
ResourceRecordSet: {
|
|
26445
|
+
Name: mailFromDomain,
|
|
26446
|
+
Type: "TXT",
|
|
26447
|
+
TTL: 1800,
|
|
26448
|
+
ResourceRecords: [{ Value: '"v=spf1 include:amazonses.com ~all"' }]
|
|
26449
|
+
}
|
|
26450
|
+
});
|
|
26451
|
+
}
|
|
26452
|
+
if (changes.length === 0) {
|
|
26453
|
+
return;
|
|
26454
|
+
}
|
|
26455
|
+
await client2.send(
|
|
26456
|
+
new ChangeResourceRecordSetsCommand2({
|
|
26457
|
+
HostedZoneId: hostedZoneId,
|
|
26458
|
+
ChangeBatch: {
|
|
26459
|
+
Changes: changes
|
|
26460
|
+
}
|
|
26461
|
+
})
|
|
26462
|
+
);
|
|
26463
|
+
}
|
|
26464
|
+
async function findHostedZone(domain, region) {
|
|
26465
|
+
const client2 = new Route53Client2({ region });
|
|
26466
|
+
try {
|
|
26467
|
+
const response = await client2.send(
|
|
26468
|
+
new ListHostedZonesByNameCommand({
|
|
26469
|
+
DNSName: domain,
|
|
26470
|
+
MaxItems: 1
|
|
26471
|
+
})
|
|
26472
|
+
);
|
|
26473
|
+
const zone = response.HostedZones?.[0];
|
|
26474
|
+
if (zone && zone.Name === `${domain}.` && zone.Id) {
|
|
26475
|
+
return {
|
|
26476
|
+
id: zone.Id.replace("/hostedzone/", ""),
|
|
26477
|
+
name: zone.Name
|
|
26478
|
+
};
|
|
26479
|
+
}
|
|
26480
|
+
return null;
|
|
26481
|
+
} catch (_error) {
|
|
26482
|
+
return null;
|
|
26483
|
+
}
|
|
26484
|
+
}
|
|
26485
|
+
async function createDNSRecords(hostedZoneId, domain, dkimTokens, region, customTrackingDomain, mailFromDomain, cloudFrontDomain) {
|
|
26486
|
+
const client2 = new Route53Client2({ region });
|
|
26487
|
+
const changes = [];
|
|
26488
|
+
for (const token of dkimTokens) {
|
|
26489
|
+
changes.push({
|
|
26490
|
+
Action: "UPSERT",
|
|
26491
|
+
ResourceRecordSet: {
|
|
26492
|
+
Name: `${token}._domainkey.${domain}`,
|
|
26493
|
+
Type: "CNAME",
|
|
26494
|
+
TTL: 1800,
|
|
26495
|
+
ResourceRecords: [{ Value: `${token}.dkim.amazonses.com` }]
|
|
26316
26496
|
}
|
|
26317
|
-
|
|
26318
|
-
|
|
26319
|
-
|
|
26320
|
-
|
|
26321
|
-
|
|
26322
|
-
|
|
26323
|
-
|
|
26324
|
-
|
|
26325
|
-
|
|
26326
|
-
|
|
26327
|
-
);
|
|
26328
|
-
|
|
26329
|
-
|
|
26497
|
+
});
|
|
26498
|
+
}
|
|
26499
|
+
const existingTXT = await getExistingTXTRecords(client2, hostedZoneId, domain);
|
|
26500
|
+
const newTXTValues = [];
|
|
26501
|
+
if (existingTXT.spfValue) {
|
|
26502
|
+
const mergedSPF = mergeSPFRecord(existingTXT.spfValue);
|
|
26503
|
+
newTXTValues.push(`"${mergedSPF}"`);
|
|
26504
|
+
for (const value of existingTXT.allValues) {
|
|
26505
|
+
const unquoted = value.replace(/^"|"$/g, "");
|
|
26506
|
+
if (!unquoted.startsWith("v=spf1")) {
|
|
26507
|
+
newTXTValues.push(value);
|
|
26508
|
+
}
|
|
26509
|
+
}
|
|
26510
|
+
} else if (existingTXT.allValues.length > 0) {
|
|
26511
|
+
newTXTValues.push('"v=spf1 include:amazonses.com ~all"');
|
|
26512
|
+
newTXTValues.push(...existingTXT.allValues);
|
|
26513
|
+
} else {
|
|
26514
|
+
newTXTValues.push('"v=spf1 include:amazonses.com ~all"');
|
|
26515
|
+
}
|
|
26516
|
+
changes.push({
|
|
26517
|
+
Action: "UPSERT",
|
|
26518
|
+
ResourceRecordSet: {
|
|
26519
|
+
Name: domain,
|
|
26520
|
+
Type: "TXT",
|
|
26521
|
+
TTL: existingTXT.ttl,
|
|
26522
|
+
ResourceRecords: newTXTValues.map((v) => ({ Value: v }))
|
|
26523
|
+
}
|
|
26524
|
+
});
|
|
26525
|
+
const dmarcRuaDomain = mailFromDomain || domain;
|
|
26526
|
+
changes.push({
|
|
26527
|
+
Action: "UPSERT",
|
|
26528
|
+
ResourceRecordSet: {
|
|
26529
|
+
Name: `_dmarc.${domain}`,
|
|
26530
|
+
Type: "TXT",
|
|
26531
|
+
TTL: 1800,
|
|
26532
|
+
ResourceRecords: [
|
|
26533
|
+
{
|
|
26534
|
+
Value: `"v=DMARC1; p=quarantine; sp=quarantine; np=reject; rua=mailto:postmaster@${dmarcRuaDomain}"`
|
|
26330
26535
|
}
|
|
26331
|
-
|
|
26332
|
-
|
|
26333
|
-
|
|
26334
|
-
|
|
26335
|
-
|
|
26536
|
+
]
|
|
26537
|
+
}
|
|
26538
|
+
});
|
|
26539
|
+
if (customTrackingDomain) {
|
|
26540
|
+
const targetDomain = cloudFrontDomain || `r.${region}.awstrack.me`;
|
|
26541
|
+
changes.push({
|
|
26542
|
+
Action: "UPSERT",
|
|
26543
|
+
ResourceRecordSet: {
|
|
26544
|
+
Name: customTrackingDomain,
|
|
26545
|
+
Type: "CNAME",
|
|
26546
|
+
TTL: 1800,
|
|
26547
|
+
ResourceRecords: [{ Value: targetDomain }]
|
|
26336
26548
|
}
|
|
26337
|
-
|
|
26338
|
-
|
|
26339
|
-
|
|
26340
|
-
|
|
26341
|
-
|
|
26342
|
-
|
|
26343
|
-
|
|
26344
|
-
|
|
26345
|
-
|
|
26346
|
-
|
|
26347
|
-
|
|
26348
|
-
|
|
26349
|
-
`/v2/domains/${this.domain}/records`,
|
|
26350
|
-
"POST",
|
|
26351
|
-
body
|
|
26352
|
-
);
|
|
26353
|
-
return !result.error;
|
|
26549
|
+
});
|
|
26550
|
+
}
|
|
26551
|
+
if (mailFromDomain) {
|
|
26552
|
+
changes.push({
|
|
26553
|
+
Action: "UPSERT",
|
|
26554
|
+
ResourceRecordSet: {
|
|
26555
|
+
Name: mailFromDomain,
|
|
26556
|
+
Type: "MX",
|
|
26557
|
+
TTL: 1800,
|
|
26558
|
+
ResourceRecords: [
|
|
26559
|
+
{ Value: `10 feedback-smtp.${region}.amazonses.com` }
|
|
26560
|
+
]
|
|
26354
26561
|
}
|
|
26355
|
-
|
|
26356
|
-
|
|
26357
|
-
|
|
26358
|
-
|
|
26359
|
-
|
|
26360
|
-
|
|
26361
|
-
|
|
26362
|
-
|
|
26363
|
-
if (!record) {
|
|
26364
|
-
missing.push(`DKIM: ${name}`);
|
|
26365
|
-
} else if (record.value !== expectedValue) {
|
|
26366
|
-
incorrect.push(`DKIM: ${name} (expected ${expectedValue})`);
|
|
26367
|
-
}
|
|
26368
|
-
}
|
|
26369
|
-
const spfRecord = await this.findRecord(domain, "TXT");
|
|
26370
|
-
if (!spfRecord) {
|
|
26371
|
-
missing.push(`SPF: ${domain}`);
|
|
26372
|
-
} else if (!spfRecord.value.includes("include:amazonses.com")) {
|
|
26373
|
-
incorrect.push(`SPF: ${domain} (missing amazonses.com include)`);
|
|
26374
|
-
}
|
|
26375
|
-
const dmarcRecord = await this.findRecord(`_dmarc.${domain}`, "TXT");
|
|
26376
|
-
if (!dmarcRecord) {
|
|
26377
|
-
missing.push(`DMARC: _dmarc.${domain}`);
|
|
26378
|
-
}
|
|
26379
|
-
if (mailFromDomain) {
|
|
26380
|
-
const mxRecord = await this.findRecord(mailFromDomain, "MX");
|
|
26381
|
-
if (!mxRecord) {
|
|
26382
|
-
missing.push(`MX: ${mailFromDomain}`);
|
|
26383
|
-
} else if (!mxRecord.value.includes(`feedback-smtp.${region}.amazonses.com`)) {
|
|
26384
|
-
incorrect.push(`MX: ${mailFromDomain}`);
|
|
26385
|
-
}
|
|
26386
|
-
const mailFromSpf = await this.findRecord(mailFromDomain, "TXT");
|
|
26387
|
-
if (!mailFromSpf) {
|
|
26388
|
-
missing.push(`SPF: ${mailFromDomain}`);
|
|
26389
|
-
}
|
|
26390
|
-
}
|
|
26391
|
-
return {
|
|
26392
|
-
verified: missing.length === 0 && incorrect.length === 0,
|
|
26393
|
-
missing,
|
|
26394
|
-
incorrect
|
|
26395
|
-
};
|
|
26562
|
+
});
|
|
26563
|
+
changes.push({
|
|
26564
|
+
Action: "UPSERT",
|
|
26565
|
+
ResourceRecordSet: {
|
|
26566
|
+
Name: mailFromDomain,
|
|
26567
|
+
Type: "TXT",
|
|
26568
|
+
TTL: 1800,
|
|
26569
|
+
ResourceRecords: [{ Value: '"v=spf1 include:amazonses.com ~all"' }]
|
|
26396
26570
|
}
|
|
26397
|
-
};
|
|
26571
|
+
});
|
|
26572
|
+
}
|
|
26573
|
+
await client2.send(
|
|
26574
|
+
new ChangeResourceRecordSetsCommand2({
|
|
26575
|
+
HostedZoneId: hostedZoneId,
|
|
26576
|
+
ChangeBatch: {
|
|
26577
|
+
Changes: changes
|
|
26578
|
+
}
|
|
26579
|
+
})
|
|
26580
|
+
);
|
|
26581
|
+
}
|
|
26582
|
+
async function deleteDNSRecords(hostedZoneId, domain, dkimTokens, region, customTrackingDomain, mailFromDomain) {
|
|
26583
|
+
const { deleteEmailDNSRecordsForProvider: deleteEmailDNSRecordsForProvider2 } = await Promise.resolve().then(() => (init_email_dns_cleanup(), email_dns_cleanup_exports));
|
|
26584
|
+
return deleteEmailDNSRecordsForProvider2(
|
|
26585
|
+
{ provider: "route53", hostedZoneId },
|
|
26586
|
+
{
|
|
26587
|
+
domain,
|
|
26588
|
+
dkimTokens,
|
|
26589
|
+
region,
|
|
26590
|
+
customTrackingDomain,
|
|
26591
|
+
mailFromDomain
|
|
26592
|
+
}
|
|
26593
|
+
);
|
|
26594
|
+
}
|
|
26595
|
+
var init_route53 = __esm({
|
|
26596
|
+
"src/utils/route53.ts"() {
|
|
26597
|
+
"use strict";
|
|
26598
|
+
init_esm_shims();
|
|
26398
26599
|
}
|
|
26399
26600
|
});
|
|
26400
26601
|
|
|
@@ -26415,9 +26616,9 @@ __export(create_records_exports, {
|
|
|
26415
26616
|
normalizeRoute53Name: () => normalizeRoute53Name
|
|
26416
26617
|
});
|
|
26417
26618
|
import {
|
|
26418
|
-
ChangeResourceRecordSetsCommand as
|
|
26419
|
-
ListResourceRecordSetsCommand as
|
|
26420
|
-
Route53Client as
|
|
26619
|
+
ChangeResourceRecordSetsCommand as ChangeResourceRecordSetsCommand3,
|
|
26620
|
+
ListResourceRecordSetsCommand as ListResourceRecordSetsCommand3,
|
|
26621
|
+
Route53Client as Route53Client3
|
|
26421
26622
|
} from "@aws-sdk/client-route-53";
|
|
26422
26623
|
import pc7 from "picocolors";
|
|
26423
26624
|
function buildEmailDNSRecords(data) {
|
|
@@ -26634,7 +26835,7 @@ async function listExistingRoute53InboundRecords(client2, hostedZoneId, name) {
|
|
|
26634
26835
|
const MAX_PAGES = 50;
|
|
26635
26836
|
for (let page = 0; page < MAX_PAGES; page++) {
|
|
26636
26837
|
const response = await client2.send(
|
|
26637
|
-
new
|
|
26838
|
+
new ListResourceRecordSetsCommand3({
|
|
26638
26839
|
HostedZoneId: hostedZoneId,
|
|
26639
26840
|
StartRecordName: startRecordName,
|
|
26640
26841
|
StartRecordType: startRecordType
|
|
@@ -26814,7 +27015,7 @@ async function createInboundDNSRecordsForProvider(credentials, receivingDomain,
|
|
|
26814
27015
|
const records = buildInboundDNSRecords(receivingDomain, region);
|
|
26815
27016
|
switch (credentials.provider) {
|
|
26816
27017
|
case "route53": {
|
|
26817
|
-
const client2 = new
|
|
27018
|
+
const client2 = new Route53Client3({ region });
|
|
26818
27019
|
let existing;
|
|
26819
27020
|
try {
|
|
26820
27021
|
existing = await listExistingRoute53InboundRecords(
|
|
@@ -26862,7 +27063,7 @@ async function createInboundDNSRecordsForProvider(credentials, receivingDomain,
|
|
|
26862
27063
|
}
|
|
26863
27064
|
try {
|
|
26864
27065
|
await client2.send(
|
|
26865
|
-
new
|
|
27066
|
+
new ChangeResourceRecordSetsCommand3({
|
|
26866
27067
|
HostedZoneId: credentials.hostedZoneId,
|
|
26867
27068
|
ChangeBatch: { Changes: changes }
|
|
26868
27069
|
})
|
|
@@ -31180,10 +31381,10 @@ var init_credentials = __esm({
|
|
|
31180
31381
|
|
|
31181
31382
|
// src/utils/dns/inbound-dns-cleanup.ts
|
|
31182
31383
|
import {
|
|
31183
|
-
ChangeResourceRecordSetsCommand as
|
|
31184
|
-
Route53Client as
|
|
31384
|
+
ChangeResourceRecordSetsCommand as ChangeResourceRecordSetsCommand4,
|
|
31385
|
+
Route53Client as Route53Client4
|
|
31185
31386
|
} from "@aws-sdk/client-route-53";
|
|
31186
|
-
async function
|
|
31387
|
+
async function deleteViaValueMatchedClient2(client2, records) {
|
|
31187
31388
|
const deleted = [];
|
|
31188
31389
|
const skipped = [];
|
|
31189
31390
|
const errors2 = [];
|
|
@@ -31292,7 +31493,7 @@ function buildRoute53InboundTXTRemoval(record, existing) {
|
|
|
31292
31493
|
return { change: null };
|
|
31293
31494
|
}
|
|
31294
31495
|
async function deleteRoute53InboundRecords(hostedZoneId, receivingDomain, region, records) {
|
|
31295
|
-
const client2 = new
|
|
31496
|
+
const client2 = new Route53Client4({ region });
|
|
31296
31497
|
let existing;
|
|
31297
31498
|
try {
|
|
31298
31499
|
existing = await listExistingRoute53InboundRecords(
|
|
@@ -31345,7 +31546,7 @@ async function deleteRoute53InboundRecords(hostedZoneId, receivingDomain, region
|
|
|
31345
31546
|
}
|
|
31346
31547
|
try {
|
|
31347
31548
|
await client2.send(
|
|
31348
|
-
new
|
|
31549
|
+
new ChangeResourceRecordSetsCommand4({
|
|
31349
31550
|
HostedZoneId: hostedZoneId,
|
|
31350
31551
|
ChangeBatch: { Changes: changes }
|
|
31351
31552
|
})
|
|
@@ -31368,7 +31569,7 @@ async function deleteInboundDNSRecordsForProvider(credentials, receivingDomain,
|
|
|
31368
31569
|
credentials.zoneId,
|
|
31369
31570
|
credentials.token
|
|
31370
31571
|
);
|
|
31371
|
-
return
|
|
31572
|
+
return deleteViaValueMatchedClient2(client2, records);
|
|
31372
31573
|
}
|
|
31373
31574
|
case "vercel": {
|
|
31374
31575
|
const client2 = new VercelDNSClient(
|
|
@@ -31376,7 +31577,7 @@ async function deleteInboundDNSRecordsForProvider(credentials, receivingDomain,
|
|
|
31376
31577
|
credentials.token,
|
|
31377
31578
|
credentials.teamId
|
|
31378
31579
|
);
|
|
31379
|
-
return
|
|
31580
|
+
return deleteViaValueMatchedClient2(client2, records);
|
|
31380
31581
|
}
|
|
31381
31582
|
case "route53":
|
|
31382
31583
|
return deleteRoute53InboundRecords(
|
|
@@ -31400,7 +31601,7 @@ var init_inbound_dns_cleanup = __esm({
|
|
|
31400
31601
|
});
|
|
31401
31602
|
|
|
31402
31603
|
// src/utils/dns/inbound-preflight.ts
|
|
31403
|
-
import { Route53Client as
|
|
31604
|
+
import { Route53Client as Route53Client5 } from "@aws-sdk/client-route-53";
|
|
31404
31605
|
import * as clack24 from "@clack/prompts";
|
|
31405
31606
|
function stripQuotes(value) {
|
|
31406
31607
|
return value.replace(/^"|"$/g, "");
|
|
@@ -31478,7 +31679,7 @@ async function checkInboundDNSPreflight(credentials, receivingDomain, region, pa
|
|
|
31478
31679
|
}
|
|
31479
31680
|
}
|
|
31480
31681
|
try {
|
|
31481
|
-
const client2 = new
|
|
31682
|
+
const client2 = new Route53Client5({ region });
|
|
31482
31683
|
const existing = await listExistingRoute53InboundRecords(
|
|
31483
31684
|
client2,
|
|
31484
31685
|
credentials.hostedZoneId,
|
|
@@ -31577,6 +31778,7 @@ __export(dns_exports, {
|
|
|
31577
31778
|
checkInboundDNSPreflight: () => checkInboundDNSPreflight,
|
|
31578
31779
|
createDNSRecordsForProvider: () => createDNSRecordsForProvider,
|
|
31579
31780
|
createInboundDNSRecordsForProvider: () => createInboundDNSRecordsForProvider,
|
|
31781
|
+
deleteEmailDNSRecordsForProvider: () => deleteEmailDNSRecordsForProvider,
|
|
31580
31782
|
deleteInboundDNSRecordsForProvider: () => deleteInboundDNSRecordsForProvider,
|
|
31581
31783
|
describeInboundDNSConflict: () => describeInboundDNSConflict,
|
|
31582
31784
|
detectAvailableDNSProviders: () => detectAvailableDNSProviders,
|
|
@@ -31599,6 +31801,7 @@ var init_dns = __esm({
|
|
|
31599
31801
|
init_cloudflare();
|
|
31600
31802
|
init_create_records();
|
|
31601
31803
|
init_credentials();
|
|
31804
|
+
init_email_dns_cleanup();
|
|
31602
31805
|
init_inbound_dns_cleanup();
|
|
31603
31806
|
init_inbound_preflight();
|
|
31604
31807
|
init_vercel();
|
|
@@ -34712,13 +34915,13 @@ async function emptyS3Bucket(bucketName, region) {
|
|
|
34712
34915
|
}
|
|
34713
34916
|
async function deleteCdnDNSRecords(hostedZoneId, customDomain) {
|
|
34714
34917
|
const {
|
|
34715
|
-
Route53Client:
|
|
34716
|
-
ListResourceRecordSetsCommand:
|
|
34717
|
-
ChangeResourceRecordSetsCommand:
|
|
34918
|
+
Route53Client: Route53Client7,
|
|
34919
|
+
ListResourceRecordSetsCommand: ListResourceRecordSetsCommand4,
|
|
34920
|
+
ChangeResourceRecordSetsCommand: ChangeResourceRecordSetsCommand6
|
|
34718
34921
|
} = await import("@aws-sdk/client-route-53");
|
|
34719
|
-
const route533 = new
|
|
34922
|
+
const route533 = new Route53Client7({ region: "us-east-1" });
|
|
34720
34923
|
const listResponse = await route533.send(
|
|
34721
|
-
new
|
|
34924
|
+
new ListResourceRecordSetsCommand4({
|
|
34722
34925
|
HostedZoneId: hostedZoneId
|
|
34723
34926
|
})
|
|
34724
34927
|
);
|
|
@@ -34736,7 +34939,7 @@ async function deleteCdnDNSRecords(hostedZoneId, customDomain) {
|
|
|
34736
34939
|
ResourceRecordSet: record
|
|
34737
34940
|
}));
|
|
34738
34941
|
await route533.send(
|
|
34739
|
-
new
|
|
34942
|
+
new ChangeResourceRecordSetsCommand6({
|
|
34740
34943
|
HostedZoneId: hostedZoneId,
|
|
34741
34944
|
ChangeBatch: { Changes: changes }
|
|
34742
34945
|
})
|
|
@@ -36109,13 +36312,13 @@ ${pc12.yellow(pc12.bold("Configuration Notes:"))}`);
|
|
|
36109
36312
|
try {
|
|
36110
36313
|
progress.start("Checking existing DNS records");
|
|
36111
36314
|
const {
|
|
36112
|
-
Route53Client:
|
|
36113
|
-
ListResourceRecordSetsCommand:
|
|
36114
|
-
ChangeResourceRecordSetsCommand:
|
|
36315
|
+
Route53Client: Route53Client7,
|
|
36316
|
+
ListResourceRecordSetsCommand: ListResourceRecordSetsCommand4,
|
|
36317
|
+
ChangeResourceRecordSetsCommand: ChangeResourceRecordSetsCommand6
|
|
36115
36318
|
} = await import("@aws-sdk/client-route-53");
|
|
36116
|
-
const route533 = new
|
|
36319
|
+
const route533 = new Route53Client7({ region: "us-east-1" });
|
|
36117
36320
|
const existingRecords = await route533.send(
|
|
36118
|
-
new
|
|
36321
|
+
new ListResourceRecordSetsCommand4({
|
|
36119
36322
|
HostedZoneId: hostedZone.id,
|
|
36120
36323
|
StartRecordName: outputs.customDomain,
|
|
36121
36324
|
StartRecordType: "CNAME",
|
|
@@ -36203,7 +36406,7 @@ ${pc12.yellow(pc12.bold("Configuration Notes:"))}`);
|
|
|
36203
36406
|
}));
|
|
36204
36407
|
if (changes.length > 0) {
|
|
36205
36408
|
await route533.send(
|
|
36206
|
-
new
|
|
36409
|
+
new ChangeResourceRecordSetsCommand6({
|
|
36207
36410
|
HostedZoneId: hostedZone.id,
|
|
36208
36411
|
ChangeBatch: { Changes: changes }
|
|
36209
36412
|
})
|
|
@@ -41493,9 +41696,9 @@ var IP_BLACKLISTS = [
|
|
|
41493
41696
|
{ name: "ScientificSpam", zone: "bl.scientificspam.net", priority: "medium" },
|
|
41494
41697
|
{ name: "Swinog IP", zone: "dnsrbl.swinog.ch", priority: "medium" },
|
|
41495
41698
|
{ name: "Kempt.net", zone: "dnsbl.kempt.net", priority: "low" },
|
|
41496
|
-
{ name: "Anonmails", zone: "spam.dnsbl.anonmails.de", priority: "low" }
|
|
41699
|
+
{ name: "Anonmails", zone: "spam.dnsbl.anonmails.de", priority: "low" }
|
|
41497
41700
|
// Reputation lists
|
|
41498
|
-
|
|
41701
|
+
// cbl.anti-spam.org.cn removed: dead zone, wildcards every query to 208.98.x.x
|
|
41499
41702
|
];
|
|
41500
41703
|
var DEFAULT_TIMEOUT3 = 5e3;
|
|
41501
41704
|
var SPF_LOOKUP_LIMIT = 10;
|
|
@@ -41958,8 +42161,11 @@ var TEST_RETURN_CODES = /* @__PURE__ */ new Set([
|
|
|
41958
42161
|
"127.255.255.255"
|
|
41959
42162
|
// Reserved/test
|
|
41960
42163
|
]);
|
|
42164
|
+
function isValidListingResponse(returnCode) {
|
|
42165
|
+
return returnCode.startsWith("127.");
|
|
42166
|
+
}
|
|
41961
42167
|
function isTestResponse(returnCode) {
|
|
41962
|
-
return TEST_RETURN_CODES.has(returnCode);
|
|
42168
|
+
return !isValidListingResponse(returnCode) || TEST_RETURN_CODES.has(returnCode);
|
|
41963
42169
|
}
|
|
41964
42170
|
async function checkBlacklist(options) {
|
|
41965
42171
|
const { quick = false, skip = false, domain, ips = [] } = options;
|
|
@@ -43260,7 +43466,7 @@ async function parseAndValidateSpf(domain, record, result) {
|
|
|
43260
43466
|
result.valid = false;
|
|
43261
43467
|
} else if (allMechanism === "?all") {
|
|
43262
43468
|
result.warnings.push(
|
|
43263
|
-
"SPF uses ?all (neutral) - consider using
|
|
43469
|
+
"SPF uses ?all (neutral) - consider using ~all or -all"
|
|
43264
43470
|
);
|
|
43265
43471
|
} else if (!allMechanism) {
|
|
43266
43472
|
result.warnings.push(
|
|
@@ -43396,7 +43602,7 @@ var GRADE_BANDS = {
|
|
|
43396
43602
|
function calculateScore(checks) {
|
|
43397
43603
|
const deductions = [];
|
|
43398
43604
|
const bonuses = [];
|
|
43399
|
-
collectSpfIssues(checks.spf, deductions);
|
|
43605
|
+
collectSpfIssues(checks.spf, checks.dmarc, deductions);
|
|
43400
43606
|
collectDkimIssues(checks, deductions, bonuses);
|
|
43401
43607
|
collectDmarcIssues(checks.dmarc, deductions, bonuses);
|
|
43402
43608
|
collectInfraIssues(checks, deductions, bonuses);
|
|
@@ -43497,7 +43703,22 @@ function determineGrade(checks) {
|
|
|
43497
43703
|
}
|
|
43498
43704
|
return "F";
|
|
43499
43705
|
}
|
|
43500
|
-
function
|
|
43706
|
+
function dmarcEnforces(dmarc) {
|
|
43707
|
+
return dmarc.exists && dmarc.valid && !dmarc.testing && (dmarc.policy === "quarantine" || dmarc.policy === "reject");
|
|
43708
|
+
}
|
|
43709
|
+
var AUTHORIZING_MECHANISM = /^(?:a|mx|ptr)(?:[:/]|$)|^(?:ip4|ip6|include|exists):/i;
|
|
43710
|
+
function authorizesSenders(spf) {
|
|
43711
|
+
return (spf.record ?? "").trim().split(/\s+/).slice(1).some((term) => {
|
|
43712
|
+
if (/^redirect=/i.test(term)) {
|
|
43713
|
+
return true;
|
|
43714
|
+
}
|
|
43715
|
+
return AUTHORIZING_MECHANISM.test(term.replace(/^[+\-~?]/, ""));
|
|
43716
|
+
});
|
|
43717
|
+
}
|
|
43718
|
+
function hardfailWithoutEnforcingDmarc(spf, dmarc) {
|
|
43719
|
+
return spf.allMechanism === "-all" && authorizesSenders(spf) && !dmarcEnforces(dmarc);
|
|
43720
|
+
}
|
|
43721
|
+
function collectSpfIssues(spf, dmarc, deductions) {
|
|
43501
43722
|
if (!spf.exists) {
|
|
43502
43723
|
deductions.push({ check: "spf", points: 5, reason: "No SPF record" });
|
|
43503
43724
|
return;
|
|
@@ -43536,11 +43757,11 @@ function collectSpfIssues(spf, deductions) {
|
|
|
43536
43757
|
points: 3,
|
|
43537
43758
|
reason: "SPF ?all is too permissive"
|
|
43538
43759
|
});
|
|
43539
|
-
} else if (spf
|
|
43760
|
+
} else if (hardfailWithoutEnforcingDmarc(spf, dmarc)) {
|
|
43540
43761
|
deductions.push({
|
|
43541
43762
|
check: "spf",
|
|
43542
43763
|
points: 2,
|
|
43543
|
-
reason: "SPF
|
|
43764
|
+
reason: "SPF -all (hardfail) without an enforcing DMARC policy \u2014 forwarded mail is rejected before DKIM is checked"
|
|
43544
43765
|
});
|
|
43545
43766
|
}
|
|
43546
43767
|
if (spf.lookupCount > 10) {
|
|
@@ -44081,7 +44302,7 @@ function displayResults2(result, options) {
|
|
|
44081
44302
|
console.log();
|
|
44082
44303
|
console.log(pc21.bold("AUTHENTICATION"));
|
|
44083
44304
|
console.log();
|
|
44084
|
-
displaySpfResult(spf, options.verbose);
|
|
44305
|
+
displaySpfResult(spf, dmarc, options.verbose);
|
|
44085
44306
|
displayDkimResult(dkim, options.verbose);
|
|
44086
44307
|
displayDmarcResult(dmarc);
|
|
44087
44308
|
displayBimiResult(bimi, result.domain);
|
|
@@ -44221,7 +44442,7 @@ function displayScoreBox(domain, score, grade, quick) {
|
|
|
44221
44442
|
console.log(pc21.dim("\u2502") + " ".repeat(width - 2) + pc21.dim("\u2502"));
|
|
44222
44443
|
console.log(pc21.dim(`\u2570${"\u2500".repeat(width - 2)}\u256F`));
|
|
44223
44444
|
}
|
|
44224
|
-
function displaySpfResult(spf, verbose) {
|
|
44445
|
+
function displaySpfResult(spf, dmarc, verbose) {
|
|
44225
44446
|
const status2 = spf.exists && spf.valid ? pc21.green("\u2713") : spf.exists ? pc21.yellow("\u26A0") : pc21.red("\u2717");
|
|
44226
44447
|
console.log(
|
|
44227
44448
|
` ${status2} ${pc21.dim("SPF")} ${spf.record || "Not found"}`
|
|
@@ -44237,9 +44458,9 @@ function displaySpfResult(spf, verbose) {
|
|
|
44237
44458
|
" " + formatSpfLookupTree(spf.lookupTree).split("\n").join("\n ")
|
|
44238
44459
|
);
|
|
44239
44460
|
}
|
|
44240
|
-
if (spf
|
|
44461
|
+
if (hardfailWithoutEnforcingDmarc(spf, dmarc)) {
|
|
44241
44462
|
console.log(
|
|
44242
|
-
` ${pc21.
|
|
44463
|
+
` ${pc21.yellow("Hardfail (-all) with no enforcing DMARC \u2014 forwarded mail is rejected before DKIM is checked")}`
|
|
44243
44464
|
);
|
|
44244
44465
|
}
|
|
44245
44466
|
}
|
|
@@ -45272,6 +45493,17 @@ async function getEmailIdentityInfo(domain, region) {
|
|
|
45272
45493
|
throw error;
|
|
45273
45494
|
}
|
|
45274
45495
|
}
|
|
45496
|
+
function reportEmailDNSCleanup(result) {
|
|
45497
|
+
for (const label of result.deleted) {
|
|
45498
|
+
clack22.log.success(`Deleted DNS record: ${label}`);
|
|
45499
|
+
}
|
|
45500
|
+
for (const skip of result.skipped) {
|
|
45501
|
+
clack22.log.warn(`Left DNS record in place: ${skip.record} (${skip.reason})`);
|
|
45502
|
+
}
|
|
45503
|
+
for (const err of result.errors) {
|
|
45504
|
+
clack22.log.warn(`DNS cleanup error: ${err}`);
|
|
45505
|
+
}
|
|
45506
|
+
}
|
|
45275
45507
|
function selectAgentsToDestroy(emailConfig) {
|
|
45276
45508
|
const agentsConfig = emailConfig?.agents;
|
|
45277
45509
|
if (!agentsConfig?.enabled) {
|
|
@@ -45419,7 +45651,7 @@ async function emailDestroy(options) {
|
|
|
45419
45651
|
shouldCleanDNS = true;
|
|
45420
45652
|
} else {
|
|
45421
45653
|
const cleanDNS = await clack22.confirm({
|
|
45422
|
-
message: `Found Route53 hosted zone for ${pc24.cyan(domain)}. Delete DNS records (DKIM, DMARC, MAIL FROM)
|
|
45654
|
+
message: `Found Route53 hosted zone for ${pc24.cyan(domain)}. Delete the DNS records Wraps created (DKIM, DMARC, tracking CNAME, MAIL FROM)? Records Wraps did not create are left in place.`,
|
|
45423
45655
|
initialValue: true
|
|
45424
45656
|
});
|
|
45425
45657
|
if (clack22.isCancel(cleanDNS)) {
|
|
@@ -45495,16 +45727,18 @@ async function emailDestroy(options) {
|
|
|
45495
45727
|
}
|
|
45496
45728
|
if (shouldCleanDNS && hostedZone && domain && dkimTokens.length > 0) {
|
|
45497
45729
|
try {
|
|
45498
|
-
await progress.execute(
|
|
45499
|
-
|
|
45730
|
+
const result = await progress.execute(
|
|
45731
|
+
`Deleting DNS records for ${domain}`,
|
|
45732
|
+
() => deleteDNSRecords(
|
|
45500
45733
|
hostedZone.id,
|
|
45501
45734
|
domain,
|
|
45502
45735
|
dkimTokens,
|
|
45503
45736
|
region,
|
|
45504
45737
|
emailConfig?.tracking?.customRedirectDomain,
|
|
45505
45738
|
mailFromDomain
|
|
45506
|
-
)
|
|
45507
|
-
|
|
45739
|
+
)
|
|
45740
|
+
);
|
|
45741
|
+
reportEmailDNSCleanup(result);
|
|
45508
45742
|
} catch (error) {
|
|
45509
45743
|
const msg = error instanceof Error ? error.message : String(error);
|
|
45510
45744
|
clack22.log.warn(`Could not delete DNS records: ${msg}`);
|
|
@@ -45769,8 +46003,8 @@ import {
|
|
|
45769
46003
|
UpdateDistributionCommand
|
|
45770
46004
|
} from "@aws-sdk/client-cloudfront";
|
|
45771
46005
|
import {
|
|
45772
|
-
ChangeResourceRecordSetsCommand as
|
|
45773
|
-
Route53Client as
|
|
46006
|
+
ChangeResourceRecordSetsCommand as ChangeResourceRecordSetsCommand5,
|
|
46007
|
+
Route53Client as Route53Client6
|
|
45774
46008
|
} from "@aws-sdk/client-route-53";
|
|
45775
46009
|
var US_EAST_1 = "us-east-1";
|
|
45776
46010
|
var CERT_POLL_ATTEMPTS = 10;
|
|
@@ -45971,9 +46205,9 @@ async function pushValidationRecord(provider, sendingDomain, sesRegion, record)
|
|
|
45971
46205
|
return result.success;
|
|
45972
46206
|
}
|
|
45973
46207
|
if (credResult.credentials.provider === "route53") {
|
|
45974
|
-
const route533 = new
|
|
46208
|
+
const route533 = new Route53Client6({ region: sesRegion });
|
|
45975
46209
|
await route533.send(
|
|
45976
|
-
new
|
|
46210
|
+
new ChangeResourceRecordSetsCommand5({
|
|
45977
46211
|
HostedZoneId: credResult.credentials.hostedZoneId,
|
|
45978
46212
|
ChangeBatch: {
|
|
45979
46213
|
Changes: [
|
|
@@ -49225,9 +49459,9 @@ async function inboundInit(options) {
|
|
|
49225
49459
|
region,
|
|
49226
49460
|
domain
|
|
49227
49461
|
);
|
|
49228
|
-
if (result.success
|
|
49462
|
+
if (result.success) {
|
|
49229
49463
|
progress.succeed(
|
|
49230
|
-
`Created ${result.recordsCreated} DNS records in ${getDNSProviderDisplayName2(dnsProvider)}`
|
|
49464
|
+
result.recordsCreated > 0 ? `Created ${result.recordsCreated} DNS records in ${getDNSProviderDisplayName2(dnsProvider)}` : `DNS already configured for ${receivingDomain}`
|
|
49231
49465
|
);
|
|
49232
49466
|
dnsAutoCreated = true;
|
|
49233
49467
|
} else {
|
|
@@ -49356,6 +49590,12 @@ Deploy first: ${pc28.cyan("wraps email inbound init")}
|
|
|
49356
49590
|
clack27.log.warn(
|
|
49357
49591
|
`This will remove inbound email for ${pc28.cyan(inboundConfig.receivingDomain || "")}`
|
|
49358
49592
|
);
|
|
49593
|
+
const replyThreading = emailService.config.replyThreading;
|
|
49594
|
+
if (replyThreading?.enabled && replyThreading.domains.length > 0) {
|
|
49595
|
+
clack27.log.warn(
|
|
49596
|
+
`Reply threading will stop working for ${replyThreading.domains.map((d6) => pc28.cyan(d6.domain)).join(", ")} once inbound infrastructure is removed. Run ${pc28.cyan("wraps email reply destroy")} to clean those up.`
|
|
49597
|
+
);
|
|
49598
|
+
}
|
|
49359
49599
|
if (allInboundDomains.length > 0) {
|
|
49360
49600
|
const { buildInboundDNSRecords: buildRecordsForDisplay } = await Promise.resolve().then(() => (init_dns(), dns_exports));
|
|
49361
49601
|
clack27.log.info(pc28.bold("DNS records that will be deleted:"));
|
|
@@ -50036,9 +50276,9 @@ Deploy first: ${pc28.cyan("wraps email inbound init")}
|
|
|
50036
50276
|
region,
|
|
50037
50277
|
parentDomain
|
|
50038
50278
|
);
|
|
50039
|
-
if (result.success
|
|
50279
|
+
if (result.success) {
|
|
50040
50280
|
progress.succeed(
|
|
50041
|
-
`Created ${result.recordsCreated} DNS records in ${getDNSProviderDisplayName2(dnsProvider)}`
|
|
50281
|
+
result.recordsCreated > 0 ? `Created ${result.recordsCreated} DNS records in ${getDNSProviderDisplayName2(dnsProvider)}` : `DNS already configured for ${receivingDomain}`
|
|
50042
50282
|
);
|
|
50043
50283
|
dnsAutoCreated = true;
|
|
50044
50284
|
if (replyThreadingEnabled) {
|
|
@@ -51854,9 +52094,9 @@ to diagnose, or retry the init.`,
|
|
|
51854
52094
|
region,
|
|
51855
52095
|
domain
|
|
51856
52096
|
);
|
|
51857
|
-
if (result.success
|
|
52097
|
+
if (result.success) {
|
|
51858
52098
|
progress.succeed(
|
|
51859
|
-
`Created ${result.recordsCreated} DNS records for r.mail.${domain} via ${getDNSProviderDisplayName2(dnsProvider)}`
|
|
52099
|
+
result.recordsCreated > 0 ? `Created ${result.recordsCreated} DNS records for r.mail.${domain} via ${getDNSProviderDisplayName2(dnsProvider)}` : `DNS already configured for r.mail.${domain}`
|
|
51860
52100
|
);
|
|
51861
52101
|
dnsAutoCreated = true;
|
|
51862
52102
|
}
|
|
@@ -52327,17 +52567,65 @@ function stripDomainFromReplyThreadingMetadata(params) {
|
|
|
52327
52567
|
domains: remaining
|
|
52328
52568
|
};
|
|
52329
52569
|
}
|
|
52570
|
+
var UNSUPPORTED_REPLY_DNS_CLEANUP = {
|
|
52571
|
+
deleted: [],
|
|
52572
|
+
skipped: [],
|
|
52573
|
+
supported: false,
|
|
52574
|
+
errors: []
|
|
52575
|
+
};
|
|
52330
52576
|
async function cleanupDomainInfra(params) {
|
|
52331
|
-
const { domain, region, progress } = params;
|
|
52577
|
+
const { domain, region, progress, metadata } = params;
|
|
52332
52578
|
await progress.execute(
|
|
52333
52579
|
`Removing r.mail.${domain} from receipt rule`,
|
|
52334
52580
|
async () => {
|
|
52335
52581
|
await removeDomainFromReceiptRule(region, `r.mail.${domain}`);
|
|
52336
52582
|
}
|
|
52337
52583
|
);
|
|
52338
|
-
|
|
52339
|
-
|
|
52340
|
-
|
|
52584
|
+
const dnsProvider = metadata.services.email?.dnsProvider;
|
|
52585
|
+
if (!dnsProvider || dnsProvider === "manual") {
|
|
52586
|
+
return UNSUPPORTED_REPLY_DNS_CLEANUP;
|
|
52587
|
+
}
|
|
52588
|
+
try {
|
|
52589
|
+
const { getDNSCredentials: getDNSCredentials2, deleteInboundDNSRecordsForProvider: deleteInboundDNSRecordsForProvider2 } = await Promise.resolve().then(() => (init_dns(), dns_exports));
|
|
52590
|
+
const credentialResult = await getDNSCredentials2(
|
|
52591
|
+
dnsProvider,
|
|
52592
|
+
domain,
|
|
52593
|
+
region
|
|
52594
|
+
);
|
|
52595
|
+
if (!(credentialResult.valid && credentialResult.credentials)) {
|
|
52596
|
+
clack34.log.warn(
|
|
52597
|
+
`Could not validate ${dnsProvider} credentials to delete DNS records for r.mail.${domain}: ${credentialResult.error || "unknown error"}`
|
|
52598
|
+
);
|
|
52599
|
+
return UNSUPPORTED_REPLY_DNS_CLEANUP;
|
|
52600
|
+
}
|
|
52601
|
+
return await deleteInboundDNSRecordsForProvider2(
|
|
52602
|
+
credentialResult.credentials,
|
|
52603
|
+
`r.mail.${domain}`,
|
|
52604
|
+
region,
|
|
52605
|
+
domain
|
|
52606
|
+
);
|
|
52607
|
+
} catch (error) {
|
|
52608
|
+
clack34.log.warn(
|
|
52609
|
+
`Failed to delete DNS records for r.mail.${domain}: ${error instanceof Error ? error.message : String(error)}`
|
|
52610
|
+
);
|
|
52611
|
+
return UNSUPPORTED_REPLY_DNS_CLEANUP;
|
|
52612
|
+
}
|
|
52613
|
+
}
|
|
52614
|
+
function reportReplyDNSCleanup(result, domain) {
|
|
52615
|
+
for (const label of result.deleted) {
|
|
52616
|
+
clack34.log.success(`Deleted DNS record: ${label}`);
|
|
52617
|
+
}
|
|
52618
|
+
for (const skip of result.skipped) {
|
|
52619
|
+
clack34.log.warn(`Left DNS record in place: ${skip.record} (${skip.reason})`);
|
|
52620
|
+
}
|
|
52621
|
+
for (const err of result.errors) {
|
|
52622
|
+
clack34.log.warn(`DNS cleanup error: ${err}`);
|
|
52623
|
+
}
|
|
52624
|
+
if (!result.supported) {
|
|
52625
|
+
clack34.log.info(
|
|
52626
|
+
`Remove MX/SPF DNS records for ${pc35.cyan(`r.mail.${domain}`)} from your DNS provider.`
|
|
52627
|
+
);
|
|
52628
|
+
}
|
|
52341
52629
|
}
|
|
52342
52630
|
async function replyDestroy(options) {
|
|
52343
52631
|
if (!isJsonMode()) {
|
|
@@ -52447,20 +52735,33 @@ async function replyDestroy(options) {
|
|
|
52447
52735
|
}
|
|
52448
52736
|
);
|
|
52449
52737
|
}
|
|
52738
|
+
const dnsCleanupByDomain = [];
|
|
52450
52739
|
for (const domain of targets) {
|
|
52451
|
-
await cleanupDomainInfra({
|
|
52740
|
+
const result = await cleanupDomainInfra({
|
|
52741
|
+
domain,
|
|
52742
|
+
region,
|
|
52743
|
+
progress,
|
|
52744
|
+
metadata
|
|
52745
|
+
});
|
|
52746
|
+
dnsCleanupByDomain.push({ domain, result });
|
|
52452
52747
|
}
|
|
52453
52748
|
await saveConnectionMetadata(metadata);
|
|
52454
52749
|
if (isJsonMode()) {
|
|
52455
52750
|
jsonSuccess("email.reply.destroy", {
|
|
52456
52751
|
removed: targets,
|
|
52457
|
-
region
|
|
52752
|
+
region,
|
|
52753
|
+
dnsDeleted: dnsCleanupByDomain.flatMap((d6) => d6.result.deleted),
|
|
52754
|
+
dnsSkipped: dnsCleanupByDomain.flatMap((d6) => d6.result.skipped),
|
|
52755
|
+
dnsSupported: dnsCleanupByDomain.some((d6) => d6.result.supported)
|
|
52458
52756
|
});
|
|
52459
52757
|
return;
|
|
52460
52758
|
}
|
|
52461
52759
|
console.log();
|
|
52462
52760
|
clack34.log.success(`Removed reply threading for ${targets.join(", ")}.`);
|
|
52463
52761
|
console.log();
|
|
52762
|
+
for (const { domain, result } of dnsCleanupByDomain) {
|
|
52763
|
+
reportReplyDNSCleanup(result, domain);
|
|
52764
|
+
}
|
|
52464
52765
|
}
|
|
52465
52766
|
async function replyDecode(addressInput, options) {
|
|
52466
52767
|
const progress = new DeploymentProgress();
|