dkimkit 0.0.0-stage → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +161 -2
- package/dist/src/cli.d.ts +2 -0
- package/dist/src/cli.js +398 -0
- package/dist/src/cli.js.map +1 -0
- package/dist/src/dns-host.d.ts +10 -0
- package/dist/src/dns-host.js +31 -0
- package/dist/src/dns-host.js.map +1 -0
- package/dist/src/errors.d.ts +17 -0
- package/dist/src/errors.js +51 -0
- package/dist/src/errors.js.map +1 -0
- package/dist/src/hosted/api.d.ts +107 -0
- package/dist/src/hosted/api.js +919 -0
- package/dist/src/hosted/api.js.map +1 -0
- package/dist/src/hosted/auth.d.ts +80 -0
- package/dist/src/hosted/auth.js +147 -0
- package/dist/src/hosted/auth.js.map +1 -0
- package/dist/src/hosted/billing-dodo.d.ts +58 -0
- package/dist/src/hosted/billing-dodo.js +139 -0
- package/dist/src/hosted/billing-dodo.js.map +1 -0
- package/dist/src/hosted/billing-stripe.d.ts +82 -0
- package/dist/src/hosted/billing-stripe.js +205 -0
- package/dist/src/hosted/billing-stripe.js.map +1 -0
- package/dist/src/hosted/billing.d.ts +114 -0
- package/dist/src/hosted/billing.js +173 -0
- package/dist/src/hosted/billing.js.map +1 -0
- package/dist/src/hosted/bulk.d.ts +47 -0
- package/dist/src/hosted/bulk.js +66 -0
- package/dist/src/hosted/bulk.js.map +1 -0
- package/dist/src/hosted/cli.d.ts +2 -0
- package/dist/src/hosted/cli.js +318 -0
- package/dist/src/hosted/cli.js.map +1 -0
- package/dist/src/hosted/crypto.d.ts +22 -0
- package/dist/src/hosted/crypto.js +55 -0
- package/dist/src/hosted/crypto.js.map +1 -0
- package/dist/src/hosted/index.d.ts +7 -0
- package/dist/src/hosted/index.js +11 -0
- package/dist/src/hosted/index.js.map +1 -0
- package/dist/src/hosted/markdown.d.ts +16 -0
- package/dist/src/hosted/markdown.js +168 -0
- package/dist/src/hosted/markdown.js.map +1 -0
- package/dist/src/hosted/net.d.ts +9 -0
- package/dist/src/hosted/net.js +60 -0
- package/dist/src/hosted/net.js.map +1 -0
- package/dist/src/hosted/postgres-store.d.ts +62 -0
- package/dist/src/hosted/postgres-store.js +387 -0
- package/dist/src/hosted/postgres-store.js.map +1 -0
- package/dist/src/hosted/serialize.d.ts +144 -0
- package/dist/src/hosted/serialize.js +126 -0
- package/dist/src/hosted/serialize.js.map +1 -0
- package/dist/src/hosted/service.d.ts +170 -0
- package/dist/src/hosted/service.js +603 -0
- package/dist/src/hosted/service.js.map +1 -0
- package/dist/src/hosted/site.d.ts +84 -0
- package/dist/src/hosted/site.js +653 -0
- package/dist/src/hosted/site.js.map +1 -0
- package/dist/src/hosted/sqlite-store.d.ts +45 -0
- package/dist/src/hosted/sqlite-store.js +351 -0
- package/dist/src/hosted/sqlite-store.js.map +1 -0
- package/dist/src/hosted/store.d.ts +257 -0
- package/dist/src/hosted/store.js +181 -0
- package/dist/src/hosted/store.js.map +1 -0
- package/dist/src/hosted/widget.d.ts +46 -0
- package/dist/src/hosted/widget.js +130 -0
- package/dist/src/hosted/widget.js.map +1 -0
- package/dist/src/hosted/worker.d.ts +45 -0
- package/dist/src/hosted/worker.js +142 -0
- package/dist/src/hosted/worker.js.map +1 -0
- package/dist/src/http.d.ts +14 -0
- package/dist/src/http.js +54 -0
- package/dist/src/http.js.map +1 -0
- package/dist/src/index.d.ts +19 -0
- package/dist/src/index.js +19 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/orchestrator.d.ts +70 -0
- package/dist/src/orchestrator.js +69 -0
- package/dist/src/orchestrator.js.map +1 -0
- package/dist/src/propagation.d.ts +30 -0
- package/dist/src/propagation.js +76 -0
- package/dist/src/propagation.js.map +1 -0
- package/dist/src/providers/dns/aws.d.ts +28 -0
- package/dist/src/providers/dns/aws.js +103 -0
- package/dist/src/providers/dns/aws.js.map +1 -0
- package/dist/src/providers/dns/cloudflare.d.ts +26 -0
- package/dist/src/providers/dns/cloudflare.js +97 -0
- package/dist/src/providers/dns/cloudflare.js.map +1 -0
- package/dist/src/providers/dns/manual.d.ts +12 -0
- package/dist/src/providers/dns/manual.js +18 -0
- package/dist/src/providers/dns/manual.js.map +1 -0
- package/dist/src/providers/dns/route53.d.ts +34 -0
- package/dist/src/providers/dns/route53.js +176 -0
- package/dist/src/providers/dns/route53.js.map +1 -0
- package/dist/src/providers/email/factory.d.ts +29 -0
- package/dist/src/providers/email/factory.js +67 -0
- package/dist/src/providers/email/factory.js.map +1 -0
- package/dist/src/providers/email/mailgun.d.ts +36 -0
- package/dist/src/providers/email/mailgun.js +141 -0
- package/dist/src/providers/email/mailgun.js.map +1 -0
- package/dist/src/providers/email/postmark.d.ts +37 -0
- package/dist/src/providers/email/postmark.js +121 -0
- package/dist/src/providers/email/postmark.js.map +1 -0
- package/dist/src/providers/email/sendgrid.d.ts +31 -0
- package/dist/src/providers/email/sendgrid.js +109 -0
- package/dist/src/providers/email/sendgrid.js.map +1 -0
- package/dist/src/providers/email/ses.d.ts +39 -0
- package/dist/src/providers/email/ses.js +123 -0
- package/dist/src/providers/email/ses.js.map +1 -0
- package/dist/src/providers/factory.d.ts +14 -0
- package/dist/src/providers/factory.js +32 -0
- package/dist/src/providers/factory.js.map +1 -0
- package/dist/src/records.d.ts +35 -0
- package/dist/src/records.js +109 -0
- package/dist/src/records.js.map +1 -0
- package/dist/src/server.d.ts +67 -0
- package/dist/src/server.js +301 -0
- package/dist/src/server.js.map +1 -0
- package/dist/src/types.d.ts +75 -0
- package/dist/src/types.js +2 -0
- package/dist/src/types.js.map +1 -0
- package/dist/src/xml.d.ts +6 -0
- package/dist/src/xml.js +23 -0
- package/dist/src/xml.js.map +1 -0
- package/docs/billing.md +43 -0
- package/docs/getting-started.md +66 -0
- package/docs/hosted-api.md +192 -0
- package/docs/library.md +253 -0
- package/docs/self-hosting.md +153 -0
- package/docs/widget.md +121 -0
- package/package.json +63 -4
- package/web/dashboard.html +669 -0
- package/web/index.html +697 -0
- package/web/widget-frame.html +480 -0
- package/web/widget.js +91 -0
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { ApiError, ConfigError } from '../../errors.js';
|
|
2
|
+
import { requestText } from '../../http.js';
|
|
3
|
+
import { normalizeName } from '../../records.js';
|
|
4
|
+
import { signRequest } from '../dns/aws.js';
|
|
5
|
+
const INBOUND_UNSUPPORTED = 'Receiving email with Amazon SES isn’t supported yet. Use SendGrid, Postmark or Mailgun for inbound mail.';
|
|
6
|
+
/**
|
|
7
|
+
* Amazon SES (API v2) domain identities with Easy DKIM (three CNAMEs) and a
|
|
8
|
+
* custom MAIL FROM subdomain (an MX for bounces and an SPF TXT). SES checks DNS
|
|
9
|
+
* by itself, so "validate" reads the current status.
|
|
10
|
+
*/
|
|
11
|
+
export class SesProvider {
|
|
12
|
+
opts;
|
|
13
|
+
name = 'ses';
|
|
14
|
+
label = 'Amazon SES';
|
|
15
|
+
supportsInbound = false;
|
|
16
|
+
baseUrl;
|
|
17
|
+
fetchImpl;
|
|
18
|
+
constructor(opts) {
|
|
19
|
+
this.opts = opts;
|
|
20
|
+
if (!/^[a-z]{2}(-[a-z]+)+-\d$/.test(opts.region))
|
|
21
|
+
throw new ConfigError(`"${opts.region}" isn’t an AWS region (e.g. us-east-1).`);
|
|
22
|
+
this.baseUrl = opts.baseUrl ?? `https://email.${opts.region}.amazonaws.com`;
|
|
23
|
+
this.fetchImpl = opts.fetch ?? fetch;
|
|
24
|
+
}
|
|
25
|
+
defaultInboundHostname(domain) {
|
|
26
|
+
return `parse.${normalizeName(domain)}`;
|
|
27
|
+
}
|
|
28
|
+
async findDomain(domain) {
|
|
29
|
+
const name = normalizeName(domain);
|
|
30
|
+
try {
|
|
31
|
+
return this.toAuthentication(name, await this.identity(name), false);
|
|
32
|
+
}
|
|
33
|
+
catch (err) {
|
|
34
|
+
if (err instanceof ApiError && err.status === 404)
|
|
35
|
+
return null;
|
|
36
|
+
throw err;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/** `subdomain` is the MAIL FROM label (default "mail", giving mail.<domain>). */
|
|
40
|
+
async ensureDomain(domain, opts = {}) {
|
|
41
|
+
const existing = await this.findDomain(domain);
|
|
42
|
+
if (existing)
|
|
43
|
+
return existing;
|
|
44
|
+
const name = normalizeName(domain);
|
|
45
|
+
await this.request('POST', '/v2/email/identities', { EmailIdentity: name });
|
|
46
|
+
await this.request('PUT', `/v2/email/identities/${encodeURIComponent(name)}/mail-from`, {
|
|
47
|
+
MailFromDomain: `${opts.subdomain ?? 'mail'}.${name}`,
|
|
48
|
+
// Until the MX is in place, SES falls back to its own MAIL FROM rather than rejecting mail.
|
|
49
|
+
BehaviorOnMxFailure: 'USE_DEFAULT_VALUE',
|
|
50
|
+
});
|
|
51
|
+
return this.toAuthentication(name, await this.identity(name), true);
|
|
52
|
+
}
|
|
53
|
+
async getDomain(id) {
|
|
54
|
+
return this.toAuthentication(id, await this.identity(id), false);
|
|
55
|
+
}
|
|
56
|
+
async deleteDomain(id) {
|
|
57
|
+
await this.request('DELETE', `/v2/email/identities/${encodeURIComponent(id)}`);
|
|
58
|
+
}
|
|
59
|
+
async validateDomain(id) {
|
|
60
|
+
const identity = await this.identity(id);
|
|
61
|
+
const dkimStatus = identity.DkimAttributes?.Status ?? 'NOT_STARTED';
|
|
62
|
+
const dkim = dkimStatus === 'SUCCESS';
|
|
63
|
+
const results = [{ record: 'dkim', valid: dkim, ...(dkim ? {} : { reason: `DKIM status: ${dkimStatus.toLowerCase().replace(/_/g, ' ')}` }) }];
|
|
64
|
+
const mailFrom = identity.MailFromAttributes;
|
|
65
|
+
if (mailFrom?.MailFromDomain) {
|
|
66
|
+
const ok = mailFrom.MailFromDomainStatus === 'SUCCESS';
|
|
67
|
+
const status = (mailFrom.MailFromDomainStatus ?? 'PENDING').toLowerCase().replace(/_/g, ' ');
|
|
68
|
+
results.push({ record: 'mail_from', valid: ok, ...(ok ? {} : { reason: `MAIL FROM status: ${status}` }) });
|
|
69
|
+
}
|
|
70
|
+
// Sending is allowed once the identity (via DKIM) verifies; MAIL FROM falls back until its MX is found.
|
|
71
|
+
return { valid: Boolean(identity.VerifiedForSendingStatus) && dkim, results };
|
|
72
|
+
}
|
|
73
|
+
inboundRecords() {
|
|
74
|
+
throw new ConfigError(INBOUND_UNSUPPORTED);
|
|
75
|
+
}
|
|
76
|
+
async ensureInboundParse() {
|
|
77
|
+
throw new ConfigError(INBOUND_UNSUPPORTED);
|
|
78
|
+
}
|
|
79
|
+
async deleteInboundParse() { }
|
|
80
|
+
identity(name) {
|
|
81
|
+
return this.request('GET', `/v2/email/identities/${encodeURIComponent(name)}`);
|
|
82
|
+
}
|
|
83
|
+
toAuthentication(domain, identity, created) {
|
|
84
|
+
const records = (identity.DkimAttributes?.Tokens ?? []).map((token, i) => ({
|
|
85
|
+
type: 'CNAME',
|
|
86
|
+
name: `${token}._domainkey.${domain}`,
|
|
87
|
+
value: `${token}.dkim.amazonses.com`,
|
|
88
|
+
purpose: `dkim${i + 1}`,
|
|
89
|
+
}));
|
|
90
|
+
const mailFrom = identity.MailFromAttributes?.MailFromDomain;
|
|
91
|
+
if (mailFrom) {
|
|
92
|
+
const host = normalizeName(mailFrom);
|
|
93
|
+
records.push({ type: 'MX', name: host, value: `feedback-smtp.${this.opts.region}.amazonses.com`, priority: 10, purpose: 'mail_from_mx' });
|
|
94
|
+
records.push({
|
|
95
|
+
type: 'TXT',
|
|
96
|
+
name: host,
|
|
97
|
+
value: 'v=spf1 include:amazonses.com ~all',
|
|
98
|
+
purpose: 'mail_from_spf',
|
|
99
|
+
note: 'If this name already has an SPF record (v=spf1 …), add include:amazonses.com to it instead of creating a second one.',
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
return { id: domain, domain, valid: Boolean(identity.VerifiedForSendingStatus), records, created };
|
|
103
|
+
}
|
|
104
|
+
async request(method, path, body) {
|
|
105
|
+
const url = this.baseUrl + path;
|
|
106
|
+
const payload = body === undefined ? undefined : JSON.stringify(body);
|
|
107
|
+
const text = await requestText(this.fetchImpl, 'Amazon SES', url, () => ({
|
|
108
|
+
method,
|
|
109
|
+
body: payload,
|
|
110
|
+
headers: signRequest({ method, url, body: payload, headers: payload ? { 'content-type': 'application/json' } : {} }, this.opts.credentials, this.opts.region, 'ses'),
|
|
111
|
+
}), (t) => {
|
|
112
|
+
try {
|
|
113
|
+
const b = JSON.parse(t);
|
|
114
|
+
return b.message ?? b.Message;
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
return undefined;
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
return (text ? JSON.parse(text) : {});
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
//# sourceMappingURL=ses.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ses.js","sourceRoot":"","sources":["../../../../src/providers/email/ses.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { DnsProvider, DnsRecord } from '../types.js';
|
|
2
|
+
import { type AwsCredentials } from './dns/aws.js';
|
|
3
|
+
export declare const DNS_PROVIDERS: readonly ["cloudflare", "route53", "manual"];
|
|
4
|
+
export type DnsProviderName = (typeof DNS_PROVIDERS)[number];
|
|
5
|
+
export interface DnsProviderConfig {
|
|
6
|
+
cloudflareApiToken?: string;
|
|
7
|
+
/** Explicit AWS keys; otherwise environment or shared credentials file. */
|
|
8
|
+
aws?: Partial<AwsCredentials>;
|
|
9
|
+
awsProfile?: string;
|
|
10
|
+
route53HostedZoneId?: string;
|
|
11
|
+
onManualRecords?: (records: DnsRecord[]) => Promise<void>;
|
|
12
|
+
env?: NodeJS.ProcessEnv;
|
|
13
|
+
}
|
|
14
|
+
export declare function createDnsProvider(name: string, config?: DnsProviderConfig): DnsProvider;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { ConfigError } from '../errors.js';
|
|
2
|
+
import { loadAwsCredentials } from './dns/aws.js';
|
|
3
|
+
import { CloudflareDns } from './dns/cloudflare.js';
|
|
4
|
+
import { ManualDns } from './dns/manual.js';
|
|
5
|
+
import { Route53Dns } from './dns/route53.js';
|
|
6
|
+
export const DNS_PROVIDERS = ['cloudflare', 'route53', 'manual'];
|
|
7
|
+
export function createDnsProvider(name, config = {}) {
|
|
8
|
+
const env = config.env ?? process.env;
|
|
9
|
+
switch (name) {
|
|
10
|
+
case 'cloudflare': {
|
|
11
|
+
const apiToken = config.cloudflareApiToken || env.CLOUDFLARE_API_TOKEN;
|
|
12
|
+
if (!apiToken)
|
|
13
|
+
throw new ConfigError('Cloudflare needs an API token (CLOUDFLARE_API_TOKEN).');
|
|
14
|
+
return new CloudflareDns({ apiToken });
|
|
15
|
+
}
|
|
16
|
+
case 'route53': {
|
|
17
|
+
const { accessKeyId, secretAccessKey, sessionToken } = config.aws ?? {};
|
|
18
|
+
if (Boolean(accessKeyId) !== Boolean(secretAccessKey)) {
|
|
19
|
+
throw new ConfigError('Route53 needs both an AWS access key id and a secret access key.');
|
|
20
|
+
}
|
|
21
|
+
const credentials = accessKeyId && secretAccessKey
|
|
22
|
+
? { accessKeyId, secretAccessKey, ...(sessionToken ? { sessionToken } : {}) }
|
|
23
|
+
: () => loadAwsCredentials({ profile: config.awsProfile || undefined, env });
|
|
24
|
+
return new Route53Dns({ credentials, hostedZoneId: config.route53HostedZoneId || undefined });
|
|
25
|
+
}
|
|
26
|
+
case 'manual':
|
|
27
|
+
return new ManualDns(config.onManualRecords ?? (async () => { }));
|
|
28
|
+
default:
|
|
29
|
+
throw new ConfigError(`Unknown DNS provider "${name}". Use one of: ${DNS_PROVIDERS.join(', ')}.`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=factory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"factory.js","sourceRoot":"","sources":["../../../src/providers/factory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAE3C,OAAO,EAAE,kBAAkB,EAAuB,MAAM,cAAc,CAAC;AACvE,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAE9C,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,YAAY,EAAE,SAAS,EAAE,QAAQ,CAAU,CAAC;AAa1E,MAAM,UAAU,iBAAiB,CAAC,IAAY,EAAE,SAA4B,EAAE;IAC5E,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC;IACtC,QAAQ,IAAI,EAAE,CAAC;QACb,KAAK,YAAY,CAAC,CAAC,CAAC;YAClB,MAAM,QAAQ,GAAG,MAAM,CAAC,kBAAkB,IAAI,GAAG,CAAC,oBAAoB,CAAC;YACvE,IAAI,CAAC,QAAQ;gBAAE,MAAM,IAAI,WAAW,CAAC,uDAAuD,CAAC,CAAC;YAC9F,OAAO,IAAI,aAAa,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC;QACzC,CAAC;QACD,KAAK,SAAS,CAAC,CAAC,CAAC;YACf,MAAM,EAAE,WAAW,EAAE,eAAe,EAAE,YAAY,EAAE,GAAG,MAAM,CAAC,GAAG,IAAI,EAAE,CAAC;YACxE,IAAI,OAAO,CAAC,WAAW,CAAC,KAAK,OAAO,CAAC,eAAe,CAAC,EAAE,CAAC;gBACtD,MAAM,IAAI,WAAW,CAAC,kEAAkE,CAAC,CAAC;YAC5F,CAAC;YACD,MAAM,WAAW,GACf,WAAW,IAAI,eAAe;gBAC5B,CAAC,CAAC,EAAE,WAAW,EAAE,eAAe,EAAE,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE;gBAC7E,CAAC,CAAC,GAAG,EAAE,CAAC,kBAAkB,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,UAAU,IAAI,SAAS,EAAE,GAAG,EAAE,CAAC,CAAC;YACjF,OAAO,IAAI,UAAU,CAAC,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,CAAC,mBAAmB,IAAI,SAAS,EAAE,CAAC,CAAC;QAChG,CAAC;QACD,KAAK,QAAQ;YACX,OAAO,IAAI,SAAS,CAAC,MAAM,CAAC,eAAe,IAAI,CAAC,KAAK,IAAI,EAAE,GAAE,CAAC,CAAC,CAAC,CAAC;QACnE;YACE,MAAM,IAAI,WAAW,CAAC,yBAAyB,IAAI,kBAAkB,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACtG,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { DnsRecord, PlannedChange, RecordType } from './types.js';
|
|
2
|
+
export declare function isRecordType(type: string): type is RecordType;
|
|
3
|
+
/** A normalized hostname with at least two labels (IDNs must be punycode). */
|
|
4
|
+
export declare function isValidDomain(name: string): boolean;
|
|
5
|
+
export declare function normalizeName(name: string): string;
|
|
6
|
+
/** Hostnames compare case-insensitively without trailing dots; TXT values compare with quoting and chunking removed. */
|
|
7
|
+
export declare function normalizeValue(type: RecordType, value: string): string;
|
|
8
|
+
export declare function sameValue(type: RecordType, a: string, b: string): boolean;
|
|
9
|
+
/** A record already present at the DNS host, in provider-neutral form. */
|
|
10
|
+
export interface ExistingRecord {
|
|
11
|
+
id: string;
|
|
12
|
+
type: string;
|
|
13
|
+
value: string;
|
|
14
|
+
priority?: number;
|
|
15
|
+
proxied?: boolean;
|
|
16
|
+
}
|
|
17
|
+
export declare function isSpf(value: string): boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Adds the mechanisms of `desired` (e.g. "include:mailgun.org") to an existing
|
|
20
|
+
* SPF record, before its `all`/`redirect` term. Returns null when the existing
|
|
21
|
+
* record already has them all. A name must have exactly one SPF record, so
|
|
22
|
+
* providers' SPF records are merged in rather than added next to the old one.
|
|
23
|
+
*/
|
|
24
|
+
export declare function mergeSpf(existing: string, desired: string): {
|
|
25
|
+
value: string;
|
|
26
|
+
added: string[];
|
|
27
|
+
} | null;
|
|
28
|
+
/**
|
|
29
|
+
* Decides what to do with `record` given the records already at its name.
|
|
30
|
+
* Never replaces a record pointing elsewhere unless `overwrite` is set, and never
|
|
31
|
+
* deletes records of other types. `desired` is every record being planned, so
|
|
32
|
+
* a provider's own multi-value sets (e.g. two MX hosts) don't conflict with each other.
|
|
33
|
+
*/
|
|
34
|
+
export declare function decideChange(record: DnsRecord, existing: ExistingRecord[], overwrite?: boolean, desired?: DnsRecord[]): PlannedChange;
|
|
35
|
+
export declare function describeRecord(record: DnsRecord): string;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
const RECORD_TYPES = ['CNAME', 'TXT', 'MX'];
|
|
2
|
+
export function isRecordType(type) {
|
|
3
|
+
return RECORD_TYPES.includes(type);
|
|
4
|
+
}
|
|
5
|
+
const DOMAIN_RE = /^(?!-)[a-z0-9-]{1,63}(?<!-)(\.(?!-)[a-z0-9-]{1,63}(?<!-))+$/;
|
|
6
|
+
/** A normalized hostname with at least two labels (IDNs must be punycode). */
|
|
7
|
+
export function isValidDomain(name) {
|
|
8
|
+
return name.length <= 253 && DOMAIN_RE.test(name);
|
|
9
|
+
}
|
|
10
|
+
export function normalizeName(name) {
|
|
11
|
+
return name.trim().toLowerCase().replace(/\.+$/, '');
|
|
12
|
+
}
|
|
13
|
+
/** Hostnames compare case-insensitively without trailing dots; TXT values compare with quoting and chunking removed. */
|
|
14
|
+
export function normalizeValue(type, value) {
|
|
15
|
+
if (type === 'TXT') {
|
|
16
|
+
return value.trim().replace(/^"(.*)"$/s, '$1').replace(/"\s*"/g, '');
|
|
17
|
+
}
|
|
18
|
+
return normalizeName(value);
|
|
19
|
+
}
|
|
20
|
+
export function sameValue(type, a, b) {
|
|
21
|
+
return normalizeValue(type, a) === normalizeValue(type, b);
|
|
22
|
+
}
|
|
23
|
+
const SPF_TERMINATOR = /^([~+?-]?all|redirect=.*)$/i;
|
|
24
|
+
export function isSpf(value) {
|
|
25
|
+
return /^v=spf1(\s|$)/i.test(normalizeValue('TXT', value));
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Adds the mechanisms of `desired` (e.g. "include:mailgun.org") to an existing
|
|
29
|
+
* SPF record, before its `all`/`redirect` term. Returns null when the existing
|
|
30
|
+
* record already has them all. A name must have exactly one SPF record, so
|
|
31
|
+
* providers' SPF records are merged in rather than added next to the old one.
|
|
32
|
+
*/
|
|
33
|
+
export function mergeSpf(existing, desired) {
|
|
34
|
+
const have = normalizeValue('TXT', existing).split(/\s+/).filter(Boolean);
|
|
35
|
+
const want = normalizeValue('TXT', desired)
|
|
36
|
+
.split(/\s+/)
|
|
37
|
+
.filter((t) => t && t.toLowerCase() !== 'v=spf1' && !SPF_TERMINATOR.test(t));
|
|
38
|
+
const added = want.filter((t) => !have.some((h) => h.toLowerCase() === t.toLowerCase()));
|
|
39
|
+
if (added.length === 0)
|
|
40
|
+
return null;
|
|
41
|
+
const end = have.findIndex((t) => SPF_TERMINATOR.test(t));
|
|
42
|
+
const merged = end === -1 ? [...have, ...added] : [...have.slice(0, end), ...added, ...have.slice(end)];
|
|
43
|
+
return { value: merged.join(' '), added };
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Decides what to do with `record` given the records already at its name.
|
|
47
|
+
* Never replaces a record pointing elsewhere unless `overwrite` is set, and never
|
|
48
|
+
* deletes records of other types. `desired` is every record being planned, so
|
|
49
|
+
* a provider's own multi-value sets (e.g. two MX hosts) don't conflict with each other.
|
|
50
|
+
*/
|
|
51
|
+
export function decideChange(record, existing, overwrite = false, desired = []) {
|
|
52
|
+
const conflict = (reason) => ({ record, action: 'conflict', reason });
|
|
53
|
+
const siblings = desired.filter((d) => d !== record && d.name === record.name && d.type === record.type);
|
|
54
|
+
const ours = (e) => siblings.some((s) => sameValue(record.type, s.value, e.value));
|
|
55
|
+
const sameType = existing.filter((e) => e.type === record.type);
|
|
56
|
+
const others = existing.filter((e) => e.type !== record.type);
|
|
57
|
+
if (record.type === 'CNAME' && others.length > 0) {
|
|
58
|
+
const types = [...new Set(others.map((e) => e.type))].join(', ');
|
|
59
|
+
return conflict(`a CNAME cannot coexist with the existing ${types} record(s) at this name`);
|
|
60
|
+
}
|
|
61
|
+
const blockingCname = others.find((e) => e.type === 'CNAME');
|
|
62
|
+
if (blockingCname) {
|
|
63
|
+
return conflict(`an existing CNAME (${blockingCname.value}) blocks other records at this name`);
|
|
64
|
+
}
|
|
65
|
+
const match = sameType.find((e) => sameValue(record.type, e.value, record.value));
|
|
66
|
+
if (match) {
|
|
67
|
+
if (match.proxied) {
|
|
68
|
+
return { record, action: 'update', existingId: match.id, reason: 'disable proxying' };
|
|
69
|
+
}
|
|
70
|
+
if (record.type === 'MX' && record.priority !== undefined && match.priority !== record.priority) {
|
|
71
|
+
return { record, action: 'update', existingId: match.id, reason: `priority ${match.priority} → ${record.priority}` };
|
|
72
|
+
}
|
|
73
|
+
return { record, action: 'noop' };
|
|
74
|
+
}
|
|
75
|
+
if (record.type === 'TXT') {
|
|
76
|
+
// SPF: merge into the existing record rather than adding a second one.
|
|
77
|
+
if (isSpf(record.value)) {
|
|
78
|
+
const spf = sameType.filter((e) => isSpf(e.value));
|
|
79
|
+
if (spf.length > 1)
|
|
80
|
+
return conflict('the name already has more than one SPF record, which makes SPF fail; reduce it to one first');
|
|
81
|
+
if (spf.length === 1) {
|
|
82
|
+
const merged = mergeSpf(spf[0].value, record.value);
|
|
83
|
+
if (!merged)
|
|
84
|
+
return { record: { ...record, value: normalizeValue('TXT', spf[0].value) }, action: 'noop' };
|
|
85
|
+
return {
|
|
86
|
+
record: { ...record, value: merged.value },
|
|
87
|
+
action: 'update',
|
|
88
|
+
existingId: spf[0].id,
|
|
89
|
+
reason: `add ${merged.added.join(' ')} to the existing SPF record`,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
// Other TXT records can sit side by side.
|
|
94
|
+
return { record, action: 'create' };
|
|
95
|
+
}
|
|
96
|
+
const foreign = sameType.filter((e) => !ours(e));
|
|
97
|
+
if (foreign.length === 0)
|
|
98
|
+
return { record, action: 'create' };
|
|
99
|
+
const current = foreign.map((e) => e.value).join(', ');
|
|
100
|
+
if (overwrite && foreign.length === 1) {
|
|
101
|
+
return { record, action: 'update', existingId: foreign[0].id, reason: `replace ${current}` };
|
|
102
|
+
}
|
|
103
|
+
return conflict(`existing ${record.type} points to ${current}; use overwrite to replace it`);
|
|
104
|
+
}
|
|
105
|
+
export function describeRecord(record) {
|
|
106
|
+
const priority = record.priority !== undefined ? `${record.priority} ` : '';
|
|
107
|
+
return `${record.type} ${record.name} → ${priority}${record.value}`;
|
|
108
|
+
}
|
|
109
|
+
//# sourceMappingURL=records.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"records.js","sourceRoot":"","sources":["../../src/records.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { type SetupOptions } from './orchestrator.js';
|
|
2
|
+
import type { DnsProvider, EmailProvider } from './types.js';
|
|
3
|
+
/** Body of /api/plan, /api/setup and /api/status. Secrets fall back to environment variables. */
|
|
4
|
+
export interface SetupRequest {
|
|
5
|
+
domain: string;
|
|
6
|
+
subdomain?: string;
|
|
7
|
+
dns: string;
|
|
8
|
+
/** sendgrid | postmark | mailgun | ses (default sendgrid) */
|
|
9
|
+
emailProvider?: string;
|
|
10
|
+
onBehalfOf?: string;
|
|
11
|
+
credentials?: {
|
|
12
|
+
sendgridApiKey?: string;
|
|
13
|
+
postmarkAccountToken?: string;
|
|
14
|
+
postmarkInboundServerId?: string;
|
|
15
|
+
mailgunApiKey?: string;
|
|
16
|
+
mailgunRegion?: string;
|
|
17
|
+
/** Amazon SES uses the AWS fields below (or the AWS environment) plus this region. */
|
|
18
|
+
sesRegion?: string;
|
|
19
|
+
cloudflareApiToken?: string;
|
|
20
|
+
awsAccessKeyId?: string;
|
|
21
|
+
awsSecretAccessKey?: string;
|
|
22
|
+
awsSessionToken?: string;
|
|
23
|
+
awsProfile?: string;
|
|
24
|
+
route53HostedZoneId?: string;
|
|
25
|
+
};
|
|
26
|
+
inbound?: {
|
|
27
|
+
url: string;
|
|
28
|
+
hostname?: string;
|
|
29
|
+
spamCheck?: boolean;
|
|
30
|
+
sendRaw?: boolean;
|
|
31
|
+
};
|
|
32
|
+
overwrite?: boolean;
|
|
33
|
+
waitSeconds?: number;
|
|
34
|
+
}
|
|
35
|
+
export interface UiServerOptions {
|
|
36
|
+
/** Default 4545; 0 picks a free port. */
|
|
37
|
+
port?: number;
|
|
38
|
+
token?: string;
|
|
39
|
+
env?: NodeJS.ProcessEnv;
|
|
40
|
+
/** Override provider construction (custom providers, tests). */
|
|
41
|
+
providers?: (req: SetupRequest) => {
|
|
42
|
+
email: EmailProvider;
|
|
43
|
+
dns: DnsProvider;
|
|
44
|
+
} | Promise<{
|
|
45
|
+
email: EmailProvider;
|
|
46
|
+
dns: DnsProvider;
|
|
47
|
+
}>;
|
|
48
|
+
/** Merged into every setup run. */
|
|
49
|
+
setupDefaults?: Partial<SetupOptions>;
|
|
50
|
+
version?: string;
|
|
51
|
+
}
|
|
52
|
+
export interface UiServer {
|
|
53
|
+
/** Open this once; it exchanges the token for a cookie. */
|
|
54
|
+
url: string;
|
|
55
|
+
port: number;
|
|
56
|
+
close(): Promise<void>;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Local web UI. Binds to 127.0.0.1 only. A random token in the first URL is
|
|
60
|
+
* swapped for a SameSite=Strict cookie; the Host header is checked to block
|
|
61
|
+
* DNS rebinding; API calls must be JSON POSTs, which cross-origin pages can't
|
|
62
|
+
* send without a CORS preflight this server never approves.
|
|
63
|
+
*/
|
|
64
|
+
export declare function startUiServer(opts?: UiServerOptions): Promise<UiServer>;
|
|
65
|
+
export declare function parseSetupRequest(input: unknown, { requireDns }?: {
|
|
66
|
+
requireDns?: boolean | undefined;
|
|
67
|
+
}): SetupRequest;
|