|
@@ -19,6 +19,11 @@ export async function applyDnsSetup(domain, credential, guide) {
|
|
|
const records = (guide.records || []).filter((record) => ['verification', 'dkim', 'spf', 'dmarc', 'sender-a'].includes(record.key));
|
|
const records = (guide.records || []).filter((record) => ['verification', 'dkim', 'spf', 'dmarc', 'sender-a'].includes(record.key));
|
|
|
const results = [];
|
|
const results = [];
|
|
|
for (const record of records) {
|
|
for (const record of records) {
|
|
|
|
|
+ const zoneName = credential.zoneName || '';
|
|
|
|
|
+ if (zoneName && !isHostInZone(record.host, zoneName)) {
|
|
|
|
|
+ results.push(outOfZoneResult(record, zoneName));
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
try {
|
|
try {
|
|
|
const result = await provider.upsert(record, domain);
|
|
const result = await provider.upsert(record, domain);
|
|
|
results.push({ key: record.key, host: record.host, type: record.type, ok: true, detail: result });
|
|
results.push({ key: record.key, host: record.host, type: record.type, ok: true, detail: result });
|
|
@@ -74,7 +79,12 @@ class CloudflareProvider {
|
|
|
await this.deleteExtras(zoneId, existing, match, record);
|
|
await this.deleteExtras(zoneId, existing, match, record);
|
|
|
return 'updated';
|
|
return 'updated';
|
|
|
}
|
|
}
|
|
|
- await this.request(`/zones/${zoneId}/dns_records`, { method: 'POST', body: payload });
|
|
|
|
|
|
|
+ try {
|
|
|
|
|
+ await this.request(`/zones/${zoneId}/dns_records`, { method: 'POST', body: payload });
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ if (/identical record already exists/i.test(error.message)) return 'unchanged';
|
|
|
|
|
+ throw error;
|
|
|
|
|
+ }
|
|
|
await this.deleteExtras(zoneId, existing, null, record);
|
|
await this.deleteExtras(zoneId, existing, null, record);
|
|
|
return 'created';
|
|
return 'created';
|
|
|
}
|
|
}
|
|
@@ -306,7 +316,35 @@ function relativeName(host, zoneName) {
|
|
|
if (!cleanZone) throw new Error('DNS 凭据缺少 zoneName。');
|
|
if (!cleanZone) throw new Error('DNS 凭据缺少 zoneName。');
|
|
|
if (cleanHost === cleanZone) return '@';
|
|
if (cleanHost === cleanZone) return '@';
|
|
|
if (cleanHost.endsWith(`.${cleanZone}`)) return cleanHost.slice(0, -cleanZone.length - 1) || '@';
|
|
if (cleanHost.endsWith(`.${cleanZone}`)) return cleanHost.slice(0, -cleanZone.length - 1) || '@';
|
|
|
- return cleanHost;
|
|
|
|
|
|
|
+ throw new Error(`记录 ${host} 不在 DNS Zone ${zoneName} 下。`);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function isHostInZone(host, zoneName) {
|
|
|
|
|
+ const cleanHost = String(host || '').replace(/\.$/, '').toLowerCase();
|
|
|
|
|
+ const cleanZone = String(zoneName || '').replace(/\.$/, '').toLowerCase();
|
|
|
|
|
+ return Boolean(cleanHost && cleanZone && (cleanHost === cleanZone || cleanHost.endsWith(`.${cleanZone}`)));
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function outOfZoneResult(record, zoneName) {
|
|
|
|
|
+ const base = {
|
|
|
|
|
+ key: record.key,
|
|
|
|
|
+ host: record.host,
|
|
|
|
|
+ type: record.type
|
|
|
|
|
+ };
|
|
|
|
|
+ if (record.key === 'sender-a' && record.status === 'ok') {
|
|
|
|
|
+ return {
|
|
|
|
|
+ ...base,
|
|
|
|
|
+ ok: true,
|
|
|
|
|
+ skipped: true,
|
|
|
|
|
+ detail: `发信主机不在 ${zoneName} Zone 下,已跳过;当前 A 记录已正确解析。`
|
|
|
|
|
+ };
|
|
|
|
|
+ }
|
|
|
|
|
+ return {
|
|
|
|
|
+ ...base,
|
|
|
|
|
+ ok: false,
|
|
|
|
|
+ skipped: true,
|
|
|
|
|
+ error: `记录 ${record.host} 不在 DNS Zone ${zoneName} 下,请绑定正确的 DNS API 或手动配置。`
|
|
|
|
|
+ };
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
function signAliyun(params, accessKeySecret) {
|
|
function signAliyun(params, accessKeySecret) {
|