@rapidmx/web-client 0.37.1 → 0.39.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/apps/admin/configuration-encryption/index.tsx +17 -0
- package/apps/admin/domains/[uid].tsx +17 -4
- package/apps/admin/integrations/dns-providers/[uid].tsx +205 -0
- package/apps/admin/integrations/dns-providers/index.tsx +164 -0
- package/apps/admin/integrations/dns-providers/new/index.tsx +55 -0
- package/apps/admin/integrations/dns-resolver/index.tsx +16 -0
- package/apps/admin/integrations/giphy/index.tsx +16 -0
- package/apps/admin/integrations/index.tsx +31 -0
- package/apps/admin/integrations/plugin-registry/index.tsx +16 -0
- package/apps/admin/integrations/signing-ca/index.tsx +16 -0
- package/apps/admin/integrations/stun/index.tsx +16 -0
- package/apps/shared/components/admin/configEncryption/ConfigKeysManager.tsx +529 -0
- package/apps/shared/components/admin/configEncryption/keyringParts.ts +37 -0
- package/apps/shared/components/admin/diagnostics/DiagnosticsManager.tsx +20 -4
- package/apps/shared/components/admin/diagnostics/InformationPanel.tsx +23 -6
- package/apps/shared/components/admin/diagnostics/KeyringCard.tsx +144 -0
- package/apps/shared/components/admin/diagnostics/SettingsTable.tsx +21 -6
- package/apps/shared/components/admin/diagnostics/diagnosticsApi.ts +15 -3
- package/apps/shared/components/admin/integrations/DnsProviderForm.tsx +354 -0
- package/apps/shared/components/admin/integrations/DnsProviderHosts.tsx +197 -0
- package/apps/shared/components/admin/integrations/DnsProviderOperations.tsx +115 -0
- package/apps/shared/components/admin/integrations/DnsResolverForm.tsx +61 -0
- package/apps/shared/components/admin/integrations/GiphyForm.tsx +59 -0
- package/apps/shared/components/admin/integrations/IntegrationSectionForm.tsx +234 -0
- package/apps/shared/components/admin/integrations/IntegrationSectionPage.tsx +40 -0
- package/apps/shared/components/admin/integrations/IntegrationsLayout.tsx +72 -0
- package/apps/shared/components/admin/integrations/PluginRegistryForm.tsx +230 -0
- package/apps/shared/components/admin/integrations/SecretField.tsx +91 -0
- package/apps/shared/components/admin/integrations/SigningCaForm.tsx +95 -0
- package/apps/shared/components/admin/integrations/StunForm.tsx +63 -0
- package/apps/shared/components/admin/integrations/dnsProviderParts.tsx +72 -0
- package/apps/shared/components/admin/layout/AdminShell.tsx +12 -1
- package/apps/shared/components/admin/settings/CopyableValue.tsx +16 -0
- package/apps/shared/components/admin/settings/DomainApexTxt.tsx +319 -0
- package/apps/shared/components/admin/settings/DomainDkimRotation.tsx +408 -0
- package/apps/shared/components/admin/settings/DomainDnsProvider.tsx +147 -0
- package/apps/shared/components/admin/settings/DomainDnsSetup.tsx +174 -17
- package/apps/shared/components/admin/setup/SetupWizard.tsx +39 -6
- package/dist/apps/admin/configuration-encryption/index.d.ts +4 -0
- package/dist/apps/admin/configuration-encryption/index.js +7 -0
- package/dist/apps/admin/domains/[uid].js +11 -4
- package/dist/apps/admin/integrations/dns-providers/[uid].d.ts +7 -0
- package/dist/apps/admin/integrations/dns-providers/[uid].js +117 -0
- package/dist/apps/admin/integrations/dns-providers/index.d.ts +3 -0
- package/dist/apps/admin/integrations/dns-providers/index.js +68 -0
- package/dist/apps/admin/integrations/dns-providers/new/index.d.ts +3 -0
- package/dist/apps/admin/integrations/dns-providers/new/index.js +31 -0
- package/dist/apps/admin/integrations/dns-resolver/index.d.ts +3 -0
- package/dist/apps/admin/integrations/dns-resolver/index.js +6 -0
- package/dist/apps/admin/integrations/giphy/index.d.ts +3 -0
- package/dist/apps/admin/integrations/giphy/index.js +6 -0
- package/dist/apps/admin/integrations/index.d.ts +4 -0
- package/dist/apps/admin/integrations/index.js +7 -0
- package/dist/apps/admin/integrations/plugin-registry/index.d.ts +3 -0
- package/dist/apps/admin/integrations/plugin-registry/index.js +6 -0
- package/dist/apps/admin/integrations/signing-ca/index.d.ts +3 -0
- package/dist/apps/admin/integrations/signing-ca/index.js +6 -0
- package/dist/apps/admin/integrations/stun/index.d.ts +3 -0
- package/dist/apps/admin/integrations/stun/index.js +6 -0
- package/dist/apps/shared/components/admin/configEncryption/ConfigKeysManager.d.ts +9 -0
- package/dist/apps/shared/components/admin/configEncryption/ConfigKeysManager.js +187 -0
- package/dist/apps/shared/components/admin/configEncryption/keyringParts.d.ts +10 -0
- package/dist/apps/shared/components/admin/configEncryption/keyringParts.js +26 -0
- package/dist/apps/shared/components/admin/diagnostics/DiagnosticsManager.js +20 -4
- package/dist/apps/shared/components/admin/diagnostics/InformationPanel.d.ts +8 -4
- package/dist/apps/shared/components/admin/diagnostics/InformationPanel.js +7 -5
- package/dist/apps/shared/components/admin/diagnostics/KeyringCard.d.ts +18 -0
- package/dist/apps/shared/components/admin/diagnostics/KeyringCard.js +51 -0
- package/dist/apps/shared/components/admin/diagnostics/SettingsTable.d.ts +9 -1
- package/dist/apps/shared/components/admin/diagnostics/SettingsTable.js +10 -3
- package/dist/apps/shared/components/admin/diagnostics/diagnosticsApi.d.ts +13 -3
- package/dist/apps/shared/components/admin/integrations/DnsProviderForm.d.ts +17 -0
- package/dist/apps/shared/components/admin/integrations/DnsProviderForm.js +147 -0
- package/dist/apps/shared/components/admin/integrations/DnsProviderHosts.d.ts +10 -0
- package/dist/apps/shared/components/admin/integrations/DnsProviderHosts.js +101 -0
- package/dist/apps/shared/components/admin/integrations/DnsProviderOperations.d.ts +8 -0
- package/dist/apps/shared/components/admin/integrations/DnsProviderOperations.js +60 -0
- package/dist/apps/shared/components/admin/integrations/DnsResolverForm.d.ts +7 -0
- package/dist/apps/shared/components/admin/integrations/DnsResolverForm.js +15 -0
- package/dist/apps/shared/components/admin/integrations/GiphyForm.d.ts +7 -0
- package/dist/apps/shared/components/admin/integrations/GiphyForm.js +11 -0
- package/dist/apps/shared/components/admin/integrations/IntegrationSectionForm.d.ts +54 -0
- package/dist/apps/shared/components/admin/integrations/IntegrationSectionForm.js +131 -0
- package/dist/apps/shared/components/admin/integrations/IntegrationSectionPage.d.ts +12 -0
- package/dist/apps/shared/components/admin/integrations/IntegrationSectionPage.js +21 -0
- package/dist/apps/shared/components/admin/integrations/IntegrationsLayout.d.ts +14 -0
- package/dist/apps/shared/components/admin/integrations/IntegrationsLayout.js +46 -0
- package/dist/apps/shared/components/admin/integrations/PluginRegistryForm.d.ts +7 -0
- package/dist/apps/shared/components/admin/integrations/PluginRegistryForm.js +90 -0
- package/dist/apps/shared/components/admin/integrations/SecretField.d.ts +25 -0
- package/dist/apps/shared/components/admin/integrations/SecretField.js +25 -0
- package/dist/apps/shared/components/admin/integrations/SigningCaForm.d.ts +7 -0
- package/dist/apps/shared/components/admin/integrations/SigningCaForm.js +25 -0
- package/dist/apps/shared/components/admin/integrations/StunForm.d.ts +9 -0
- package/dist/apps/shared/components/admin/integrations/StunForm.js +26 -0
- package/dist/apps/shared/components/admin/integrations/dnsProviderParts.d.ts +28 -0
- package/dist/apps/shared/components/admin/integrations/dnsProviderParts.js +43 -0
- package/dist/apps/shared/components/admin/layout/AdminShell.d.ts +1 -1
- package/dist/apps/shared/components/admin/layout/AdminShell.js +8 -1
- package/dist/apps/shared/components/admin/settings/CopyableValue.d.ts +6 -0
- package/dist/apps/shared/components/admin/settings/CopyableValue.js +6 -0
- package/dist/apps/shared/components/admin/settings/DomainApexTxt.d.ts +13 -0
- package/dist/apps/shared/components/admin/settings/DomainApexTxt.js +148 -0
- package/dist/apps/shared/components/admin/settings/DomainDkimRotation.d.ts +15 -0
- package/dist/apps/shared/components/admin/settings/DomainDkimRotation.js +200 -0
- package/dist/apps/shared/components/admin/settings/DomainDnsProvider.d.ts +12 -0
- package/dist/apps/shared/components/admin/settings/DomainDnsProvider.js +70 -0
- package/dist/apps/shared/components/admin/settings/DomainDnsSetup.d.ts +3 -3
- package/dist/apps/shared/components/admin/settings/DomainDnsSetup.js +61 -8
- package/dist/apps/shared/components/admin/setup/SetupWizard.js +9 -1
- package/dist/lib/admin/configKeysApi.d.ts +96 -0
- package/dist/lib/admin/configKeysApi.js +34 -0
- package/dist/lib/admin/dnsProvidersApi.d.ts +172 -0
- package/dist/lib/admin/dnsProvidersApi.js +70 -0
- package/dist/lib/admin/domainsApi.d.ts +116 -1
- package/dist/lib/admin/domainsApi.js +33 -0
- package/dist/lib/admin/integrationsApi.d.ts +75 -0
- package/dist/lib/admin/integrationsApi.js +31 -0
- package/lib/admin/configKeysApi.ts +117 -0
- package/lib/admin/dnsProvidersApi.ts +241 -0
- package/lib/admin/domainsApi.ts +145 -1
- package/lib/admin/integrationsApi.ts +107 -0
- package/package.json +38 -37
|
@@ -24,6 +24,23 @@ export interface Domain {
|
|
|
24
24
|
/** When set, this domain is a pure alias of the domain named here - it has no mailboxes of its own; mail
|
|
25
25
|
* addressed to it is delivered to the matching mailbox on the domain it aliases instead. */
|
|
26
26
|
aliasOf?: string;
|
|
27
|
+
/** Where the domain came from: `"seed"` for one the server added at start-up from the installation's own list, `"console"` for one added through
|
|
28
|
+
* the API. Unset on a domain from before this was recorded, which counts as added through the console. Server-managed. */
|
|
29
|
+
origin?: "seed" | "console" | null;
|
|
30
|
+
/** The DNS provider config (`system/dns-providers`) that publishes this domain's records, `null` or unset for "manual": the administrator adds
|
|
31
|
+
* them by hand. Changes only through `assignDnsProvider()`. */
|
|
32
|
+
dnsProviderUid?: string | null;
|
|
33
|
+
/** Whether RapidMX owns the domain's root TXT records (to publish SPF there) and which other values it keeps in them. Unset means not managed;
|
|
34
|
+
* `keep` and `spf` stay after an opt-out. Changes only through `updateApexTxt()`. */
|
|
35
|
+
apexTxt?: ApexTxtSettings | null;
|
|
36
|
+
/** The DKIM key rotation in progress, if any (`getDkimRotation()` is the console's view of it). Server-managed: changes only through `rotateDkim()`,
|
|
37
|
+
* `cancelDkimRotation()` and `setDkimRotationPaused()`. */
|
|
38
|
+
dkimRotation?: DkimRotationState | null;
|
|
39
|
+
}
|
|
40
|
+
export interface ApexTxtSettings {
|
|
41
|
+
managed: boolean;
|
|
42
|
+
keep: string[];
|
|
43
|
+
spf?: string;
|
|
27
44
|
}
|
|
28
45
|
/** `client`, given by every function below, is an explicit `ApiClient` from `createApiClient()` (e.g. one
|
|
29
46
|
* account of a multi-account app) to call instead of the default global `apiFetch()` - see `withClient()`'s
|
|
@@ -56,7 +73,22 @@ export declare function deleteDomain(uid: string, version: number, client?: ApiC
|
|
|
56
73
|
* Idempotent on an already-verified domain; a still-unverified result is not an error (only
|
|
57
74
|
* `lastCheckedAt` advances) — always returns the domain's current state either way. */
|
|
58
75
|
export declare function verifyDomain(uid: string, client?: ApiClient): Promise<Domain>;
|
|
59
|
-
export type DnsRecordType = "ownership" | "mx" | "spf" | "dkim" | "dmarc" | "openpgpkey_cname" | "autodiscover_cname" | "autodiscover_srv";
|
|
76
|
+
export type DnsRecordType = "ownership" | "mx" | "spf" | "dkim" | "dkim_next" | "dkim_previous" | "dmarc" | "openpgpkey_cname" | "autodiscover_cname" | "autodiscover_srv";
|
|
77
|
+
/** How a record is published: `published` (the DNS controller applied it and the live lookup sees it), `pending`, `conflict` (a record this deployment
|
|
78
|
+
* doesn't own is there) or `manual` (the administrator adds it, see `DnsManualReason`). */
|
|
79
|
+
export type DnsPublicationState = "published" | "pending" | "conflict" | "manual";
|
|
80
|
+
/** Why a published record is in conflict: a record of the same type is there that this deployment doesn't own (`not_owned`, which a takeover can
|
|
81
|
+
* overwrite), or records of another type that can't stand next to it (`type_mismatch`: a CNAME wanted where other records are, or other records wanted
|
|
82
|
+
* where a CNAME is), which RapidMX never deletes or takes over and the administrator deletes at their DNS host. */
|
|
83
|
+
export type DnsConflictReason = "not_owned" | "type_mismatch";
|
|
84
|
+
/** A live record in the way of a published one (`type_mismatch`): its type and its value as text. */
|
|
85
|
+
export interface DnsExistingRecord {
|
|
86
|
+
type: "A" | "AAAA" | "CNAME" | "MX" | "TXT" | "SRV";
|
|
87
|
+
value: string;
|
|
88
|
+
}
|
|
89
|
+
/** Why a record is `manual`: no provider is assigned, the provider can't hold the record's kind, the name lies outside the provider's zones, or the
|
|
90
|
+
* record belongs to the root TXT set, which RapidMX manages only on the administrator's opt-in. */
|
|
91
|
+
export type DnsManualReason = "no_provider" | "kind_unsupported" | "outside_zones" | "apex_txt_unmanaged";
|
|
60
92
|
/** One mail-related DNS record this server recommends for a domain, live-checked against real DNS —
|
|
61
93
|
* purely diagnostic, never mutates anything (unlike `verifyDomain`). The two `autodiscover_*` types are only
|
|
62
94
|
* ever present while `@rapidmx/autodiscover-plugin` is active for this deployment. */
|
|
@@ -71,5 +103,88 @@ export interface DnsRecordCheck {
|
|
|
71
103
|
found: boolean;
|
|
72
104
|
matches: boolean;
|
|
73
105
|
actualValue?: string;
|
|
106
|
+
/** Whether, and how, the DNS controller publishes this record. Present only on a server that publishes, and only for a configured record. */
|
|
107
|
+
publication?: DnsPublicationState;
|
|
108
|
+
manualReason?: DnsManualReason;
|
|
109
|
+
/** With `publication: "conflict"`: why. A server that doesn't say means `not_owned`. */
|
|
110
|
+
conflictReason?: DnsConflictReason;
|
|
111
|
+
/** With `conflictReason: "type_mismatch"`: the live records the administrator has to delete. */
|
|
112
|
+
existing?: DnsExistingRecord[];
|
|
113
|
+
/** `ownership` only: the token is (still) at the domain's root, where it lived before `_rapidmx-verify.<domain>`. */
|
|
114
|
+
legacyApex?: boolean;
|
|
74
115
|
}
|
|
75
116
|
export declare function getDnsSetup(uid: string, client?: ApiClient): Promise<DnsRecordCheck[]>;
|
|
117
|
+
/** Assigns the domain to a DNS provider config, or back to manual (`null`). Needs an elevated token; the provider must cover the domain more specifically
|
|
118
|
+
* than any other (a 400 otherwise). Answers the domain. */
|
|
119
|
+
export declare function assignDnsProvider(uid: string, dnsProviderUid: string | null, client?: ApiClient): Promise<Domain>;
|
|
120
|
+
/** What `GET /:id/apex-txt` answers: the managed state and what the opt-in form starts from. */
|
|
121
|
+
export interface ApexTxtView {
|
|
122
|
+
managed: boolean;
|
|
123
|
+
keep: string[];
|
|
124
|
+
spf?: string;
|
|
125
|
+
/** The values at the domain's root now, as a live lookup sees them. */
|
|
126
|
+
current: string[];
|
|
127
|
+
/** `current` without SPF records and RapidMX ownership tokens: what the list of values to keep starts as. */
|
|
128
|
+
suggestedKeep: string[];
|
|
129
|
+
/** The lookup couldn't be answered, so `current` and `suggestedKeep` say nothing about what is there: the opt-in must not be offered. */
|
|
130
|
+
lookupFailed?: true;
|
|
131
|
+
}
|
|
132
|
+
export declare function getApexTxt(uid: string, client?: ApiClient): Promise<ApexTxtView>;
|
|
133
|
+
/** Opts the domain's root TXT records into RapidMX's management (`managed: true` with the values to keep), edits the kept values while managed, or
|
|
134
|
+
* opts out (`managed: false`). Needs an elevated token. Answers the domain. */
|
|
135
|
+
export declare function updateApexTxt(uid: string, input: {
|
|
136
|
+
managed: boolean;
|
|
137
|
+
keep?: string[];
|
|
138
|
+
}, client?: ApiClient): Promise<Domain>;
|
|
139
|
+
/** Why a DKIM key rotation began: the schedule, an administrator's "Rotate now", or a key reported compromised (no publish wait, no overlap). */
|
|
140
|
+
export type DkimRotationReason = "scheduled" | "manual" | "compromised";
|
|
141
|
+
/** A domain's DKIM key rotation, as `Domain.dkimRotation` holds it. `idle`: none in progress. `pending`: the new key (`next`, whose `publicKey` is `""`
|
|
142
|
+
* until it is generated) is published and signing waits for it to be live (`liveSince`) and the publish wait to pass. `switching`: signing switched to the
|
|
143
|
+
* new key (`switchedAt`) and the mail server hasn't confirmed it yet. `overlap`: confirmed; the old key (`previous`) stays published until its
|
|
144
|
+
* `retireAt`. */
|
|
145
|
+
export interface DkimRotationState {
|
|
146
|
+
phase: "idle" | "pending" | "switching" | "overlap";
|
|
147
|
+
next?: {
|
|
148
|
+
selector: string;
|
|
149
|
+
publicKey: string;
|
|
150
|
+
createdAt: string;
|
|
151
|
+
liveSince?: string;
|
|
152
|
+
reason: DkimRotationReason;
|
|
153
|
+
};
|
|
154
|
+
previous?: {
|
|
155
|
+
selector: string;
|
|
156
|
+
publicKey: string;
|
|
157
|
+
retireAt?: string;
|
|
158
|
+
};
|
|
159
|
+
/** The reason of the rotation that switched, kept until it is retired. */
|
|
160
|
+
reason?: DkimRotationReason;
|
|
161
|
+
switchedAt?: string;
|
|
162
|
+
lastRotatedAt?: string;
|
|
163
|
+
lastCancelledAt?: string;
|
|
164
|
+
/** Scheduled rotation is paused for this domain. */
|
|
165
|
+
paused?: boolean;
|
|
166
|
+
/** What is holding the rotation up: no confirmation from the mail server, a key that couldn't be generated. */
|
|
167
|
+
lastError?: string;
|
|
168
|
+
}
|
|
169
|
+
/** Why a domain's key can't rotate: the key provider doesn't rotate, outbound mail is signed by SES, the domain has no key yet, or its key was imported by
|
|
170
|
+
* hand or can't be told from one. */
|
|
171
|
+
export type DkimRotationBlocker = "unsupported" | "ses" | "no_key" | "imported" | "unknown_key";
|
|
172
|
+
/** What `GET /:id/dkim/rotation` answers. */
|
|
173
|
+
export interface DkimRotationView {
|
|
174
|
+
rotation: DkimRotationState | null;
|
|
175
|
+
rotatable: boolean;
|
|
176
|
+
blocker?: DkimRotationBlocker;
|
|
177
|
+
/** When the next scheduled rotation starts; only while none is in progress, the domain isn't paused and scheduled rotation is on. */
|
|
178
|
+
nextScheduledAt?: string;
|
|
179
|
+
}
|
|
180
|
+
export declare function getDkimRotation(uid: string, client?: ApiClient): Promise<DkimRotationView>;
|
|
181
|
+
/** Starts a rotation of the domain's DKIM key ("Rotate now"). With `compromised`, the publish wait and the overlap are skipped, and during a rotation it
|
|
182
|
+
* reports the key being replaced as compromised. Needs an elevated token. Answers the domain. */
|
|
183
|
+
export declare function rotateDkim(uid: string, input?: {
|
|
184
|
+
compromised?: boolean;
|
|
185
|
+
}, client?: ApiClient): Promise<Domain>;
|
|
186
|
+
/** Abandons a rotation the mail server hasn't confirmed (a switch not yet confirmed is reverted); a 409 once it has. Needs an elevated token. Answers the
|
|
187
|
+
* domain. */
|
|
188
|
+
export declare function cancelDkimRotation(uid: string, client?: ApiClient): Promise<Domain>;
|
|
189
|
+
/** Pauses or resumes scheduled rotation for the domain; a rotation in progress still finishes. Needs an elevated token. Answers the domain. */
|
|
190
|
+
export declare function setDkimRotationPaused(uid: string, paused: boolean, client?: ApiClient): Promise<Domain>;
|
|
@@ -42,3 +42,36 @@ export function verifyDomain(uid, client) {
|
|
|
42
42
|
export function getDnsSetup(uid, client) {
|
|
43
43
|
return withClient(client, `/mail/domains/${encodeURIComponent(uid)}/dns-setup`);
|
|
44
44
|
}
|
|
45
|
+
/** Assigns the domain to a DNS provider config, or back to manual (`null`). Needs an elevated token; the provider must cover the domain more specifically
|
|
46
|
+
* than any other (a 400 otherwise). Answers the domain. */
|
|
47
|
+
export function assignDnsProvider(uid, dnsProviderUid, client) {
|
|
48
|
+
return withClient(client, `/mail/domains/${encodeURIComponent(uid)}/dns-provider`, {
|
|
49
|
+
method: "PUT",
|
|
50
|
+
body: JSON.stringify({ dnsProviderUid }),
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
export function getApexTxt(uid, client) {
|
|
54
|
+
return withClient(client, `/mail/domains/${encodeURIComponent(uid)}/apex-txt`);
|
|
55
|
+
}
|
|
56
|
+
/** Opts the domain's root TXT records into RapidMX's management (`managed: true` with the values to keep), edits the kept values while managed, or
|
|
57
|
+
* opts out (`managed: false`). Needs an elevated token. Answers the domain. */
|
|
58
|
+
export function updateApexTxt(uid, input, client) {
|
|
59
|
+
return withClient(client, `/mail/domains/${encodeURIComponent(uid)}/apex-txt`, { method: "PUT", body: JSON.stringify(input) });
|
|
60
|
+
}
|
|
61
|
+
export function getDkimRotation(uid, client) {
|
|
62
|
+
return withClient(client, `/mail/domains/${encodeURIComponent(uid)}/dkim/rotation`);
|
|
63
|
+
}
|
|
64
|
+
/** Starts a rotation of the domain's DKIM key ("Rotate now"). With `compromised`, the publish wait and the overlap are skipped, and during a rotation it
|
|
65
|
+
* reports the key being replaced as compromised. Needs an elevated token. Answers the domain. */
|
|
66
|
+
export function rotateDkim(uid, input = {}, client) {
|
|
67
|
+
return withClient(client, `/mail/domains/${encodeURIComponent(uid)}/dkim/rotate`, { method: "POST", body: JSON.stringify(input) });
|
|
68
|
+
}
|
|
69
|
+
/** Abandons a rotation the mail server hasn't confirmed (a switch not yet confirmed is reverted); a 409 once it has. Needs an elevated token. Answers the
|
|
70
|
+
* domain. */
|
|
71
|
+
export function cancelDkimRotation(uid, client) {
|
|
72
|
+
return withClient(client, `/mail/domains/${encodeURIComponent(uid)}/dkim/rotation/cancel`, { method: "POST" });
|
|
73
|
+
}
|
|
74
|
+
/** Pauses or resumes scheduled rotation for the domain; a rotation in progress still finishes. Needs an elevated token. Answers the domain. */
|
|
75
|
+
export function setDkimRotationPaused(uid, paused, client) {
|
|
76
|
+
return withClient(client, `/mail/domains/${encodeURIComponent(uid)}/dkim/rotation`, { method: "PUT", body: JSON.stringify({ paused }) });
|
|
77
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed wrappers over `@rapidmx/restapi`'s `BaseIntegrationsRoute` (mounted at `system/integrations`): the third-party services an administrator
|
|
3
|
+
* chooses (Giphy, STUN, the S/MIME signing CA, the DNS resolver, the plugin registry), each section overriding some of the deployment's configuration.
|
|
4
|
+
* `GET` needs a trusted role; every write a trusted role and an elevated token. Secrets are write-only: the server answers them only as the mask.
|
|
5
|
+
*/
|
|
6
|
+
import { ApiClient } from "../util/api.js";
|
|
7
|
+
/** What the server shows in place of a secret that is set (restapi `SecretMask`); sent back, it means "keep the saved value". `unreadable` is the
|
|
8
|
+
* server saying that this copy can't decrypt it (a key missing from its keyring, after a restore with an older one): never sent back. */
|
|
9
|
+
export interface SecretMask {
|
|
10
|
+
secret: true;
|
|
11
|
+
unreadable?: boolean;
|
|
12
|
+
}
|
|
13
|
+
/** Whether `value` is the mask: an object whose keys are `secret`, set to `true`, and at most `unreadable`, a flag. */
|
|
14
|
+
export declare function isSecretMask(value: unknown): value is SecretMask;
|
|
15
|
+
/** Whether `value` is the mask of a secret that is set but that the server can't read: it must be entered again. */
|
|
16
|
+
export declare function isUnreadableSecret(value: unknown): boolean;
|
|
17
|
+
export type IntegrationSectionName = "giphy" | "stun" | "signingCa" | "dnsResolver" | "pluginRegistry";
|
|
18
|
+
/** Where a field's value comes from: saved by an administrator here, or the deployment's configuration. */
|
|
19
|
+
export type IntegrationSource = "admin" | "deployment";
|
|
20
|
+
/** One section as the server shows it (restapi `IntegrationSectionView`): the values in effect, the deployment's own values (what a reset puts a field
|
|
21
|
+
* back to) and where each value comes from. A secret is the mask when set, else `null`. */
|
|
22
|
+
export interface IntegrationSectionView<V> {
|
|
23
|
+
values: V;
|
|
24
|
+
defaults: V;
|
|
25
|
+
source: Record<keyof V, IntegrationSource>;
|
|
26
|
+
}
|
|
27
|
+
export interface GiphyIntegration {
|
|
28
|
+
enabled: boolean;
|
|
29
|
+
apiKey: SecretMask | null;
|
|
30
|
+
}
|
|
31
|
+
export interface StunIntegration {
|
|
32
|
+
/** `null`: the meet plugin's built-in list. */
|
|
33
|
+
urls: string[] | null;
|
|
34
|
+
}
|
|
35
|
+
export type SigningCaBackend = "rfc8823" | "manual" | "none";
|
|
36
|
+
export interface SigningCaIntegration {
|
|
37
|
+
backend: SigningCaBackend;
|
|
38
|
+
directoryUrl: string;
|
|
39
|
+
contactEmail: string | null;
|
|
40
|
+
}
|
|
41
|
+
export type DnsResolverMode = "node" | "doh-dnssec";
|
|
42
|
+
export interface DnsResolverIntegration {
|
|
43
|
+
mode: DnsResolverMode;
|
|
44
|
+
dohEndpoint: string;
|
|
45
|
+
}
|
|
46
|
+
/** A plugin namespace as the view shows it: its token only as the mask. Sent back, `token` is the mask (keep), a new value, or left out (none). */
|
|
47
|
+
export interface IntegrationPluginNamespace {
|
|
48
|
+
name: string;
|
|
49
|
+
registry?: string;
|
|
50
|
+
token?: SecretMask | string;
|
|
51
|
+
}
|
|
52
|
+
export interface PluginRegistryIntegration {
|
|
53
|
+
url: string;
|
|
54
|
+
token: SecretMask | null;
|
|
55
|
+
namespaces: IntegrationPluginNamespace[];
|
|
56
|
+
}
|
|
57
|
+
/** What `GET /system/integrations` and every write answer (restapi `IntegrationSettingsResponse`). */
|
|
58
|
+
export interface IntegrationSettings {
|
|
59
|
+
giphy: IntegrationSectionView<GiphyIntegration>;
|
|
60
|
+
stun: IntegrationSectionView<StunIntegration>;
|
|
61
|
+
signingCa: IntegrationSectionView<SigningCaIntegration>;
|
|
62
|
+
dnsResolver: IntegrationSectionView<DnsResolverIntegration>;
|
|
63
|
+
pluginRegistry: IntegrationSectionView<PluginRegistryIntegration>;
|
|
64
|
+
/** The saved settings' version (0 before anything was saved), sent back with a change so one made against an older version is refused (409). */
|
|
65
|
+
version: number;
|
|
66
|
+
}
|
|
67
|
+
/** A section's change: a field left out is unchanged, `null` puts it back to the deployment's value, a secret sent back as the mask is kept. */
|
|
68
|
+
export type IntegrationSectionPatch = Record<string, unknown> & {
|
|
69
|
+
version?: number;
|
|
70
|
+
};
|
|
71
|
+
/** `client`, given by every function below, is an explicit `ApiClient` from `createApiClient()` (e.g. one
|
|
72
|
+
* account of a multi-account app) to call instead of the default global `apiFetch()` - see `withClient()`'s
|
|
73
|
+
* own doc comment in `util/api.ts`. Omitted (the default), every function here behaves exactly as before. */
|
|
74
|
+
export declare function getIntegrations(client?: ApiClient): Promise<IntegrationSettings>;
|
|
75
|
+
export declare function updateIntegrationSection(section: IntegrationSectionName, patch: IntegrationSectionPatch, client?: ApiClient): Promise<IntegrationSettings>;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
2
|
+
// Copyright (C) 2026 Jean-Philippe Steinmetz
|
|
3
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
5
|
+
/**
|
|
6
|
+
* Typed wrappers over `@rapidmx/restapi`'s `BaseIntegrationsRoute` (mounted at `system/integrations`): the third-party services an administrator
|
|
7
|
+
* chooses (Giphy, STUN, the S/MIME signing CA, the DNS resolver, the plugin registry), each section overriding some of the deployment's configuration.
|
|
8
|
+
* `GET` needs a trusted role; every write a trusted role and an elevated token. Secrets are write-only: the server answers them only as the mask.
|
|
9
|
+
*/
|
|
10
|
+
import { withClient } from "../util/api.js";
|
|
11
|
+
/** Whether `value` is the mask: an object whose keys are `secret`, set to `true`, and at most `unreadable`, a flag. */
|
|
12
|
+
export function isSecretMask(value) {
|
|
13
|
+
if (typeof value !== "object" || value === null || value.secret !== true) {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
const unreadable = value.unreadable;
|
|
17
|
+
return Object.keys(value).length === (unreadable === undefined ? 1 : 2) && (unreadable === undefined || typeof unreadable === "boolean");
|
|
18
|
+
}
|
|
19
|
+
/** Whether `value` is the mask of a secret that is set but that the server can't read: it must be entered again. */
|
|
20
|
+
export function isUnreadableSecret(value) {
|
|
21
|
+
return isSecretMask(value) && value.unreadable === true;
|
|
22
|
+
}
|
|
23
|
+
/** `client`, given by every function below, is an explicit `ApiClient` from `createApiClient()` (e.g. one
|
|
24
|
+
* account of a multi-account app) to call instead of the default global `apiFetch()` - see `withClient()`'s
|
|
25
|
+
* own doc comment in `util/api.ts`. Omitted (the default), every function here behaves exactly as before. */
|
|
26
|
+
export function getIntegrations(client) {
|
|
27
|
+
return withClient(client, "/system/integrations");
|
|
28
|
+
}
|
|
29
|
+
export function updateIntegrationSection(section, patch, client) {
|
|
30
|
+
return withClient(client, `/system/integrations/${section}`, { method: "PUT", body: JSON.stringify(patch) });
|
|
31
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
2
|
+
// Copyright (C) 2026 Jean-Philippe Steinmetz
|
|
3
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
5
|
+
/**
|
|
6
|
+
* Typed wrappers over `@rapidmx/restapi`'s `BaseConfigKeyRoute` (mounted at `system/config-keys`): the keyring that encrypts the credentials, API keys and
|
|
7
|
+
* tokens saved in the console, and the rotation of its keys. `GET` needs a trusted role; both writes a trusted role and an elevated token. Never any key
|
|
8
|
+
* material: a key is its `kid`, its age and how many values it encrypts.
|
|
9
|
+
*/
|
|
10
|
+
import { ApiClient, ApiRequestError, withClient } from "../util/api.js";
|
|
11
|
+
|
|
12
|
+
/** Where a rotation is (restapi `ConfigKeyRotationPhase`). */
|
|
13
|
+
export type ConfigKeyRotationPhase = "adding" | "activating" | "reencrypting" | "retiring" | "done" | "failed";
|
|
14
|
+
|
|
15
|
+
/** One key of the ring (restapi `PublicConfigKey`). */
|
|
16
|
+
export interface ConfigKeyInfo {
|
|
17
|
+
kid: string;
|
|
18
|
+
/** ISO 8601. */
|
|
19
|
+
createdAt: string;
|
|
20
|
+
/** Whether the server copy that answered encrypts with it. */
|
|
21
|
+
active: boolean;
|
|
22
|
+
/** The values encrypted with it at the end of the last re-encryption pass; left out until one has counted. */
|
|
23
|
+
envelopes?: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** The current or last rotation (restapi `PublicConfigKeyRotation`). */
|
|
27
|
+
export interface ConfigKeyRotation {
|
|
28
|
+
/** The new key, once it is in the keyring. */
|
|
29
|
+
kid?: string;
|
|
30
|
+
/** The keys it replaces. */
|
|
31
|
+
previousKids: string[];
|
|
32
|
+
compromised: boolean;
|
|
33
|
+
requestedBy: string;
|
|
34
|
+
requestedAt: string;
|
|
35
|
+
phase: ConfigKeyRotationPhase;
|
|
36
|
+
activationStartedAt?: string;
|
|
37
|
+
/** The server copies (pod name and process id) that haven't yet loaded the new key while it is `activating`, and, once activated, those that don't yet
|
|
38
|
+
* encrypt with it. */
|
|
39
|
+
waitingFor?: string[];
|
|
40
|
+
activatedAt?: string;
|
|
41
|
+
/** Set by "Activate anyway", with the copies that hadn't loaded the key then. */
|
|
42
|
+
activationForced?: { by: string; at: string; staleCopies: string[] };
|
|
43
|
+
finishedAt?: string;
|
|
44
|
+
/** What failed (a keyring write refused, for example), never a value. */
|
|
45
|
+
lastError?: string;
|
|
46
|
+
/** Why the new key is still not active and what resolves it, once the wait has gone on past `config_encryption:activation:warn_minutes`. */
|
|
47
|
+
warning?: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** A saved value the last re-encryption pass couldn't decrypt: named by record and field, never its value. */
|
|
51
|
+
export interface ConfigKeyUnreadable {
|
|
52
|
+
record: string;
|
|
53
|
+
uid: string;
|
|
54
|
+
field: string;
|
|
55
|
+
/** The key the envelope names. */
|
|
56
|
+
kid: string;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface ConfigKeyReencryption {
|
|
60
|
+
targetKid: string;
|
|
61
|
+
/** Whether the last pass saw every row and saved every change. */
|
|
62
|
+
complete: boolean;
|
|
63
|
+
startedAt: string;
|
|
64
|
+
finishedAt?: string;
|
|
65
|
+
unreadable?: ConfigKeyUnreadable[];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** The plugin secret settings' encryption (spec 5.7). */
|
|
69
|
+
export interface PluginSecretsState {
|
|
70
|
+
/** When the first encryption run completed; until then secret settings are saved in plain text. */
|
|
71
|
+
encryptedAt?: string;
|
|
72
|
+
waitingForRollout?: boolean;
|
|
73
|
+
checkedAt?: string;
|
|
74
|
+
/** Why the run is still waiting (old pods, or the pods can't be listed). */
|
|
75
|
+
reason?: string;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** What `GET /system/config-keys` and both writes answer (restapi `PublicConfigKeys`). */
|
|
79
|
+
export interface ConfigKeys {
|
|
80
|
+
/** Whether a rotation can be started: the keyring is in a store the server can write, and the server copies can be coordinated. */
|
|
81
|
+
available: boolean;
|
|
82
|
+
activeKid: string | null;
|
|
83
|
+
keys: ConfigKeyInfo[];
|
|
84
|
+
/** When the values were last counted. */
|
|
85
|
+
countedAt: string | null;
|
|
86
|
+
rotation: ConfigKeyRotation | null;
|
|
87
|
+
reencryption: ConfigKeyReencryption | null;
|
|
88
|
+
pluginSecrets: PluginSecretsState | null;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** `client`, given by every function below, is an explicit `ApiClient` from `createApiClient()` (e.g. one
|
|
92
|
+
* account of a multi-account app) to call instead of the default global `apiFetch()` - see `withClient()`'s
|
|
93
|
+
* own doc comment in `util/api.ts`. Omitted (the default), every function here behaves exactly as before. */
|
|
94
|
+
export function getConfigKeys(client?: ApiClient): Promise<ConfigKeys> {
|
|
95
|
+
return withClient(client, "/system/config-keys");
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Starts a rotation of the configuration key. With `compromised`, it also marks a rotation already running as compromised. Needs an elevated token. A 409
|
|
99
|
+
* (one is running) carries its progress: see `configKeysOfRefusal()`. */
|
|
100
|
+
export function rotateConfigKey(input: { compromised?: boolean } = {}, client?: ApiClient): Promise<ConfigKeys> {
|
|
101
|
+
return withClient(client, "/system/config-keys/rotate", { method: "POST", body: JSON.stringify(input) });
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** "Activate anyway": makes the rotation's new key the one that encrypts before every server copy has loaded it. A 409 (the rotation isn't waiting to be
|
|
105
|
+
* activated, or `kid` isn't its key) carries its progress. Needs an elevated token. */
|
|
106
|
+
export function activateConfigKey(kid: string, client?: ApiClient): Promise<ConfigKeys> {
|
|
107
|
+
return withClient(client, "/system/config-keys/activate", { method: "POST", body: JSON.stringify({ kid }) });
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** The keyring's state a 409 carries as its body (a rotation already running, or not waiting to be activated), `undefined` for any other failure. */
|
|
111
|
+
export function configKeysOfRefusal(err: unknown): ConfigKeys | undefined {
|
|
112
|
+
if (!(err instanceof ApiRequestError) || err.status !== 409) {
|
|
113
|
+
return undefined;
|
|
114
|
+
}
|
|
115
|
+
const body = err.details as Partial<ConfigKeys> | null | undefined;
|
|
116
|
+
return body && typeof body === "object" && Array.isArray(body.keys) ? (body as ConfigKeys) : undefined;
|
|
117
|
+
}
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
2
|
+
// Copyright (C) 2026 Jean-Philippe Steinmetz
|
|
3
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
5
|
+
/**
|
|
6
|
+
* Typed wrappers over `@rapidmx/restapi`'s `BaseDnsProviderRoute` (mounted at `system/dns-providers`): the DNS provider configs the server publishes
|
|
7
|
+
* domain records through, the provider types the console's form is built from, the operations the DNS controller runs for a config, and what the
|
|
8
|
+
* controller last said about itself. Reads need a trusted role; every write a trusted role and an elevated token. Credentials are write-only.
|
|
9
|
+
*/
|
|
10
|
+
import { ApiClient, withClient } from "../util/api.js";
|
|
11
|
+
import type { DnsConflictReason, DnsExistingRecord, DnsManualReason, DnsPublicationState, DnsRecordType } from "./domainsApi.js";
|
|
12
|
+
import type { SecretMask } from "./integrationsApi.js";
|
|
13
|
+
|
|
14
|
+
export type DnsProviderType = "cloudflare" | "route53" | "rfc2136" | "google" | "azure";
|
|
15
|
+
|
|
16
|
+
export type DnsProviderOptionValue = string | number | boolean | string[];
|
|
17
|
+
|
|
18
|
+
export type DnsProviderOptionKind = "string" | "string-list" | "integer" | "boolean" | "enum";
|
|
19
|
+
|
|
20
|
+
/** One non-secret option of a provider type (restapi `DnsProviderTypeInfo["options"]`). */
|
|
21
|
+
export interface DnsProviderOptionSpec {
|
|
22
|
+
key: string;
|
|
23
|
+
label: string;
|
|
24
|
+
kind: DnsProviderOptionKind;
|
|
25
|
+
required: boolean;
|
|
26
|
+
/** What a value left out is saved as. */
|
|
27
|
+
default?: string | number | boolean;
|
|
28
|
+
/** The choices of an `enum`. */
|
|
29
|
+
values?: string[];
|
|
30
|
+
min?: number;
|
|
31
|
+
max?: number;
|
|
32
|
+
maxItems?: number;
|
|
33
|
+
description: string;
|
|
34
|
+
format?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** One credential field of a provider type: saved encrypted on the config, never shown again. */
|
|
38
|
+
export interface DnsProviderSecretFieldSpec {
|
|
39
|
+
key: string;
|
|
40
|
+
label: string;
|
|
41
|
+
/** Always needed. One needed only in some configurations says when in `description`. */
|
|
42
|
+
required: boolean;
|
|
43
|
+
/** A file's content (Google's service-account JSON) rather than a token. */
|
|
44
|
+
multiLine?: boolean;
|
|
45
|
+
description: string;
|
|
46
|
+
format?: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** What `GET /system/dns-providers/types` answers per type. */
|
|
50
|
+
export interface DnsProviderTypeInfo {
|
|
51
|
+
type: DnsProviderType;
|
|
52
|
+
label: string;
|
|
53
|
+
externalDnsProvider: string;
|
|
54
|
+
/** Whether the type has been tested against the real service; the console marks the others "not yet live-tested". */
|
|
55
|
+
tested: boolean;
|
|
56
|
+
options: DnsProviderOptionSpec[];
|
|
57
|
+
secretFields: DnsProviderSecretFieldSpec[];
|
|
58
|
+
unsupportedRecordKinds: ("MX" | "SRV")[];
|
|
59
|
+
notes: string[];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** What the DNS controller last reported about a config's external-dns instance; every field optional. */
|
|
63
|
+
export interface DnsProviderStatus {
|
|
64
|
+
reportedAt?: string;
|
|
65
|
+
readyReplicas?: number;
|
|
66
|
+
image?: string;
|
|
67
|
+
lastRolloutAt?: string;
|
|
68
|
+
lastSyncAt?: string;
|
|
69
|
+
registryErrors?: number;
|
|
70
|
+
sourceErrors?: number;
|
|
71
|
+
providerErrors?: number;
|
|
72
|
+
ownerMismatchRecords?: number;
|
|
73
|
+
lastError?: string;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** A provider config as the server shows it (restapi `PublicDnsProvider`): its credentials only as masks, one per key saved. */
|
|
77
|
+
export interface DnsProvider {
|
|
78
|
+
uid: string;
|
|
79
|
+
name: string;
|
|
80
|
+
type: DnsProviderType;
|
|
81
|
+
zones: string[];
|
|
82
|
+
options: Record<string, DnsProviderOptionValue>;
|
|
83
|
+
credentials: Record<string, SecretMask>;
|
|
84
|
+
origin?: "installer" | "console";
|
|
85
|
+
enabled: boolean;
|
|
86
|
+
allowTakeover: boolean;
|
|
87
|
+
status?: DnsProviderStatus;
|
|
88
|
+
/** The domains assigned to this config. */
|
|
89
|
+
domainCount: number;
|
|
90
|
+
version: number;
|
|
91
|
+
dateCreated?: string;
|
|
92
|
+
dateModified?: string;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** What a create or an update sends. `credentials` is write-only: a string sets a field, `null` clears it, leaving it out keeps it. */
|
|
96
|
+
export interface DnsProviderInput {
|
|
97
|
+
name?: string;
|
|
98
|
+
/** Create only: a config's type never changes. */
|
|
99
|
+
type?: DnsProviderType;
|
|
100
|
+
zones?: string[];
|
|
101
|
+
options?: Record<string, DnsProviderOptionValue>;
|
|
102
|
+
credentials?: Record<string, string | null>;
|
|
103
|
+
enabled?: boolean;
|
|
104
|
+
allowTakeover?: boolean;
|
|
105
|
+
/** Update only: the version the change was made against; a newer one is a 409. */
|
|
106
|
+
version?: number;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export type DnsOperationType = "test" | "takeover" | "restore";
|
|
110
|
+
|
|
111
|
+
export type DnsOperationState = "queued" | "running" | "succeeded" | "failed";
|
|
112
|
+
|
|
113
|
+
/** One operation the DNS controller runs for a config (restapi `PublicDnsOperation`). */
|
|
114
|
+
export interface DnsOperation {
|
|
115
|
+
uid: string;
|
|
116
|
+
type: DnsOperationType;
|
|
117
|
+
providerUid: string;
|
|
118
|
+
domainUid?: string;
|
|
119
|
+
/** `hosts-<provider uid>` for the takeover of the deployment's own host names, which has no domain. */
|
|
120
|
+
endpointSetId?: string;
|
|
121
|
+
state: DnsOperationState;
|
|
122
|
+
/** The controller's result, or why the operation was cancelled (`Cancelled: ...`). */
|
|
123
|
+
message?: string;
|
|
124
|
+
requestedBy?: string;
|
|
125
|
+
startedAt?: string;
|
|
126
|
+
finishedAt?: string;
|
|
127
|
+
dateCreated?: string;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** What one status report of the controller is remembered by. */
|
|
131
|
+
export interface DnsControllerReport {
|
|
132
|
+
controllerVersion: string;
|
|
133
|
+
desiredStateVersion?: number;
|
|
134
|
+
leaseHolder: boolean;
|
|
135
|
+
leaseIdentity: string;
|
|
136
|
+
at: string;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** What `GET /system/dns-providers/controller` answers: what the DNS controller last said about itself, `null` for what it never said. */
|
|
140
|
+
export interface DnsControllerState {
|
|
141
|
+
handshake: {
|
|
142
|
+
controllerVersion: string;
|
|
143
|
+
apiVersions: string[];
|
|
144
|
+
compatible: boolean;
|
|
145
|
+
selected?: string;
|
|
146
|
+
reason?: string;
|
|
147
|
+
at: string;
|
|
148
|
+
} | null;
|
|
149
|
+
lastReport: DnsControllerReport | null;
|
|
150
|
+
lastStandbyReport: DnsControllerReport | null;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const BASE = "/system/dns-providers";
|
|
154
|
+
|
|
155
|
+
/** The path of the config `uid`. */
|
|
156
|
+
function providerPath(uid: string): string {
|
|
157
|
+
return `${BASE}/${encodeURIComponent(uid)}`;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** `client`, given by every function below, is an explicit `ApiClient` from `createApiClient()` (e.g. one
|
|
161
|
+
* account of a multi-account app) to call instead of the default global `apiFetch()` - see `withClient()`'s
|
|
162
|
+
* own doc comment in `util/api.ts`. Omitted (the default), every function here behaves exactly as before. */
|
|
163
|
+
export function listDnsProviders(client?: ApiClient): Promise<DnsProvider[]> {
|
|
164
|
+
return withClient(client, BASE);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export function listDnsProviderTypes(client?: ApiClient): Promise<DnsProviderTypeInfo[]> {
|
|
168
|
+
return withClient(client, `${BASE}/types`);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export function getDnsControllerState(client?: ApiClient): Promise<DnsControllerState> {
|
|
172
|
+
return withClient(client, `${BASE}/controller`);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export function getDnsProvider(uid: string, client?: ApiClient): Promise<DnsProvider> {
|
|
176
|
+
return withClient(client, providerPath(uid));
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export function createDnsProvider(input: DnsProviderInput, client?: ApiClient): Promise<DnsProvider> {
|
|
180
|
+
return withClient(client, BASE, { method: "POST", body: JSON.stringify(input) });
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export function updateDnsProvider(uid: string, input: DnsProviderInput, client?: ApiClient): Promise<DnsProvider> {
|
|
184
|
+
return withClient(client, providerPath(uid), { method: "PUT", body: JSON.stringify(input) });
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/** Refused (409) while a domain is assigned to the config or a restore through it is still to run. */
|
|
188
|
+
export function deleteDnsProvider(uid: string, client?: ApiClient): Promise<void> {
|
|
189
|
+
return withClient(client, providerPath(uid), { method: "DELETE" });
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/** Queues a credentials test the DNS controller runs; answers the queued operation. */
|
|
193
|
+
export function testDnsProvider(uid: string, client?: ApiClient): Promise<DnsOperation> {
|
|
194
|
+
return withClient(client, `${providerPath(uid)}/test`, { method: "POST" });
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/** Queues the takeover of a domain's records that this deployment doesn't own yet: the DNS controller overwrites them with the wanted values. `records`
|
|
198
|
+
* are the record types to take over, all the domain publishes through the config when left out; never the root TXT set (`spf`). Answers the queued
|
|
199
|
+
* operation. */
|
|
200
|
+
export function takeoverDnsRecords(uid: string, domainUid: string, records?: DnsRecordType[], client?: ApiClient): Promise<DnsOperation> {
|
|
201
|
+
return withClient(client, `${providerPath(uid)}/takeover`, {
|
|
202
|
+
method: "POST",
|
|
203
|
+
body: JSON.stringify({ domainUid, ...(records ? { records } : {}) }),
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/** Queues the takeover of the deployment's own host names (`mail.<domain>`, `auth.<domain>` and the like) that this config publishes and that something
|
|
208
|
+
* else created: the DNS controller overwrites them with the wanted values. `records` are the host names to take over, all the config publishes when left
|
|
209
|
+
* out; a host whose records of another type are in the way (`type_mismatch`) is left out, or refused when named. Answers the queued operation. */
|
|
210
|
+
export function takeoverDnsHosts(uid: string, records?: string[], client?: ApiClient): Promise<DnsOperation> {
|
|
211
|
+
return withClient(client, `${providerPath(uid)}/takeover`, {
|
|
212
|
+
method: "POST",
|
|
213
|
+
body: JSON.stringify({ hosts: true, ...(records ? { records } : {}) }),
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/** Why a host name isn't published automatically: a domain record's reasons, or no public address or no mail host to point at. */
|
|
218
|
+
export type DnsHostManualReason = DnsManualReason | "no_addresses" | "no_target";
|
|
219
|
+
|
|
220
|
+
/** How one record of the deployment's own host names is published (`GET /system/dns-providers/hosts`, restapi `DnsRecordPublication`). */
|
|
221
|
+
export interface DnsHostPublication {
|
|
222
|
+
type: string;
|
|
223
|
+
recordName: string;
|
|
224
|
+
recordKind: "A" | "AAAA" | "CNAME" | "MX" | "TXT" | "SRV";
|
|
225
|
+
state: DnsPublicationState;
|
|
226
|
+
manualReason?: DnsHostManualReason;
|
|
227
|
+
conflictReason?: DnsConflictReason;
|
|
228
|
+
existing?: DnsExistingRecord[];
|
|
229
|
+
/** The config that publishes it, which a takeover names. */
|
|
230
|
+
providerUid?: string;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/** The publication of each of the deployment's own host names, looked up live. Needs a trusted role; a server without DNS publishing answers 404. */
|
|
234
|
+
export function listDnsHostPublications(client?: ApiClient): Promise<DnsHostPublication[]> {
|
|
235
|
+
return withClient(client, `${BASE}/hosts`);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/** The config's latest operations, newest first. */
|
|
239
|
+
export function listDnsProviderOperations(uid: string, client?: ApiClient): Promise<DnsOperation[]> {
|
|
240
|
+
return withClient(client, `${providerPath(uid)}/operations`);
|
|
241
|
+
}
|