domainkit 0.1.0 → 0.2.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 +91 -71
- package/dist/{auth-Vi-hhXbV.mjs → auth-CZ1pZHMZ.mjs} +301 -38
- package/dist/auth-CZ1pZHMZ.mjs.map +1 -0
- package/dist/{client-BnD7QLRF.mjs → auth-CzhZ3Cuc.mjs} +235 -131
- package/dist/auth-CzhZ3Cuc.mjs.map +1 -0
- package/dist/authorization-DOg2Re1D.mjs +247 -0
- package/dist/authorization-DOg2Re1D.mjs.map +1 -0
- package/dist/canonical-json-CpvqGMcr.mjs +57 -0
- package/dist/canonical-json-CpvqGMcr.mjs.map +1 -0
- package/dist/{cloudflare-C6ryPlCZ.mjs → cloudflare-BedFw4cX.mjs} +28 -7
- package/dist/cloudflare-BedFw4cX.mjs.map +1 -0
- package/dist/cloudflare-FDl5q7Dm.d.mts +32 -0
- package/dist/cloudflare.d.mts +3 -3
- package/dist/cloudflare.mjs +2 -2
- package/dist/connection-BWrBwqjk.mjs +58 -0
- package/dist/connection-BWrBwqjk.mjs.map +1 -0
- package/dist/connection-CEqadXSm.d.mts +182 -0
- package/dist/connection-CcQokgrd.mjs +186 -0
- package/dist/connection-CcQokgrd.mjs.map +1 -0
- package/dist/connection-api-DftK7eHo.d.mts +7 -0
- package/dist/{dns-record-C_KwGPLf.mjs → dns-record-BLeqo56C.mjs} +23 -3
- package/dist/dns-record-BLeqo56C.mjs.map +1 -0
- package/dist/effect-cloudflare.d.mts +2 -2
- package/dist/effect-cloudflare.mjs +2 -2
- package/dist/effect-vercel.d.mts +2 -2
- package/dist/effect-vercel.mjs +2 -2
- package/dist/effect.d.mts +9 -46
- package/dist/effect.mjs +29 -7
- package/dist/effect.mjs.map +1 -1
- package/dist/index-B1opsgls.d.mts +159 -0
- package/dist/{index-CEzk2gbZ.d.mts → index-Ewc__bQu.d.mts} +58 -7
- package/dist/index.d.mts +169 -113
- package/dist/index.mjs +201 -138
- package/dist/index.mjs.map +1 -1
- package/dist/{resolver-BHPsADzE.d.mts → resolver-CJOR9mQ8.d.mts} +620 -263
- package/dist/resolver-CxE-VDw4.mjs +598 -0
- package/dist/resolver-CxE-VDw4.mjs.map +1 -0
- package/dist/testing.d.mts +53 -44
- package/dist/testing.mjs +379 -107
- package/dist/testing.mjs.map +1 -1
- package/dist/{vercel-DD89DCqH.mjs → vercel-CWZ33NUS.mjs} +23 -3
- package/dist/vercel-CWZ33NUS.mjs.map +1 -0
- package/dist/vercel-DgJaUX9D.d.mts +31 -0
- package/dist/vercel.d.mts +3 -3
- package/dist/vercel.mjs +2 -2
- package/dist/verify-BzpiA0Es.mjs +642 -0
- package/dist/verify-BzpiA0Es.mjs.map +1 -0
- package/dist/verify-DZWLM91T.d.mts +805 -0
- package/dist/zone-discovery-BA5I24vu.d.mts +1390 -0
- package/dist/zones-DKHTpX9C.mjs +19 -0
- package/dist/zones-DKHTpX9C.mjs.map +1 -0
- package/package.json +1 -1
- package/dist/auth-Vi-hhXbV.mjs.map +0 -1
- package/dist/client-BnD7QLRF.mjs.map +0 -1
- package/dist/cloudflare-C6ryPlCZ.mjs.map +0 -1
- package/dist/cloudflare-tdjNS8vE.d.mts +0 -23
- package/dist/dns-record-C_KwGPLf.mjs.map +0 -1
- package/dist/index-CS9Rl5LQ.d.mts +0 -71
- package/dist/manifest-CP85ePFn.d.mts +0 -583
- package/dist/receipt-CGSnfZQ6.mjs +0 -28
- package/dist/receipt-CGSnfZQ6.mjs.map +0 -1
- package/dist/resolver-tEXpKxmH.mjs +0 -200
- package/dist/resolver-tEXpKxmH.mjs.map +0 -1
- package/dist/vercel-D-E2aqb1.d.mts +0 -22
- package/dist/vercel-DD89DCqH.mjs.map +0 -1
- package/dist/verify-B80xCBh2.d.mts +0 -1407
- package/dist/verify-BjrR_4NF.mjs +0 -1401
- package/dist/verify-BjrR_4NF.mjs.map +0 -1
package/dist/verify-BjrR_4NF.mjs
DELETED
|
@@ -1,1401 +0,0 @@
|
|
|
1
|
-
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
|
|
2
|
-
import { D as parse$1, O as Error$3, T as decode$4, _ as equals, b as Service, h as decode$5, m as data, s as Observed, u as Schema$6, w as Schema$5, y as Error$4 } from "./dns-record-C_KwGPLf.mjs";
|
|
3
|
-
import { a as toAsync$2, m as Service$3, n as Service$5, o as Service$4, t as Error$5, u as Service$2, v as Service$1 } from "./resolver-tEXpKxmH.mjs";
|
|
4
|
-
import { c as Value } from "./client-BnD7QLRF.mjs";
|
|
5
|
-
import { Cause, Clock, Crypto, Effect, Layer, PlatformError, Schema, SchemaIssue, SchemaTransformation } from "effect";
|
|
6
|
-
import * as oauth from "oauth4webapi";
|
|
7
|
-
import { getDomain } from "tldts";
|
|
8
|
-
import * as DnsPacket from "@leichtgewicht/dns-packet";
|
|
9
|
-
//#region package.json
|
|
10
|
-
var version = "0.1.0";
|
|
11
|
-
//#endregion
|
|
12
|
-
//#region src/auth/connection.ts
|
|
13
|
-
var connection_exports = /* @__PURE__ */ __exportAll({
|
|
14
|
-
AuthorizationError: () => AuthorizationError$1,
|
|
15
|
-
Grant: () => Grant,
|
|
16
|
-
Schema: () => Schema$4,
|
|
17
|
-
assertGrant: () => assertGrant,
|
|
18
|
-
decode: () => decode$3,
|
|
19
|
-
encode: () => encode$2,
|
|
20
|
-
validate: () => validate$3
|
|
21
|
-
});
|
|
22
|
-
const Grant = Schema.TaggedUnion({
|
|
23
|
-
account: {},
|
|
24
|
-
domains: { domains: Schema.Array(Schema$5) }
|
|
25
|
-
});
|
|
26
|
-
/** An owner-scoped grant bound to one provider authorization. */
|
|
27
|
-
const Schema$4 = Schema.Struct({
|
|
28
|
-
authorizationId: Schema.String,
|
|
29
|
-
createdAt: Schema.DateFromString,
|
|
30
|
-
grant: Grant,
|
|
31
|
-
id: Schema.String,
|
|
32
|
-
ownerId: Schema.String
|
|
33
|
-
});
|
|
34
|
-
const decode$3 = Effect.fn("Connection.decode")((input) => Schema.decodeUnknownEffect(Schema$4)(input).pipe(Effect.mapError((cause) => new Error$3({ message: cause.message }))));
|
|
35
|
-
const validate$3 = Effect.fn("Connection.validate")((input) => Schema.decodeUnknownEffect(Schema.toType(Schema$4))(input).pipe(Effect.mapError((cause) => new Error$3({ message: cause.message }))));
|
|
36
|
-
const encode$2 = Schema.encodeSync(Schema$4);
|
|
37
|
-
var AuthorizationError$1 = class extends Schema.TaggedError()("AuthorizationError", { message: Schema.String }) {};
|
|
38
|
-
function assertGrant(connection, authorization, request) {
|
|
39
|
-
if (connection.authorizationId !== authorization.id || authorization.providerId !== request.providerId) throw new AuthorizationError$1({ message: "Connection does not grant this provider account" });
|
|
40
|
-
if (authorization.expiresAt !== null) {
|
|
41
|
-
if (Number.isNaN(authorization.expiresAt.getTime())) throw new AuthorizationError$1({ message: "Connection expiration is invalid" });
|
|
42
|
-
if (authorization.expiresAt <= (request.now ?? /* @__PURE__ */ new Date())) throw new AuthorizationError$1({ message: "Connection has expired" });
|
|
43
|
-
}
|
|
44
|
-
if (!authorization.capabilities.includes(request.capability)) throw new AuthorizationError$1({ message: `Connection lacks the ${request.capability} capability` });
|
|
45
|
-
let domain;
|
|
46
|
-
try {
|
|
47
|
-
domain = parse$1(request.domain);
|
|
48
|
-
} catch (cause) {
|
|
49
|
-
if (cause instanceof Error$3) throw cause;
|
|
50
|
-
throw new Error$3({ message: cause instanceof Error ? cause.message : String(cause) });
|
|
51
|
-
}
|
|
52
|
-
if (connection.grant._tag === "domains" && !connection.grant.domains.includes(domain)) throw new AuthorizationError$1({ message: "Connection does not grant this domain" });
|
|
53
|
-
return domain;
|
|
54
|
-
}
|
|
55
|
-
//#endregion
|
|
56
|
-
//#region src/auth/connection-lifecycle.ts
|
|
57
|
-
var connection_lifecycle_exports = /* @__PURE__ */ __exportAll({
|
|
58
|
-
Error: () => Error$2,
|
|
59
|
-
detach: () => detach,
|
|
60
|
-
updateGrant: () => updateGrant
|
|
61
|
-
});
|
|
62
|
-
var Error$2 = class extends Schema.TaggedError()("ConnectionLifecycleError", { message: Schema.String }) {};
|
|
63
|
-
/** Replace the grant on an owner binding after the host has obtained explicit consent. */
|
|
64
|
-
const updateGrant = Effect.fn("ConnectionLifecycle.updateGrant")(function* (input) {
|
|
65
|
-
const connectionStore = yield* Service$1;
|
|
66
|
-
const connection = yield* validate$3({
|
|
67
|
-
...input.connection,
|
|
68
|
-
grant: input.grant
|
|
69
|
-
});
|
|
70
|
-
yield* connectionStore.put(connection);
|
|
71
|
-
return connection;
|
|
72
|
-
});
|
|
73
|
-
/**
|
|
74
|
-
* Detach one owner binding. The final binding invokes provider revocation before deleting local
|
|
75
|
-
* credential and authorization state. The callback keeps provider-specific revocation outside the
|
|
76
|
-
* portable authorization model.
|
|
77
|
-
*/
|
|
78
|
-
function detach(input) {
|
|
79
|
-
return Effect.gen(function* () {
|
|
80
|
-
const authorizationStore = yield* Service$2;
|
|
81
|
-
const connectionStore = yield* Service$1;
|
|
82
|
-
const credentialStore = yield* Service$3;
|
|
83
|
-
const connection = yield* connectionStore.get(input.connectionId);
|
|
84
|
-
if (connection === null) return yield* new Error$2({ message: "Connection binding does not exist" });
|
|
85
|
-
const remainingBindings = (yield* connectionStore.listByAuthorizationId(connection.authorizationId)).filter(({ id }) => id !== connection.id);
|
|
86
|
-
if (remainingBindings.length > 0) {
|
|
87
|
-
yield* connectionStore.delete(connection.id);
|
|
88
|
-
return {
|
|
89
|
-
connection,
|
|
90
|
-
remainingBindings: remainingBindings.length,
|
|
91
|
-
revokedAuthorization: false
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
const authorization = yield* authorizationStore.get(connection.authorizationId);
|
|
95
|
-
if (authorization === null) {
|
|
96
|
-
yield* credentialStore.delete(connection.authorizationId);
|
|
97
|
-
yield* connectionStore.delete(connection.id);
|
|
98
|
-
return {
|
|
99
|
-
connection,
|
|
100
|
-
remainingBindings: 0,
|
|
101
|
-
revokedAuthorization: true
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
|
-
yield* input.revokeAuthorization(authorization);
|
|
105
|
-
yield* credentialStore.delete(authorization.id);
|
|
106
|
-
yield* authorizationStore.delete(authorization.id);
|
|
107
|
-
yield* connectionStore.delete(connection.id);
|
|
108
|
-
return {
|
|
109
|
-
connection,
|
|
110
|
-
remainingBindings: 0,
|
|
111
|
-
revokedAuthorization: true
|
|
112
|
-
};
|
|
113
|
-
});
|
|
114
|
-
}
|
|
115
|
-
//#endregion
|
|
116
|
-
//#region src/auth/authorization.ts
|
|
117
|
-
var authorization_exports = /* @__PURE__ */ __exportAll({
|
|
118
|
-
Capability: () => Capability,
|
|
119
|
-
Schema: () => Schema$3,
|
|
120
|
-
decode: () => decode$2,
|
|
121
|
-
encode: () => encode$1,
|
|
122
|
-
validate: () => validate$2
|
|
123
|
-
});
|
|
124
|
-
const Capability = Schema.Literals(["dns:read", "dns:write"]);
|
|
125
|
-
/** A provider credential authorization shared by one or more owner connections. */
|
|
126
|
-
const Schema$3 = Schema.Struct({
|
|
127
|
-
accountId: Schema.String,
|
|
128
|
-
capabilities: Schema.Array(Capability),
|
|
129
|
-
createdAt: Schema.DateFromString,
|
|
130
|
-
expiresAt: Schema.NullOr(Schema.DateFromString),
|
|
131
|
-
id: Schema.String,
|
|
132
|
-
kind: Schema.Literals(["oauth2", "token"]),
|
|
133
|
-
providerId: Schema.String,
|
|
134
|
-
scopes: Schema.Array(Schema.String),
|
|
135
|
-
subjectId: Schema.String
|
|
136
|
-
});
|
|
137
|
-
const decode$2 = Effect.fn("ProviderAuthorization.decode")((input) => Schema.decodeUnknownEffect(Schema$3)(input).pipe(Effect.mapError((cause) => new Error$3({ message: cause.message }))));
|
|
138
|
-
const validate$2 = Effect.fn("ProviderAuthorization.validate")((input) => Schema.decodeUnknownEffect(Schema.toType(Schema$3))(input).pipe(Effect.mapError((cause) => new Error$3({ message: cause.message }))));
|
|
139
|
-
const encode$1 = Schema.encodeSync(Schema$3);
|
|
140
|
-
//#endregion
|
|
141
|
-
//#region src/plan/canonical-json.ts
|
|
142
|
-
var canonical_json_exports = /* @__PURE__ */ __exportAll({
|
|
143
|
-
CryptoError: () => CryptoError,
|
|
144
|
-
sha256Encoded: () => sha256Encoded,
|
|
145
|
-
sha256Json: () => sha256Json,
|
|
146
|
-
sha256Text: () => sha256Text,
|
|
147
|
-
stringify: () => stringify,
|
|
148
|
-
webCryptoLayer: () => webCryptoLayer
|
|
149
|
-
});
|
|
150
|
-
var CryptoError = class extends Schema.TaggedError()("CryptoError", { message: Schema.String }) {};
|
|
151
|
-
/** Deterministic JSON serialization for already schema-encoded protocol values. */
|
|
152
|
-
function stringify(value) {
|
|
153
|
-
if (value === null || typeof value !== "object") {
|
|
154
|
-
const encoded = JSON.stringify(value);
|
|
155
|
-
if (encoded === void 0) throw new TypeError("Value is not representable as JSON");
|
|
156
|
-
return encoded;
|
|
157
|
-
}
|
|
158
|
-
if (isJsonArray(value)) return `[${value.map(stringify).join(",")}]`;
|
|
159
|
-
return `{${Object.entries(value).sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0).map(([key, entry]) => `${JSON.stringify(key)}:${stringify(entry)}`).join(",")}}`;
|
|
160
|
-
}
|
|
161
|
-
function isJsonArray(value) {
|
|
162
|
-
return Array.isArray(value);
|
|
163
|
-
}
|
|
164
|
-
const sha256Text = Effect.fn("Digest.sha256Text")(function* (value) {
|
|
165
|
-
return [...yield* (yield* Crypto.Crypto).digest("SHA-256", new TextEncoder().encode(value)).pipe(Effect.mapError((cause) => new CryptoError({ message: cause.message })))].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
166
|
-
});
|
|
167
|
-
const sha256Json = Effect.fn("Digest.sha256Json")((value) => Effect.try({
|
|
168
|
-
try: () => stringify(value),
|
|
169
|
-
catch: (cause) => new CryptoError({ message: cause instanceof Error ? cause.message : String(cause) })
|
|
170
|
-
}).pipe(Effect.flatMap(sha256Text)));
|
|
171
|
-
function sha256EncodedProgram(schema, value) {
|
|
172
|
-
return Schema.encodeEffect(schema)(value).pipe(Effect.mapError((cause) => new CryptoError({ message: cause.message })), Effect.flatMap(Schema.decodeUnknownEffect(Schema.Json)), Effect.mapError((cause) => new CryptoError({ message: cause.message })), Effect.flatMap(sha256Json));
|
|
173
|
-
}
|
|
174
|
-
const sha256Encoded = Effect.fn("Digest.sha256Encoded")(sha256EncodedProgram);
|
|
175
|
-
/** Portable Web Crypto implementation for hosts that want a ready-made Layer. */
|
|
176
|
-
const webCryptoLayer = Layer.succeed(Crypto.Crypto, Crypto.make({
|
|
177
|
-
randomBytes: (size) => crypto.getRandomValues(new Uint8Array(size)),
|
|
178
|
-
digest: (algorithm, data) => Effect.tryPromise({
|
|
179
|
-
try: async () => {
|
|
180
|
-
const bytes = new Uint8Array(data.byteLength);
|
|
181
|
-
bytes.set(data);
|
|
182
|
-
return new Uint8Array(await crypto.subtle.digest(algorithm, bytes.buffer));
|
|
183
|
-
},
|
|
184
|
-
catch: (cause) => PlatformError.systemError({
|
|
185
|
-
_tag: "Unknown",
|
|
186
|
-
module: "WebCrypto",
|
|
187
|
-
method: "digest",
|
|
188
|
-
cause
|
|
189
|
-
})
|
|
190
|
-
})
|
|
191
|
-
}));
|
|
192
|
-
//#endregion
|
|
193
|
-
//#region src/auth/oauth.ts
|
|
194
|
-
var oauth_exports = /* @__PURE__ */ __exportAll({
|
|
195
|
-
begin: () => begin,
|
|
196
|
-
complete: () => complete,
|
|
197
|
-
refresh: () => refresh,
|
|
198
|
-
revoke: () => revoke
|
|
199
|
-
});
|
|
200
|
-
function beginProgram(input) {
|
|
201
|
-
return Effect.gen(function* () {
|
|
202
|
-
const stateStore = yield* Service$4;
|
|
203
|
-
const cryptoService = yield* Crypto.Crypto;
|
|
204
|
-
const now = yield* Clock.currentTimeMillis;
|
|
205
|
-
const state = base64Url(yield* cryptoBytes(cryptoService, 32));
|
|
206
|
-
const codeVerifier = base64Url(yield* cryptoBytes(cryptoService, 32));
|
|
207
|
-
const challenge = base64Url(yield* cryptoService.digest("SHA-256", new TextEncoder().encode(codeVerifier)).pipe(Effect.mapError((cause) => new CryptoError({ message: cause.message }))));
|
|
208
|
-
const stateHash = yield* sha256Text(state);
|
|
209
|
-
const authorizationUrl = new URL(input.method.authorizationServer.authorization_endpoint);
|
|
210
|
-
authorizationUrl.search = new URLSearchParams({
|
|
211
|
-
client_id: input.client.clientId,
|
|
212
|
-
code_challenge: challenge,
|
|
213
|
-
code_challenge_method: "S256",
|
|
214
|
-
redirect_uri: input.redirectUri,
|
|
215
|
-
response_type: "code",
|
|
216
|
-
scope: input.method.scopes.join(" "),
|
|
217
|
-
state
|
|
218
|
-
}).toString();
|
|
219
|
-
yield* stateStore.put({
|
|
220
|
-
clientId: input.client.clientId,
|
|
221
|
-
codeVerifier: Value.from(codeVerifier),
|
|
222
|
-
expiresAt: new Date(now + (input.ttlMs ?? 6e5)),
|
|
223
|
-
grant: input.grant,
|
|
224
|
-
method: input.method,
|
|
225
|
-
ownerId: input.ownerId,
|
|
226
|
-
redirectUri: input.redirectUri,
|
|
227
|
-
stateHash,
|
|
228
|
-
subjectId: input.subjectId
|
|
229
|
-
});
|
|
230
|
-
return { authorizationUrl };
|
|
231
|
-
});
|
|
232
|
-
}
|
|
233
|
-
function completeProgram(input) {
|
|
234
|
-
return Effect.gen(function* () {
|
|
235
|
-
const stateStore = yield* Service$4;
|
|
236
|
-
const authorizationStore = yield* Service$2;
|
|
237
|
-
const connectionStore = yield* Service$1;
|
|
238
|
-
const credentialStore = yield* Service$3;
|
|
239
|
-
const cryptoService = yield* Crypto.Crypto;
|
|
240
|
-
const state = input.callbackUrl.searchParams.get("state");
|
|
241
|
-
if (state === null) return yield* new AuthorizationError$1({ message: "OAuth callback is missing state" });
|
|
242
|
-
const now = yield* Clock.currentTimeMillis;
|
|
243
|
-
const continuation = yield* stateStore.consume(yield* sha256Text(state), new Date(now));
|
|
244
|
-
if (continuation === null) return yield* new AuthorizationError$1({ message: "OAuth continuation is expired, unknown, or already used" });
|
|
245
|
-
if (continuation.clientId !== input.client.clientId) return yield* new AuthorizationError$1({ message: "OAuth client does not match the continuation" });
|
|
246
|
-
const as = continuation.method.authorizationServer;
|
|
247
|
-
const client = { client_id: continuation.clientId };
|
|
248
|
-
const authentication = yield* effectClientAuthentication(continuation.method, input.client);
|
|
249
|
-
const tokens = yield* providerRequest(input.providerId, async (signal) => {
|
|
250
|
-
const callbackParameters = oauth.validateAuthResponse(as, client, input.callbackUrl, state);
|
|
251
|
-
const response = await oauth.authorizationCodeGrantRequest(as, client, authentication, callbackParameters, continuation.redirectUri, continuation.codeVerifier.expose(), requestOptions(input.fetch, signal));
|
|
252
|
-
return oauth.processAuthorizationCodeResponse(as, client, response);
|
|
253
|
-
});
|
|
254
|
-
const accessToken = Value.from(tokens.access_token);
|
|
255
|
-
const subject = yield* input.resolveSubject(tokens, accessToken);
|
|
256
|
-
const existingAuthorization = yield* authorizationStore.findByProviderAccount(input.providerId, subject.accountId);
|
|
257
|
-
const authorizationId = existingAuthorization?.id ?? (yield* cryptoService.randomUUIDv4.pipe(Effect.mapError((cause) => new CryptoError({ message: cause.message }))));
|
|
258
|
-
const authorization = yield* validate$2({
|
|
259
|
-
accountId: subject.accountId,
|
|
260
|
-
capabilities: [...continuation.method.capabilities],
|
|
261
|
-
createdAt: existingAuthorization?.createdAt ?? new Date(now),
|
|
262
|
-
expiresAt: subject.expiresAt,
|
|
263
|
-
id: authorizationId,
|
|
264
|
-
kind: "oauth2",
|
|
265
|
-
providerId: input.providerId,
|
|
266
|
-
scopes: (tokens.scope ?? continuation.method.scopes.join(" ")).split(" ").filter(Boolean),
|
|
267
|
-
subjectId: continuation.subjectId
|
|
268
|
-
});
|
|
269
|
-
const existingConnection = yield* connectionStore.find(continuation.ownerId, authorization.id);
|
|
270
|
-
const connection = yield* validate$3({
|
|
271
|
-
authorizationId: authorization.id,
|
|
272
|
-
createdAt: existingConnection?.createdAt ?? new Date(now),
|
|
273
|
-
grant: continuation.grant,
|
|
274
|
-
id: existingConnection?.id ?? (yield* cryptoService.randomUUIDv4.pipe(Effect.mapError((cause) => new CryptoError({ message: cause.message })))),
|
|
275
|
-
ownerId: continuation.ownerId
|
|
276
|
-
});
|
|
277
|
-
yield* credentialStore.put(authorization.id, {
|
|
278
|
-
accessToken,
|
|
279
|
-
refreshToken: tokens.refresh_token === void 0 ? null : Value.from(tokens.refresh_token),
|
|
280
|
-
tokenType: tokens.token_type
|
|
281
|
-
});
|
|
282
|
-
yield* authorizationStore.put(authorization);
|
|
283
|
-
yield* connectionStore.put(connection);
|
|
284
|
-
return {
|
|
285
|
-
authorization,
|
|
286
|
-
connection
|
|
287
|
-
};
|
|
288
|
-
});
|
|
289
|
-
}
|
|
290
|
-
function refreshProgram(input) {
|
|
291
|
-
return Effect.gen(function* () {
|
|
292
|
-
const credentialStore = yield* Service$3;
|
|
293
|
-
const credential = yield* requireCredential(input.authorization.id, credentialStore);
|
|
294
|
-
const refreshToken = credential.refreshToken;
|
|
295
|
-
if (refreshToken === null) return yield* new AuthorizationError$1({ message: "Connection has no refresh token" });
|
|
296
|
-
const as = input.method.authorizationServer;
|
|
297
|
-
const client = { client_id: input.client.clientId };
|
|
298
|
-
const authentication = yield* effectClientAuthentication(input.method, input.client);
|
|
299
|
-
const tokens = yield* providerRequest(input.authorization.providerId, async (signal) => {
|
|
300
|
-
const response = await oauth.refreshTokenGrantRequest(as, client, authentication, refreshToken.expose(), requestOptions(input.fetch, signal));
|
|
301
|
-
return oauth.processRefreshTokenResponse(as, client, response);
|
|
302
|
-
});
|
|
303
|
-
yield* credentialStore.put(input.authorization.id, {
|
|
304
|
-
accessToken: Value.from(tokens.access_token),
|
|
305
|
-
refreshToken: tokens.refresh_token === void 0 ? credential.refreshToken : Value.from(tokens.refresh_token),
|
|
306
|
-
tokenType: tokens.token_type
|
|
307
|
-
});
|
|
308
|
-
});
|
|
309
|
-
}
|
|
310
|
-
function revokeProgram(input) {
|
|
311
|
-
return Effect.gen(function* () {
|
|
312
|
-
if (input.method.authorizationServer.revocation_endpoint === void 0) return yield* new AuthorizationError$1({ message: "Provider does not advertise token revocation" });
|
|
313
|
-
const credentialStore = yield* Service$3;
|
|
314
|
-
const credential = yield* requireCredential(input.authorization.id, credentialStore);
|
|
315
|
-
const as = input.method.authorizationServer;
|
|
316
|
-
const authentication = yield* effectClientAuthentication(input.method, input.client);
|
|
317
|
-
yield* providerRequest(input.authorization.providerId, async (signal) => {
|
|
318
|
-
const response = await oauth.revocationRequest(as, { client_id: input.client.clientId }, authentication, credential.accessToken.expose(), requestOptions(input.fetch, signal));
|
|
319
|
-
await oauth.processRevocationResponse(response);
|
|
320
|
-
});
|
|
321
|
-
yield* credentialStore.delete(input.authorization.id);
|
|
322
|
-
});
|
|
323
|
-
}
|
|
324
|
-
function effectClientAuthentication(method, client) {
|
|
325
|
-
return Effect.try({
|
|
326
|
-
try: () => clientAuthentication(method, client),
|
|
327
|
-
catch: (cause) => cause instanceof Error$3 ? cause : new Error$3({ message: "OAuth client authentication is invalid" })
|
|
328
|
-
});
|
|
329
|
-
}
|
|
330
|
-
function clientAuthentication(method, client) {
|
|
331
|
-
switch (method.clientAuth) {
|
|
332
|
-
case "none": return oauth.None();
|
|
333
|
-
case "client_secret_basic":
|
|
334
|
-
if (client.clientSecret === void 0) throw new Error$3({ message: "Client secret is required" });
|
|
335
|
-
return oauth.ClientSecretBasic(client.clientSecret.expose());
|
|
336
|
-
case "client_secret_post":
|
|
337
|
-
if (client.clientSecret === void 0) throw new Error$3({ message: "Client secret is required" });
|
|
338
|
-
return oauth.ClientSecretPost(client.clientSecret.expose());
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
function requestOptions(fetchImplementation, signal) {
|
|
342
|
-
return fetchImplementation === void 0 ? { signal } : {
|
|
343
|
-
signal,
|
|
344
|
-
[oauth.customFetch]: (url, options) => fetchImplementation(url, {
|
|
345
|
-
...options,
|
|
346
|
-
body: options.body
|
|
347
|
-
})
|
|
348
|
-
};
|
|
349
|
-
}
|
|
350
|
-
function requireCredential(connectionId, store) {
|
|
351
|
-
return store.get(connectionId).pipe(Effect.flatMap((credential) => credential === null ? Effect.fail(new AuthorizationError$1({ message: "Connection credentials are unavailable" })) : Effect.succeed(credential)));
|
|
352
|
-
}
|
|
353
|
-
function providerRequest(providerId, request) {
|
|
354
|
-
return Effect.tryPromise({
|
|
355
|
-
try: request,
|
|
356
|
-
catch: (cause) => cause instanceof AuthorizationError$1 || cause instanceof Error$3 ? cause : new Error$4({
|
|
357
|
-
cause,
|
|
358
|
-
message: safeOAuthMessage(cause),
|
|
359
|
-
operation: "oauthRequest",
|
|
360
|
-
providerId
|
|
361
|
-
})
|
|
362
|
-
});
|
|
363
|
-
}
|
|
364
|
-
function cryptoBytes(cryptoService, size) {
|
|
365
|
-
return cryptoService.randomBytes(size).pipe(Effect.mapError((cause) => new CryptoError({ message: cause.message })));
|
|
366
|
-
}
|
|
367
|
-
function base64Url(bytes) {
|
|
368
|
-
let binary = "";
|
|
369
|
-
for (const byte of bytes) binary += String.fromCharCode(byte);
|
|
370
|
-
return btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replaceAll("=", "");
|
|
371
|
-
}
|
|
372
|
-
function safeOAuthMessage(cause) {
|
|
373
|
-
return cause instanceof Error ? cause.name : "OAuth provider request failed";
|
|
374
|
-
}
|
|
375
|
-
const begin = Effect.fn("OAuth.begin")(beginProgram);
|
|
376
|
-
const complete = Effect.fn("OAuth.complete")(completeProgram);
|
|
377
|
-
const refresh = Effect.fn("OAuth.refresh")(refreshProgram);
|
|
378
|
-
const revoke = Effect.fn("OAuth.revoke")(revokeProgram);
|
|
379
|
-
//#endregion
|
|
380
|
-
//#region src/auth/manifest.ts
|
|
381
|
-
var manifest_exports = /* @__PURE__ */ __exportAll({
|
|
382
|
-
Method: () => Method,
|
|
383
|
-
Schema: () => Schema$2,
|
|
384
|
-
decode: () => decode$1
|
|
385
|
-
});
|
|
386
|
-
const Method = Schema.TaggedUnion({
|
|
387
|
-
integration: {
|
|
388
|
-
capabilities: Schema.Array(Schema.Literals(["dns:read", "dns:write"])),
|
|
389
|
-
installUrl: Schema.String,
|
|
390
|
-
tokenEndpoint: Schema.String
|
|
391
|
-
},
|
|
392
|
-
oauth2: {
|
|
393
|
-
authorizationServer: Schema.Struct({
|
|
394
|
-
authorization_endpoint: Schema.String,
|
|
395
|
-
issuer: Schema.String,
|
|
396
|
-
revocation_endpoint: Schema.optionalKey(Schema.String),
|
|
397
|
-
token_endpoint: Schema.String
|
|
398
|
-
}),
|
|
399
|
-
capabilities: Schema.Array(Schema.Literals(["dns:read", "dns:write"])),
|
|
400
|
-
clientAuth: Schema.Literals([
|
|
401
|
-
"none",
|
|
402
|
-
"client_secret_basic",
|
|
403
|
-
"client_secret_post"
|
|
404
|
-
]),
|
|
405
|
-
scopes: Schema.Array(Schema.String)
|
|
406
|
-
},
|
|
407
|
-
token: {
|
|
408
|
-
capabilities: Schema.Array(Schema.Literals(["dns:read", "dns:write"])),
|
|
409
|
-
instructionsUrl: Schema.String
|
|
410
|
-
}
|
|
411
|
-
});
|
|
412
|
-
const Schema$2 = Schema.Struct({
|
|
413
|
-
methods: Schema.Array(Method),
|
|
414
|
-
providerId: Schema.String
|
|
415
|
-
});
|
|
416
|
-
const decode$1 = Effect.fn("ProviderAuth.decode")((input) => Schema.decodeUnknownEffect(Schema$2)(input).pipe(Effect.mapError((cause) => new Error$3({ message: cause.message }))));
|
|
417
|
-
//#endregion
|
|
418
|
-
//#region src/auth/token.ts
|
|
419
|
-
var token_exports = /* @__PURE__ */ __exportAll({ connect: () => connect });
|
|
420
|
-
const connect = Effect.fn("TokenConnection.connect")(function* (input) {
|
|
421
|
-
const authorizationStore = yield* Service$2;
|
|
422
|
-
const connectionStore = yield* Service$1;
|
|
423
|
-
const credentialStore = yield* Service$3;
|
|
424
|
-
const cryptoService = yield* Crypto.Crypto;
|
|
425
|
-
const validation = yield* input.validate(input.token);
|
|
426
|
-
const createdAt = new Date(yield* Clock.currentTimeMillis);
|
|
427
|
-
const existingAuthorization = yield* authorizationStore.findByProviderAccount(input.providerId, validation.accountId);
|
|
428
|
-
const authorization = yield* validate$2({
|
|
429
|
-
accountId: validation.accountId,
|
|
430
|
-
capabilities: [...validation.capabilities],
|
|
431
|
-
createdAt: existingAuthorization?.createdAt ?? createdAt,
|
|
432
|
-
expiresAt: validation.expiresAt,
|
|
433
|
-
id: existingAuthorization?.id ?? (yield* cryptoService.randomUUIDv4.pipe(Effect.mapError((cause) => new CryptoError({ message: cause.message })))),
|
|
434
|
-
kind: "token",
|
|
435
|
-
providerId: input.providerId,
|
|
436
|
-
scopes: [...validation.scopes],
|
|
437
|
-
subjectId: input.subjectId
|
|
438
|
-
});
|
|
439
|
-
const existingConnection = yield* connectionStore.find(input.ownerId, authorization.id);
|
|
440
|
-
const connection = yield* validate$3({
|
|
441
|
-
authorizationId: authorization.id,
|
|
442
|
-
createdAt: existingConnection?.createdAt ?? createdAt,
|
|
443
|
-
grant: input.grant,
|
|
444
|
-
id: existingConnection?.id ?? (yield* cryptoService.randomUUIDv4.pipe(Effect.mapError((cause) => new CryptoError({ message: cause.message })))),
|
|
445
|
-
ownerId: input.ownerId
|
|
446
|
-
});
|
|
447
|
-
yield* credentialStore.put(authorization.id, {
|
|
448
|
-
accessToken: input.token,
|
|
449
|
-
refreshToken: null,
|
|
450
|
-
tokenType: "bearer"
|
|
451
|
-
});
|
|
452
|
-
yield* authorizationStore.put(authorization);
|
|
453
|
-
yield* connectionStore.put(connection);
|
|
454
|
-
return {
|
|
455
|
-
authorization,
|
|
456
|
-
connection
|
|
457
|
-
};
|
|
458
|
-
});
|
|
459
|
-
//#endregion
|
|
460
|
-
//#region src/discovery/zones.ts
|
|
461
|
-
var zones_exports = /* @__PURE__ */ __exportAll({ candidates: () => candidates });
|
|
462
|
-
function candidates(input) {
|
|
463
|
-
const domain = parse$1(input);
|
|
464
|
-
const registrable = getDomain(domain, { allowPrivateDomains: true });
|
|
465
|
-
if (registrable === null) throw new Error$3({ message: "Domain does not have a registrable DNS zone" });
|
|
466
|
-
const labels = domain.split(".");
|
|
467
|
-
const registrableLabels = registrable.split(".").length;
|
|
468
|
-
const values = [];
|
|
469
|
-
for (let index = 0; index <= labels.length - registrableLabels; index += 1) values.push(parse$1(labels.slice(index).join(".")));
|
|
470
|
-
return values;
|
|
471
|
-
}
|
|
472
|
-
//#endregion
|
|
473
|
-
//#region src/discovery/selection.ts
|
|
474
|
-
var selection_exports = /* @__PURE__ */ __exportAll({
|
|
475
|
-
Evidence: () => Evidence,
|
|
476
|
-
Selection: () => Selection,
|
|
477
|
-
select: () => select
|
|
478
|
-
});
|
|
479
|
-
const Evidence = Schema.Struct({
|
|
480
|
-
accountId: Schema.String,
|
|
481
|
-
connectionId: Schema.String,
|
|
482
|
-
decisiveNameserverMatch: Schema.Boolean,
|
|
483
|
-
matchedNameservers: Schema.Array(Schema$5),
|
|
484
|
-
providerId: Schema.String,
|
|
485
|
-
zone: Schema$5
|
|
486
|
-
});
|
|
487
|
-
const Selection = Schema.TaggedUnion({
|
|
488
|
-
selected: {
|
|
489
|
-
candidate: Evidence,
|
|
490
|
-
evidence: Schema.Array(Evidence),
|
|
491
|
-
reason: Schema.Literals(["explicit", "unique-nameserver-match"])
|
|
492
|
-
},
|
|
493
|
-
manual: {
|
|
494
|
-
evidence: Schema.Array(Evidence),
|
|
495
|
-
reason: Schema.Literals(["ambiguous", "unsupported"])
|
|
496
|
-
}
|
|
497
|
-
});
|
|
498
|
-
function select(input) {
|
|
499
|
-
const domain = parse$1(input.domain);
|
|
500
|
-
const zoneCandidates = new Set(candidates(domain));
|
|
501
|
-
const authoritative = nameserverSet(input.authoritativeNameservers);
|
|
502
|
-
const eligible = input.connectedZones.filter((connected) => {
|
|
503
|
-
if (!zoneCandidates.has(parse$1(connected.zone))) return false;
|
|
504
|
-
try {
|
|
505
|
-
assertGrant(connected.connection, connected.authorization, {
|
|
506
|
-
capability: "dns:read",
|
|
507
|
-
domain,
|
|
508
|
-
...input.now === void 0 ? {} : { now: input.now },
|
|
509
|
-
providerId: connected.providerId
|
|
510
|
-
});
|
|
511
|
-
return true;
|
|
512
|
-
} catch {
|
|
513
|
-
return false;
|
|
514
|
-
}
|
|
515
|
-
});
|
|
516
|
-
const evidence = eligible.map((connected) => evidenceFor(connected, authoritative));
|
|
517
|
-
if (input.explicit !== void 0) {
|
|
518
|
-
const explicitZone = parse$1(input.explicit.zone);
|
|
519
|
-
const index = eligible.findIndex((connected) => connected.providerId === input.explicit?.providerId && connected.authorization.accountId === input.explicit.accountId && parse$1(connected.zone) === explicitZone);
|
|
520
|
-
if (index < 0) throw new AuthorizationError$1({ message: "Explicit provider zone is not connected or granted" });
|
|
521
|
-
const candidate = evidence[index];
|
|
522
|
-
if (candidate === void 0) throw new Error("Eligible provider evidence is missing");
|
|
523
|
-
return {
|
|
524
|
-
_tag: "selected",
|
|
525
|
-
candidate,
|
|
526
|
-
evidence,
|
|
527
|
-
reason: "explicit"
|
|
528
|
-
};
|
|
529
|
-
}
|
|
530
|
-
const decisive = evidence.filter(({ decisiveNameserverMatch }) => decisiveNameserverMatch);
|
|
531
|
-
const candidate = decisive[0];
|
|
532
|
-
return decisive.length === 1 && candidate !== void 0 ? {
|
|
533
|
-
_tag: "selected",
|
|
534
|
-
candidate,
|
|
535
|
-
evidence,
|
|
536
|
-
reason: "unique-nameserver-match"
|
|
537
|
-
} : {
|
|
538
|
-
_tag: "manual",
|
|
539
|
-
evidence,
|
|
540
|
-
reason: decisive.length > 1 ? "ambiguous" : "unsupported"
|
|
541
|
-
};
|
|
542
|
-
}
|
|
543
|
-
function evidenceFor(connected, authoritative) {
|
|
544
|
-
const configured = new Set(nameserverSet(connected.nameservers));
|
|
545
|
-
const matchedNameservers = authoritative.filter((nameserver) => configured.has(nameserver));
|
|
546
|
-
return {
|
|
547
|
-
accountId: connected.authorization.accountId,
|
|
548
|
-
connectionId: connected.connection.id,
|
|
549
|
-
decisiveNameserverMatch: authoritative.length > 0 && matchedNameservers.length === authoritative.length,
|
|
550
|
-
matchedNameservers,
|
|
551
|
-
providerId: connected.providerId,
|
|
552
|
-
zone: parse$1(connected.zone)
|
|
553
|
-
};
|
|
554
|
-
}
|
|
555
|
-
function nameserverSet(nameservers) {
|
|
556
|
-
return [...new Set(nameservers.map(parse$1))].sort();
|
|
557
|
-
}
|
|
558
|
-
//#endregion
|
|
559
|
-
//#region src/plan/types.ts
|
|
560
|
-
var types_exports = /* @__PURE__ */ __exportAll({
|
|
561
|
-
Authorization: () => Authorization$1,
|
|
562
|
-
Operation: () => Operation$1,
|
|
563
|
-
Receipt: () => Receipt$1,
|
|
564
|
-
Schema: () => Schema$1,
|
|
565
|
-
decode: () => decode,
|
|
566
|
-
decodeReceipt: () => decodeReceipt,
|
|
567
|
-
encode: () => encode,
|
|
568
|
-
encodeReceipt: () => encodeReceipt
|
|
569
|
-
});
|
|
570
|
-
const Operation$1 = Schema.TaggedUnion({
|
|
571
|
-
create: {
|
|
572
|
-
id: Schema.String,
|
|
573
|
-
requirement: Schema$6
|
|
574
|
-
},
|
|
575
|
-
noop: {
|
|
576
|
-
id: Schema.String,
|
|
577
|
-
requirement: Schema$6,
|
|
578
|
-
ttlDrift: Schema.Boolean
|
|
579
|
-
},
|
|
580
|
-
conflict: {
|
|
581
|
-
existing: Schema.Array(Observed),
|
|
582
|
-
id: Schema.String,
|
|
583
|
-
reason: Schema.String,
|
|
584
|
-
requirement: Schema$6
|
|
585
|
-
}
|
|
586
|
-
});
|
|
587
|
-
const Schema$1 = Schema.Struct({
|
|
588
|
-
digest: Schema.String,
|
|
589
|
-
operations: Schema.Array(Operation$1),
|
|
590
|
-
providerId: Schema.String,
|
|
591
|
-
version: Schema.Literal("domainkit.dns-plan.v1"),
|
|
592
|
-
zone: Schema$5
|
|
593
|
-
});
|
|
594
|
-
const Authorization$1 = Schema.Struct({
|
|
595
|
-
allowPartial: Schema.Boolean,
|
|
596
|
-
operationIds: Schema.Array(Schema.String),
|
|
597
|
-
planDigest: Schema.String,
|
|
598
|
-
version: Schema.Literal("domainkit.plan-authorization.v1")
|
|
599
|
-
});
|
|
600
|
-
const Receipt$1 = Schema.Struct({
|
|
601
|
-
appliedAt: Schema.DateFromString,
|
|
602
|
-
operations: Schema.Array(Schema.Struct({
|
|
603
|
-
operationId: Schema.String,
|
|
604
|
-
providerRecordId: Schema.NullOr(Schema.String)
|
|
605
|
-
})),
|
|
606
|
-
planDigest: Schema.String,
|
|
607
|
-
providerId: Schema.String,
|
|
608
|
-
status: Schema.Literals(["complete", "partial"]),
|
|
609
|
-
version: Schema.Literal("domainkit.apply-receipt.v1"),
|
|
610
|
-
zone: Schema$5
|
|
611
|
-
});
|
|
612
|
-
const decode = Effect.fn("DnsPlan.decode")((input) => Schema.decodeUnknownEffect(Schema$1)(input).pipe(Effect.mapError((cause) => new Error$3({ message: cause.message }))));
|
|
613
|
-
const encode = Schema.encodeSync(Schema$1);
|
|
614
|
-
const decodeReceipt = Effect.fn("DnsPlan.decodeReceipt")((input) => Schema.decodeUnknownEffect(Receipt$1)(input).pipe(Effect.mapError((cause) => new Error$3({ message: cause.message }))));
|
|
615
|
-
const encodeReceipt = Schema.encodeSync(Receipt$1);
|
|
616
|
-
//#endregion
|
|
617
|
-
//#region src/plan/plan.ts
|
|
618
|
-
var plan_exports = /* @__PURE__ */ __exportAll({
|
|
619
|
-
AuthorizationError: () => AuthorizationError,
|
|
620
|
-
ConflictError: () => ConflictError,
|
|
621
|
-
PartialApplyError: () => PartialApplyError,
|
|
622
|
-
StaleError: () => StaleError,
|
|
623
|
-
apply: () => apply$1,
|
|
624
|
-
authorize: () => authorize$2,
|
|
625
|
-
create: () => create$1,
|
|
626
|
-
renderManualInstructions: () => renderManualInstructions,
|
|
627
|
-
validate: () => validate$1
|
|
628
|
-
});
|
|
629
|
-
const UnsignedPlan$1 = Schema.Struct({
|
|
630
|
-
operations: Schema.Array(Operation$1),
|
|
631
|
-
providerId: Schema.String,
|
|
632
|
-
version: Schema.Literal("domainkit.dns-plan.v1"),
|
|
633
|
-
zone: Schema$5
|
|
634
|
-
});
|
|
635
|
-
const RequirementDigest = Schema.Struct({ requirement: Schema$6 });
|
|
636
|
-
var ConflictError = class extends Schema.TaggedError()("PlanConflictError", { operationIds: Schema.Array(Schema.String) }) {};
|
|
637
|
-
var AuthorizationError = class extends Schema.TaggedError()("AuthorizationError", { message: Schema.String }) {};
|
|
638
|
-
var StaleError = class extends Schema.TaggedError()("StalePlanError", {
|
|
639
|
-
approvedPlanDigest: Schema.String,
|
|
640
|
-
currentPlanDigest: Schema.String
|
|
641
|
-
}) {};
|
|
642
|
-
var PartialApplyError = class extends Schema.TaggedError()("PartialApplyError", {
|
|
643
|
-
causeTag: Schema.Literals([
|
|
644
|
-
"CryptoError",
|
|
645
|
-
"InvalidInputError",
|
|
646
|
-
"Interrupted",
|
|
647
|
-
"ProviderError",
|
|
648
|
-
"StalePlanError"
|
|
649
|
-
]),
|
|
650
|
-
failedOperationId: Schema.String,
|
|
651
|
-
message: Schema.String,
|
|
652
|
-
receipt: Receipt$1
|
|
653
|
-
}) {};
|
|
654
|
-
/** Builds a deterministic DNS plan through the provider in the Effect environment. */
|
|
655
|
-
const create$1 = Effect.fn("Provisioning.create")(function* (input) {
|
|
656
|
-
const provider = yield* Service;
|
|
657
|
-
const zone = yield* decode$4(input.zone);
|
|
658
|
-
const requirements = (yield* Effect.forEach(input.requirements, decode$5, { concurrency: "unbounded" })).sort(compareRecords);
|
|
659
|
-
const existing = [...yield* provider.listRecords(zone)].sort(compareRecords);
|
|
660
|
-
const { operations } = yield* Effect.reduce(requirements, () => ({
|
|
661
|
-
operations: [],
|
|
662
|
-
projected: existing
|
|
663
|
-
}), (state, requirement) => reconcileRequirement(requirement, state.projected).pipe(Effect.map((operation) => ({
|
|
664
|
-
operations: [...state.operations, operation],
|
|
665
|
-
projected: operation._tag === "create" ? [...state.projected, operation.requirement].sort(compareRecords) : state.projected
|
|
666
|
-
}))));
|
|
667
|
-
const unsigned = {
|
|
668
|
-
operations,
|
|
669
|
-
providerId: provider.id,
|
|
670
|
-
version: "domainkit.dns-plan.v1",
|
|
671
|
-
zone
|
|
672
|
-
};
|
|
673
|
-
return {
|
|
674
|
-
...unsigned,
|
|
675
|
-
digest: yield* sha256Encoded(UnsignedPlan$1, unsigned)
|
|
676
|
-
};
|
|
677
|
-
});
|
|
678
|
-
/** Authorizes all or a selected subset of create operations in a digest-bound plan. */
|
|
679
|
-
const authorize$2 = Effect.fn("Provisioning.authorize")(function* (plan, operationIds, options = {}) {
|
|
680
|
-
yield* validate$1(plan);
|
|
681
|
-
const createIds = plan.operations.filter((operation) => operation._tag === "create").map(({ id }) => id);
|
|
682
|
-
const selected = [...new Set(operationIds ?? createIds)].sort();
|
|
683
|
-
const unknown = selected.filter((id) => !createIds.includes(id));
|
|
684
|
-
if (unknown.length > 0) return yield* new AuthorizationError({ message: `Unknown operation IDs: ${unknown.join(", ")}` });
|
|
685
|
-
if (!options.allowPartial && selected.length !== createIds.length) return yield* new AuthorizationError({ message: "Partial authorization requires allowPartial" });
|
|
686
|
-
return {
|
|
687
|
-
allowPartial: options.allowPartial ?? false,
|
|
688
|
-
operationIds: selected,
|
|
689
|
-
planDigest: plan.digest,
|
|
690
|
-
version: "domainkit.plan-authorization.v1"
|
|
691
|
-
};
|
|
692
|
-
});
|
|
693
|
-
/** Applies approved creates sequentially after full and per-operation revalidation. */
|
|
694
|
-
const apply$1 = Effect.fn("Provisioning.apply")(function* (input) {
|
|
695
|
-
const { authorization, plan } = input;
|
|
696
|
-
const provider = yield* Service;
|
|
697
|
-
yield* validate$1(plan);
|
|
698
|
-
if (authorization.planDigest !== plan.digest) return yield* new AuthorizationError({ message: "Authorization belongs to a different plan" });
|
|
699
|
-
if (provider.id !== plan.providerId) return yield* new AuthorizationError({ message: "Provider does not match the approved plan" });
|
|
700
|
-
const conflicts = plan.operations.filter((operation) => operation._tag === "conflict");
|
|
701
|
-
if (conflicts.length > 0) return yield* new ConflictError({ operationIds: conflicts.map(({ id }) => id) });
|
|
702
|
-
const creates = plan.operations.filter(Operation$1.guards.create);
|
|
703
|
-
const approved = new Set(authorization.operationIds);
|
|
704
|
-
const unknown = authorization.operationIds.filter((id) => !creates.some((operation) => operation.id === id));
|
|
705
|
-
if (unknown.length > 0) return yield* new AuthorizationError({ message: `Authorization contains unknown operations: ${unknown.join(", ")}` });
|
|
706
|
-
if (!authorization.allowPartial && creates.some(({ id }) => !approved.has(id))) return yield* new AuthorizationError({ message: "Authorization does not cover every create operation" });
|
|
707
|
-
const currentPlan = yield* create$1({
|
|
708
|
-
requirements: plan.operations.map(({ requirement }) => requirement),
|
|
709
|
-
zone: plan.zone
|
|
710
|
-
});
|
|
711
|
-
if (currentPlan.digest !== plan.digest) return yield* new StaleError({
|
|
712
|
-
approvedPlanDigest: plan.digest,
|
|
713
|
-
currentPlanDigest: currentPlan.digest
|
|
714
|
-
});
|
|
715
|
-
const operations = [];
|
|
716
|
-
for (const operation of creates) {
|
|
717
|
-
if (!approved.has(operation.id)) continue;
|
|
718
|
-
yield* Effect.gen(function* () {
|
|
719
|
-
yield* assertStillCreatable(plan, operation);
|
|
720
|
-
const result = yield* provider.createRecord(plan.zone, operation.requirement);
|
|
721
|
-
operations.push({
|
|
722
|
-
operationId: operation.id,
|
|
723
|
-
providerRecordId: result.providerRecordId
|
|
724
|
-
});
|
|
725
|
-
}).pipe(Effect.catch((failure) => operations.length === 0 ? Effect.fail(failure) : makeReceipt$1(input, operations, "partial").pipe(Effect.flatMap((receipt) => Effect.fail(new PartialApplyError({
|
|
726
|
-
causeTag: failure._tag,
|
|
727
|
-
failedOperationId: operation.id,
|
|
728
|
-
message: failureMessage(failure),
|
|
729
|
-
receipt
|
|
730
|
-
}))))), Effect.catchCause((cause) => {
|
|
731
|
-
if (!Cause.hasInterruptsOnly(cause) || operations.length === 0) return Effect.failCause(cause);
|
|
732
|
-
return makeReceipt$1(input, operations, "partial").pipe(Effect.flatMap((receipt) => Effect.fail(new PartialApplyError({
|
|
733
|
-
causeTag: "Interrupted",
|
|
734
|
-
failedOperationId: operation.id,
|
|
735
|
-
message: "DNS apply was interrupted after confirmed writes",
|
|
736
|
-
receipt
|
|
737
|
-
}))));
|
|
738
|
-
}));
|
|
739
|
-
}
|
|
740
|
-
return yield* makeReceipt$1(input, operations, "complete");
|
|
741
|
-
});
|
|
742
|
-
function renderManualInstructions(plan) {
|
|
743
|
-
return plan.operations.map((operation) => {
|
|
744
|
-
const record = operation.requirement;
|
|
745
|
-
const data$2 = Object.entries(data(record)).map(([key, value]) => `${key}=${value}`).join(" ");
|
|
746
|
-
return `${operation._tag.toUpperCase()} ${record._tag} ${record.name} ${data$2}`.trim();
|
|
747
|
-
});
|
|
748
|
-
}
|
|
749
|
-
function makeReceipt$1(input, operations, status) {
|
|
750
|
-
return Clock.currentTimeMillis.pipe(Effect.map((now) => ({
|
|
751
|
-
appliedAt: new Date(now),
|
|
752
|
-
operations,
|
|
753
|
-
planDigest: input.plan.digest,
|
|
754
|
-
providerId: input.plan.providerId,
|
|
755
|
-
status,
|
|
756
|
-
version: "domainkit.apply-receipt.v1",
|
|
757
|
-
zone: input.plan.zone
|
|
758
|
-
})));
|
|
759
|
-
}
|
|
760
|
-
function assertStillCreatable(plan, operation) {
|
|
761
|
-
return create$1({
|
|
762
|
-
requirements: [operation.requirement],
|
|
763
|
-
zone: plan.zone
|
|
764
|
-
}).pipe(Effect.flatMap((currentPlan) => {
|
|
765
|
-
const currentOperation = currentPlan.operations[0];
|
|
766
|
-
return currentOperation?._tag === "create" && currentOperation.id === operation.id ? Effect.void : Effect.fail(new StaleError({
|
|
767
|
-
approvedPlanDigest: plan.digest,
|
|
768
|
-
currentPlanDigest: currentPlan.digest
|
|
769
|
-
}));
|
|
770
|
-
}));
|
|
771
|
-
}
|
|
772
|
-
function reconcileRequirement(requirement, allExisting) {
|
|
773
|
-
return Effect.gen(function* () {
|
|
774
|
-
const sameName = allExisting.filter((record) => record.name === requirement.name);
|
|
775
|
-
const sameSet = sameName.filter((record) => record._tag !== "Opaque" && record._tag === requirement._tag);
|
|
776
|
-
const exact = sameSet.find((record) => equals(record, requirement));
|
|
777
|
-
const id = yield* sha256Encoded(RequirementDigest, { requirement });
|
|
778
|
-
if (exact !== void 0) return {
|
|
779
|
-
_tag: "noop",
|
|
780
|
-
id,
|
|
781
|
-
requirement,
|
|
782
|
-
ttlDrift: exact.ttl !== requirement.ttl
|
|
783
|
-
};
|
|
784
|
-
if (sameName.length > 0 && (requirement._tag === "CNAME" || sameName.some((record) => record._tag === "CNAME"))) return {
|
|
785
|
-
_tag: "conflict",
|
|
786
|
-
existing: sameName,
|
|
787
|
-
id,
|
|
788
|
-
reason: "CNAME records cannot coexist with other records at the same name",
|
|
789
|
-
requirement
|
|
790
|
-
};
|
|
791
|
-
if (sameSet.length > 0 && (requirement.policy === "exclusive" || sameSet.some(({ policy }) => policy === "exclusive"))) return {
|
|
792
|
-
_tag: "conflict",
|
|
793
|
-
existing: sameSet,
|
|
794
|
-
id,
|
|
795
|
-
reason: "The exclusive record set already contains incompatible data",
|
|
796
|
-
requirement
|
|
797
|
-
};
|
|
798
|
-
return {
|
|
799
|
-
_tag: "create",
|
|
800
|
-
id,
|
|
801
|
-
requirement
|
|
802
|
-
};
|
|
803
|
-
});
|
|
804
|
-
}
|
|
805
|
-
function validate$1(plan) {
|
|
806
|
-
const unsigned = {
|
|
807
|
-
operations: plan.operations,
|
|
808
|
-
providerId: plan.providerId,
|
|
809
|
-
version: plan.version,
|
|
810
|
-
zone: plan.zone
|
|
811
|
-
};
|
|
812
|
-
return sha256Encoded(UnsignedPlan$1, unsigned).pipe(Effect.flatMap((digest) => digest === plan.digest ? Effect.void : Effect.fail(new AuthorizationError({ message: "Plan digest does not match its contents" }))));
|
|
813
|
-
}
|
|
814
|
-
function failureMessage(failure) {
|
|
815
|
-
return failure._tag === "StalePlanError" ? "DNS state changed while applying the approved plan" : failure.message;
|
|
816
|
-
}
|
|
817
|
-
function compareRecords(left, right) {
|
|
818
|
-
const encode = Schema.encodeSync(Observed);
|
|
819
|
-
const toJson = Schema.decodeUnknownSync(Schema.Json);
|
|
820
|
-
const leftJson = stringify(toJson(encode(left)));
|
|
821
|
-
const rightJson = stringify(toJson(encode(right)));
|
|
822
|
-
return leftJson < rightJson ? -1 : leftJson > rightJson ? 1 : 0;
|
|
823
|
-
}
|
|
824
|
-
//#endregion
|
|
825
|
-
//#region src/plan/connection-authorization.ts
|
|
826
|
-
var connection_authorization_exports = /* @__PURE__ */ __exportAll({ authorize: () => authorize$1 });
|
|
827
|
-
const authorize$1 = Effect.fn("ConnectionAuthorization.authorize")(function* (input) {
|
|
828
|
-
const domain = yield* Effect.try({
|
|
829
|
-
try: () => assertGrant(input.connection, input.authorization, {
|
|
830
|
-
capability: "dns:write",
|
|
831
|
-
domain: input.domain,
|
|
832
|
-
providerId: input.plan.providerId
|
|
833
|
-
}),
|
|
834
|
-
catch: (cause) => cause instanceof AuthorizationError$1 || cause instanceof Error$3 ? cause : new AuthorizationError$1({ message: "Connection grant is invalid" })
|
|
835
|
-
});
|
|
836
|
-
const authorization = yield* authorize$2(input.plan, input.operationIds, input.allowPartial === void 0 ? {} : { allowPartial: input.allowPartial });
|
|
837
|
-
const selected = new Set(authorization.operationIds);
|
|
838
|
-
if (input.plan.operations.filter((operation) => operation._tag === "create" && selected.has(operation.id) && operation.requirement.name !== domain && !operation.requirement.name.endsWith(`.${domain}`)).length > 0) return yield* new AuthorizationError$1({ message: `Plan operations are outside the granted domain ${domain}` });
|
|
839
|
-
return authorization;
|
|
840
|
-
});
|
|
841
|
-
//#endregion
|
|
842
|
-
//#region src/plan/deletion.ts
|
|
843
|
-
var deletion_exports = /* @__PURE__ */ __exportAll({
|
|
844
|
-
Authorization: () => Authorization,
|
|
845
|
-
Error: () => Error$1,
|
|
846
|
-
Operation: () => Operation,
|
|
847
|
-
PartialError: () => PartialError,
|
|
848
|
-
Plan: () => Plan,
|
|
849
|
-
Receipt: () => Receipt,
|
|
850
|
-
apply: () => apply,
|
|
851
|
-
authorize: () => authorize,
|
|
852
|
-
create: () => create,
|
|
853
|
-
validate: () => validate
|
|
854
|
-
});
|
|
855
|
-
const Operation = Schema.Struct({
|
|
856
|
-
id: Schema.String,
|
|
857
|
-
providerRecordId: Schema.String,
|
|
858
|
-
record: Schema$6
|
|
859
|
-
});
|
|
860
|
-
const UnsignedPlan = Schema.Struct({
|
|
861
|
-
createdAt: Schema.DateFromString,
|
|
862
|
-
expiresAt: Schema.DateFromString,
|
|
863
|
-
operations: Schema.Array(Operation),
|
|
864
|
-
providerId: Schema.String,
|
|
865
|
-
sourcePlanDigest: Schema.String,
|
|
866
|
-
version: Schema.Literal("domainkit.deletion-plan.v1"),
|
|
867
|
-
zone: Schema$5
|
|
868
|
-
});
|
|
869
|
-
const Plan = Schema.Struct({
|
|
870
|
-
...UnsignedPlan.fields,
|
|
871
|
-
digest: Schema.String
|
|
872
|
-
});
|
|
873
|
-
const Authorization = Schema.Struct({
|
|
874
|
-
operationIds: Schema.Array(Schema.String),
|
|
875
|
-
planDigest: Schema.String,
|
|
876
|
-
version: Schema.Literal("domainkit.deletion-authorization.v1")
|
|
877
|
-
});
|
|
878
|
-
const Receipt = Schema.Struct({
|
|
879
|
-
deletedAt: Schema.DateFromString,
|
|
880
|
-
operations: Schema.Array(Schema.Struct({
|
|
881
|
-
operationId: Schema.String,
|
|
882
|
-
providerRecordId: Schema.String
|
|
883
|
-
})),
|
|
884
|
-
planDigest: Schema.String,
|
|
885
|
-
providerId: Schema.String,
|
|
886
|
-
status: Schema.Literals(["complete", "partial"]),
|
|
887
|
-
version: Schema.Literal("domainkit.deletion-receipt.v1"),
|
|
888
|
-
zone: Schema$5
|
|
889
|
-
});
|
|
890
|
-
var Error$1 = class extends Schema.TaggedError()("UnsafeDeletionError", {
|
|
891
|
-
message: Schema.String,
|
|
892
|
-
operationId: Schema.optionalKey(Schema.String)
|
|
893
|
-
}) {};
|
|
894
|
-
var PartialError = class extends Schema.TaggedError()("PartialDeletionError", {
|
|
895
|
-
failedOperationId: Schema.String,
|
|
896
|
-
message: Schema.String,
|
|
897
|
-
receipt: Receipt
|
|
898
|
-
}) {};
|
|
899
|
-
/** Build a fresh deletion plan only from records proven by a prior create receipt. */
|
|
900
|
-
const create = Effect.fn("Deletion.create")(function* (input) {
|
|
901
|
-
const provider = yield* Service;
|
|
902
|
-
yield* validate$1(input.plan);
|
|
903
|
-
if (input.receipt.planDigest !== input.plan.digest || input.receipt.providerId !== input.plan.providerId || input.receipt.zone !== input.plan.zone) return yield* new Error$1({ message: "Create receipt does not belong to the source plan" });
|
|
904
|
-
if (provider.id !== input.plan.providerId) return yield* new Error$1({ message: "Provider does not match the source receipt" });
|
|
905
|
-
const operations = yield* Effect.forEach(input.receipt.operations, (applied) => Effect.gen(function* () {
|
|
906
|
-
const source = input.plan.operations.find((operation) => operation._tag === "create" && operation.id === applied.operationId);
|
|
907
|
-
if (source === void 0 || source._tag !== "create") return yield* new Error$1({
|
|
908
|
-
message: "Receipt operation is not a created source record",
|
|
909
|
-
operationId: applied.operationId
|
|
910
|
-
});
|
|
911
|
-
if (applied.providerRecordId === null) return yield* new Error$1({
|
|
912
|
-
message: "Provider record ID is required for safe deletion",
|
|
913
|
-
operationId: applied.operationId
|
|
914
|
-
});
|
|
915
|
-
yield* assertExact(provider, input.plan.zone, applied.providerRecordId, source.requirement, source.id);
|
|
916
|
-
return {
|
|
917
|
-
id: source.id,
|
|
918
|
-
providerRecordId: applied.providerRecordId,
|
|
919
|
-
record: source.requirement
|
|
920
|
-
};
|
|
921
|
-
}));
|
|
922
|
-
const now = yield* Clock.currentTimeMillis;
|
|
923
|
-
const unsigned = {
|
|
924
|
-
createdAt: new Date(now),
|
|
925
|
-
expiresAt: new Date(now + (input.ttlMs ?? 9e5)),
|
|
926
|
-
operations,
|
|
927
|
-
providerId: input.plan.providerId,
|
|
928
|
-
sourcePlanDigest: input.plan.digest,
|
|
929
|
-
version: "domainkit.deletion-plan.v1",
|
|
930
|
-
zone: input.plan.zone
|
|
931
|
-
};
|
|
932
|
-
return {
|
|
933
|
-
...unsigned,
|
|
934
|
-
digest: yield* sha256Encoded(UnsignedPlan, unsigned)
|
|
935
|
-
};
|
|
936
|
-
});
|
|
937
|
-
/** Produce a separate digest-bound consent artifact for destructive operations. */
|
|
938
|
-
const authorize = Effect.fn("Deletion.authorize")(function* (plan, operationIds) {
|
|
939
|
-
yield* validate(plan);
|
|
940
|
-
const selected = [...new Set(operationIds ?? plan.operations.map(({ id }) => id))].sort();
|
|
941
|
-
const unknown = selected.filter((id) => !plan.operations.some((operation) => operation.id === id));
|
|
942
|
-
if (unknown.length > 0) return yield* new Error$1({ message: `Unknown deletion operation IDs: ${unknown.join(", ")}` });
|
|
943
|
-
return {
|
|
944
|
-
operationIds: selected,
|
|
945
|
-
planDigest: plan.digest,
|
|
946
|
-
version: "domainkit.deletion-authorization.v1"
|
|
947
|
-
};
|
|
948
|
-
});
|
|
949
|
-
/** Re-read every approved record before the first delete, then delete by provider record ID. */
|
|
950
|
-
const apply = Effect.fn("Deletion.apply")(function* (input) {
|
|
951
|
-
const provider = yield* Service;
|
|
952
|
-
yield* validate(input.plan);
|
|
953
|
-
if (input.authorization.planDigest !== input.plan.digest) return yield* new Error$1({ message: "Deletion authorization belongs to a different plan" });
|
|
954
|
-
if (provider.id !== input.plan.providerId) return yield* new Error$1({ message: "Provider does not match the deletion plan" });
|
|
955
|
-
const now = yield* Clock.currentTimeMillis;
|
|
956
|
-
if (input.plan.expiresAt <= new Date(now)) return yield* new Error$1({ message: "Deletion plan has expired" });
|
|
957
|
-
const selected = input.plan.operations.filter(({ id }) => input.authorization.operationIds.includes(id));
|
|
958
|
-
if (selected.length !== input.authorization.operationIds.length) return yield* new Error$1({ message: "Deletion authorization contains unknown operations" });
|
|
959
|
-
const previouslyDeleted = yield* validatePriorReceipt(input.plan, input.priorReceipt);
|
|
960
|
-
const completedIds = new Set(previouslyDeleted.map(({ operationId }) => operationId));
|
|
961
|
-
if (previouslyDeleted.some(({ operationId }) => !input.authorization.operationIds.includes(operationId))) return yield* new Error$1({ message: "Prior receipt contains an unauthorized operation" });
|
|
962
|
-
const remaining = selected.filter(({ id }) => !completedIds.has(id));
|
|
963
|
-
yield* Effect.forEach(remaining, (operation) => assertExact(provider, input.plan.zone, operation.providerRecordId, operation.record, operation.id));
|
|
964
|
-
const deleted = [...previouslyDeleted];
|
|
965
|
-
for (const operation of remaining) {
|
|
966
|
-
const failure = yield* Effect.gen(function* () {
|
|
967
|
-
yield* assertExact(provider, input.plan.zone, operation.providerRecordId, operation.record, operation.id);
|
|
968
|
-
yield* provider.deleteRecord(input.plan.zone, operation.providerRecordId);
|
|
969
|
-
deleted.push({
|
|
970
|
-
operationId: operation.id,
|
|
971
|
-
providerRecordId: operation.providerRecordId
|
|
972
|
-
});
|
|
973
|
-
}).pipe(Effect.match({
|
|
974
|
-
onFailure: (cause) => cause,
|
|
975
|
-
onSuccess: () => null
|
|
976
|
-
}));
|
|
977
|
-
if (failure !== null) {
|
|
978
|
-
if (deleted.length === 0) return yield* failure;
|
|
979
|
-
return yield* new PartialError({
|
|
980
|
-
failedOperationId: operation.id,
|
|
981
|
-
message: failure.message,
|
|
982
|
-
receipt: yield* makeReceipt(input.plan, deleted, "partial")
|
|
983
|
-
});
|
|
984
|
-
}
|
|
985
|
-
}
|
|
986
|
-
return yield* makeReceipt(input.plan, deleted, "complete");
|
|
987
|
-
});
|
|
988
|
-
function validate(plan) {
|
|
989
|
-
const unsigned = {
|
|
990
|
-
createdAt: plan.createdAt,
|
|
991
|
-
expiresAt: plan.expiresAt,
|
|
992
|
-
operations: plan.operations,
|
|
993
|
-
providerId: plan.providerId,
|
|
994
|
-
sourcePlanDigest: plan.sourcePlanDigest,
|
|
995
|
-
version: plan.version,
|
|
996
|
-
zone: plan.zone
|
|
997
|
-
};
|
|
998
|
-
return sha256Encoded(UnsignedPlan, unsigned).pipe(Effect.flatMap((digest) => digest === plan.digest ? Effect.void : Effect.fail(new Error$1({ message: "Deletion plan digest does not match its contents" }))));
|
|
999
|
-
}
|
|
1000
|
-
function validatePriorReceipt(plan, receipt) {
|
|
1001
|
-
if (receipt === void 0) return Effect.succeed([]);
|
|
1002
|
-
if (receipt.planDigest !== plan.digest || receipt.providerId !== plan.providerId || receipt.zone !== plan.zone || receipt.status !== "partial") return Effect.fail(new Error$1({ message: "Prior receipt does not belong to this deletion plan" }));
|
|
1003
|
-
for (const completed of receipt.operations) {
|
|
1004
|
-
const operation = plan.operations.find(({ id }) => id === completed.operationId);
|
|
1005
|
-
if (operation === void 0 || operation.providerRecordId !== completed.providerRecordId) return Effect.fail(new Error$1({ message: "Prior receipt operation does not match the deletion plan" }));
|
|
1006
|
-
}
|
|
1007
|
-
return Effect.succeed([...receipt.operations]);
|
|
1008
|
-
}
|
|
1009
|
-
function assertExact(provider, zone, providerRecordId, expected, operationId) {
|
|
1010
|
-
return provider.getRecord(zone, providerRecordId).pipe(Effect.flatMap((observed) => observed !== null && observed._tag !== "Opaque" && equals(observed, expected) ? Effect.void : Effect.fail(new Error$1({
|
|
1011
|
-
message: observed === null ? "Created record no longer exists" : "Created record content no longer matches its receipt",
|
|
1012
|
-
operationId
|
|
1013
|
-
}))));
|
|
1014
|
-
}
|
|
1015
|
-
function makeReceipt(plan, operations, status) {
|
|
1016
|
-
return Clock.currentTimeMillis.pipe(Effect.map((now) => ({
|
|
1017
|
-
deletedAt: new Date(now),
|
|
1018
|
-
operations,
|
|
1019
|
-
planDigest: plan.digest,
|
|
1020
|
-
providerId: plan.providerId,
|
|
1021
|
-
status,
|
|
1022
|
-
version: "domainkit.deletion-receipt.v1",
|
|
1023
|
-
zone: plan.zone
|
|
1024
|
-
})));
|
|
1025
|
-
}
|
|
1026
|
-
//#endregion
|
|
1027
|
-
//#region src/verification/dns-data.ts
|
|
1028
|
-
var dns_data_exports = /* @__PURE__ */ __exportAll({ parse: () => parse });
|
|
1029
|
-
const CanonicalAddress = Schema.String.pipe(Schema.decodeTo(Schema.String, SchemaTransformation.transform({
|
|
1030
|
-
decode: (value) => value.trim().toLowerCase(),
|
|
1031
|
-
encode: (value) => value
|
|
1032
|
-
})));
|
|
1033
|
-
const CanonicalDomain = Schema.String.pipe(Schema.decodeTo(Schema.String, SchemaTransformation.transformOrFail({
|
|
1034
|
-
decode: (value, options) => Effect.try({
|
|
1035
|
-
try: () => parse$1(value).toString(),
|
|
1036
|
-
catch: (cause) => new SchemaIssue.InvalidValue({ message: cause instanceof Error ? cause.message : String(cause) }, value, options)
|
|
1037
|
-
}),
|
|
1038
|
-
encode: (value) => Effect.succeed(value)
|
|
1039
|
-
})));
|
|
1040
|
-
const CanonicalMx = tokenizedDomain(2, 1);
|
|
1041
|
-
const CanonicalSrv = tokenizedDomain(4, 3);
|
|
1042
|
-
const CanonicalTxt = Schema.String.pipe(Schema.decodeTo(Schema.String, SchemaTransformation.transform({
|
|
1043
|
-
decode: (value) => {
|
|
1044
|
-
const trimmed = value.trim();
|
|
1045
|
-
const chunks = [...trimmed.matchAll(/"((?:\\.|[^"])*)"/g)];
|
|
1046
|
-
return chunks.length === 0 ? trimmed : chunks.map((match) => (match[1] ?? "").replace(/\\"/g, "\"").replace(/\\\\/g, "\\")).join("");
|
|
1047
|
-
},
|
|
1048
|
-
encode: (value) => value
|
|
1049
|
-
})));
|
|
1050
|
-
const CanonicalCaa = Schema.String.pipe(Schema.decodeTo(Schema.String, SchemaTransformation.transform({
|
|
1051
|
-
decode: (value) => value.trim().replace(/\s+"([\s\S]*)"$/, " $1"),
|
|
1052
|
-
encode: (value) => value
|
|
1053
|
-
})));
|
|
1054
|
-
function parse(type, input) {
|
|
1055
|
-
const schema = (() => {
|
|
1056
|
-
switch (type) {
|
|
1057
|
-
case "A":
|
|
1058
|
-
case "AAAA": return CanonicalAddress;
|
|
1059
|
-
case "CNAME":
|
|
1060
|
-
case "NS": return CanonicalDomain;
|
|
1061
|
-
case "MX": return CanonicalMx;
|
|
1062
|
-
case "SRV": return CanonicalSrv;
|
|
1063
|
-
case "TXT": return CanonicalTxt;
|
|
1064
|
-
case "CAA": return CanonicalCaa;
|
|
1065
|
-
}
|
|
1066
|
-
})();
|
|
1067
|
-
try {
|
|
1068
|
-
return Schema.decodeUnknownSync(schema)(input);
|
|
1069
|
-
} catch (cause) {
|
|
1070
|
-
throw new Error$3({ message: cause instanceof Error ? cause.message : String(cause) });
|
|
1071
|
-
}
|
|
1072
|
-
}
|
|
1073
|
-
function tokenizedDomain(length, domainIndex) {
|
|
1074
|
-
return Schema.String.pipe(Schema.decodeTo(Schema.String, SchemaTransformation.transformOrFail({
|
|
1075
|
-
decode: (value, options) => Effect.try({
|
|
1076
|
-
try: () => {
|
|
1077
|
-
const parts = value.trim().split(/\s+/);
|
|
1078
|
-
if (parts.length !== length) throw new Error("DNS record data has an invalid field count");
|
|
1079
|
-
const domain = parts[domainIndex];
|
|
1080
|
-
if (domain === void 0) throw new Error("DNS record target is missing");
|
|
1081
|
-
parts[domainIndex] = parse$1(domain);
|
|
1082
|
-
return parts.join(" ");
|
|
1083
|
-
},
|
|
1084
|
-
catch: (cause) => new SchemaIssue.InvalidValue({ message: cause instanceof Error ? cause.message : String(cause) }, value, options)
|
|
1085
|
-
}),
|
|
1086
|
-
encode: (value) => Effect.succeed(value)
|
|
1087
|
-
})));
|
|
1088
|
-
}
|
|
1089
|
-
//#endregion
|
|
1090
|
-
//#region src/verification/doh.ts
|
|
1091
|
-
var doh_exports = /* @__PURE__ */ __exportAll({
|
|
1092
|
-
layer: () => layer$1,
|
|
1093
|
-
make: () => make$1,
|
|
1094
|
-
toAsync: () => toAsync$1
|
|
1095
|
-
});
|
|
1096
|
-
function make$1(options) {
|
|
1097
|
-
const fetch = options.fetch ?? globalThis.fetch;
|
|
1098
|
-
const timeoutMs = options.timeoutMs ?? 5e3;
|
|
1099
|
-
return { resolve: Effect.fn("DnsOverHttps.resolve")((query) => Effect.tryPromise({
|
|
1100
|
-
try: (effectSignal) => request({
|
|
1101
|
-
effectSignal,
|
|
1102
|
-
fetch,
|
|
1103
|
-
query,
|
|
1104
|
-
timeoutMs,
|
|
1105
|
-
endpoint: options.endpoint
|
|
1106
|
-
}),
|
|
1107
|
-
catch: (cause) => cause instanceof Error$5 ? cause : new Error$5({
|
|
1108
|
-
cause,
|
|
1109
|
-
message: cause instanceof Error ? cause.message : "DNS resolution failed",
|
|
1110
|
-
reason: "transport"
|
|
1111
|
-
})
|
|
1112
|
-
}).pipe(Effect.flatMap((message) => decodeResolution(query, message)))) };
|
|
1113
|
-
}
|
|
1114
|
-
const layer$1 = (options) => Layer.succeed(Service$5, make$1(options));
|
|
1115
|
-
const toAsync$1 = (options) => toAsync$2(make$1(options));
|
|
1116
|
-
function decodeResolution(query, message) {
|
|
1117
|
-
return Effect.try({
|
|
1118
|
-
try: () => DnsPacket.decode(message),
|
|
1119
|
-
catch: (cause) => new Error$5({
|
|
1120
|
-
cause,
|
|
1121
|
-
message: "DoH returned an invalid DNS message",
|
|
1122
|
-
reason: "response"
|
|
1123
|
-
})
|
|
1124
|
-
}).pipe(Effect.flatMap((packet) => validatePacket(query, packet)), Effect.flatMap(decodePacket));
|
|
1125
|
-
}
|
|
1126
|
-
function decodePacket(packet) {
|
|
1127
|
-
if (packet.rcode === "NXDOMAIN") return Effect.succeed({ _tag: "nodata" });
|
|
1128
|
-
if (packet.rcode !== "NOERROR") return Effect.fail(new Error$5({
|
|
1129
|
-
message: `DNS resolver returned ${packet.rcode ?? "an unknown response code"}`,
|
|
1130
|
-
reason: "response"
|
|
1131
|
-
}));
|
|
1132
|
-
if (packet.answers === void 0 || packet.answers.length === 0) return Effect.succeed({ _tag: "nodata" });
|
|
1133
|
-
return Effect.forEach(packet.answers, decodeAnswer, { concurrency: "unbounded" }).pipe(Effect.map((answers) => {
|
|
1134
|
-
const supportedAnswers = answers.filter((answer) => answer !== null);
|
|
1135
|
-
return supportedAnswers.length === 0 ? { _tag: "nodata" } : {
|
|
1136
|
-
_tag: "answer",
|
|
1137
|
-
answers: supportedAnswers
|
|
1138
|
-
};
|
|
1139
|
-
}));
|
|
1140
|
-
}
|
|
1141
|
-
function validatePacket(query, packet) {
|
|
1142
|
-
return Effect.try({
|
|
1143
|
-
try: () => {
|
|
1144
|
-
if (packet.type !== "response") throw new Error("DoH returned a DNS query");
|
|
1145
|
-
if (packet.id !== 0) throw new Error("DoH response used an unexpected message ID");
|
|
1146
|
-
if (packet.flag_tc) throw new Error("DoH returned a truncated DNS response");
|
|
1147
|
-
const questions = packet.questions ?? [];
|
|
1148
|
-
if (questions.length !== 1) throw new Error("DoH returned an unexpected question count");
|
|
1149
|
-
const question = questions[0];
|
|
1150
|
-
if (question === void 0) throw new Error("DoH response omitted the DNS question");
|
|
1151
|
-
if (question.class !== "IN" || question.type !== query.type || parse$1(question.name) !== query.name) throw new Error("DoH response did not match the DNS question");
|
|
1152
|
-
return packet;
|
|
1153
|
-
},
|
|
1154
|
-
catch: (cause) => new Error$5({
|
|
1155
|
-
cause,
|
|
1156
|
-
message: cause instanceof Error ? cause.message : "DoH returned an invalid DNS response",
|
|
1157
|
-
reason: "response"
|
|
1158
|
-
})
|
|
1159
|
-
});
|
|
1160
|
-
}
|
|
1161
|
-
function decodeAnswer(answer) {
|
|
1162
|
-
return Effect.gen(function* () {
|
|
1163
|
-
if (answer.class !== "IN") return null;
|
|
1164
|
-
const decoded = yield* Effect.try({
|
|
1165
|
-
try: () => answerData(answer),
|
|
1166
|
-
catch: (cause) => new Error$5({
|
|
1167
|
-
cause,
|
|
1168
|
-
message: "DoH returned invalid DNS record data",
|
|
1169
|
-
reason: "response"
|
|
1170
|
-
})
|
|
1171
|
-
});
|
|
1172
|
-
if (decoded === void 0) return null;
|
|
1173
|
-
const name = yield* decode$4(answer.name);
|
|
1174
|
-
const data = yield* Effect.try({
|
|
1175
|
-
try: () => parse(decoded.type, decoded.data),
|
|
1176
|
-
catch: (cause) => new Error$5({
|
|
1177
|
-
cause,
|
|
1178
|
-
message: "DoH returned invalid DNS record data",
|
|
1179
|
-
reason: "response"
|
|
1180
|
-
})
|
|
1181
|
-
});
|
|
1182
|
-
if (answer.ttl === void 0) return yield* new Error$5({
|
|
1183
|
-
message: "DoH response omitted the DNS record TTL",
|
|
1184
|
-
reason: "response"
|
|
1185
|
-
});
|
|
1186
|
-
return {
|
|
1187
|
-
data,
|
|
1188
|
-
name,
|
|
1189
|
-
ttl: answer.ttl,
|
|
1190
|
-
type: decoded.type
|
|
1191
|
-
};
|
|
1192
|
-
}).pipe(Effect.mapError((cause) => cause instanceof Error$5 ? cause : new Error$5({
|
|
1193
|
-
cause,
|
|
1194
|
-
message: cause.message,
|
|
1195
|
-
reason: "response"
|
|
1196
|
-
})));
|
|
1197
|
-
}
|
|
1198
|
-
function answerData(answer) {
|
|
1199
|
-
switch (answer.type) {
|
|
1200
|
-
case "A":
|
|
1201
|
-
case "AAAA":
|
|
1202
|
-
case "CNAME":
|
|
1203
|
-
case "NS": return {
|
|
1204
|
-
data: answer.data,
|
|
1205
|
-
type: answer.type
|
|
1206
|
-
};
|
|
1207
|
-
case "CAA": return {
|
|
1208
|
-
data: `${answer.data.flags ?? 0} ${answer.data.tag} ${answer.data.value}`,
|
|
1209
|
-
type: answer.type
|
|
1210
|
-
};
|
|
1211
|
-
case "MX": return {
|
|
1212
|
-
data: `${requiredNumber(answer.data.preference, "MX preference")} ${answer.data.exchange}`,
|
|
1213
|
-
type: answer.type
|
|
1214
|
-
};
|
|
1215
|
-
case "SRV": return {
|
|
1216
|
-
data: [
|
|
1217
|
-
requiredNumber(answer.data.priority, "SRV priority"),
|
|
1218
|
-
requiredNumber(answer.data.weight, "SRV weight"),
|
|
1219
|
-
requiredNumber(answer.data.port, "SRV port"),
|
|
1220
|
-
answer.data.target
|
|
1221
|
-
].join(" "),
|
|
1222
|
-
type: answer.type
|
|
1223
|
-
};
|
|
1224
|
-
case "TXT": return {
|
|
1225
|
-
data: decodeTxt(answer.data),
|
|
1226
|
-
type: answer.type
|
|
1227
|
-
};
|
|
1228
|
-
default: return;
|
|
1229
|
-
}
|
|
1230
|
-
}
|
|
1231
|
-
function decodeTxt(data) {
|
|
1232
|
-
const encoded = (Array.isArray(data) ? data : [data]).map((chunk) => typeof chunk === "string" ? new TextEncoder().encode(chunk) : chunk);
|
|
1233
|
-
const length = encoded.reduce((total, chunk) => total + chunk.length, 0);
|
|
1234
|
-
const joined = new Uint8Array(length);
|
|
1235
|
-
let offset = 0;
|
|
1236
|
-
for (const chunk of encoded) {
|
|
1237
|
-
joined.set(chunk, offset);
|
|
1238
|
-
offset += chunk.length;
|
|
1239
|
-
}
|
|
1240
|
-
return new TextDecoder("utf-8", { fatal: true }).decode(joined);
|
|
1241
|
-
}
|
|
1242
|
-
function requiredNumber(value, field) {
|
|
1243
|
-
if (value === void 0) throw new Error(`DoH response omitted ${field}`);
|
|
1244
|
-
return value;
|
|
1245
|
-
}
|
|
1246
|
-
async function request(input) {
|
|
1247
|
-
const controller = new AbortController();
|
|
1248
|
-
let timedOut = false;
|
|
1249
|
-
const abortFromEffect = () => controller.abort(input.effectSignal.reason);
|
|
1250
|
-
const abortFromHost = () => controller.abort(input.query.signal?.reason);
|
|
1251
|
-
input.effectSignal.addEventListener("abort", abortFromEffect, { once: true });
|
|
1252
|
-
input.query.signal?.addEventListener("abort", abortFromHost, { once: true });
|
|
1253
|
-
const timer = setTimeout(() => {
|
|
1254
|
-
timedOut = true;
|
|
1255
|
-
controller.abort(/* @__PURE__ */ new Error("DNS resolution timed out"));
|
|
1256
|
-
}, input.timeoutMs);
|
|
1257
|
-
try {
|
|
1258
|
-
const query = Uint8Array.from(DnsPacket.encode({
|
|
1259
|
-
flags: DnsPacket.RECURSION_DESIRED,
|
|
1260
|
-
id: 0,
|
|
1261
|
-
questions: [{
|
|
1262
|
-
name: input.query.name,
|
|
1263
|
-
type: input.query.type
|
|
1264
|
-
}],
|
|
1265
|
-
type: "query"
|
|
1266
|
-
}));
|
|
1267
|
-
const response = await input.fetch(new URL(input.endpoint), {
|
|
1268
|
-
body: query,
|
|
1269
|
-
headers: {
|
|
1270
|
-
accept: "application/dns-message",
|
|
1271
|
-
"content-type": "application/dns-message"
|
|
1272
|
-
},
|
|
1273
|
-
method: "POST",
|
|
1274
|
-
signal: controller.signal
|
|
1275
|
-
});
|
|
1276
|
-
if (!response.ok) throw new Error$5({
|
|
1277
|
-
message: `DoH returned HTTP ${response.status}`,
|
|
1278
|
-
reason: "transport"
|
|
1279
|
-
});
|
|
1280
|
-
const contentType = response.headers.get("content-type")?.split(";", 1)[0]?.trim().toLowerCase();
|
|
1281
|
-
if (contentType !== "application/dns-message") throw new Error$5({
|
|
1282
|
-
message: `DoH returned unsupported content type ${contentType ?? "<missing>"}`,
|
|
1283
|
-
reason: "response"
|
|
1284
|
-
});
|
|
1285
|
-
return new Uint8Array(await response.arrayBuffer());
|
|
1286
|
-
} catch (cause) {
|
|
1287
|
-
if (timedOut) throw new Error$5({
|
|
1288
|
-
message: "DNS query timed out",
|
|
1289
|
-
reason: "timeout"
|
|
1290
|
-
});
|
|
1291
|
-
throw cause;
|
|
1292
|
-
} finally {
|
|
1293
|
-
clearTimeout(timer);
|
|
1294
|
-
input.effectSignal.removeEventListener("abort", abortFromEffect);
|
|
1295
|
-
input.query.signal?.removeEventListener("abort", abortFromHost);
|
|
1296
|
-
}
|
|
1297
|
-
}
|
|
1298
|
-
//#endregion
|
|
1299
|
-
//#region src/verification/cloudflare-doh.ts
|
|
1300
|
-
var cloudflare_doh_exports = /* @__PURE__ */ __exportAll({
|
|
1301
|
-
layer: () => layer,
|
|
1302
|
-
make: () => make,
|
|
1303
|
-
toAsync: () => toAsync
|
|
1304
|
-
});
|
|
1305
|
-
const endpoint = "https://cloudflare-dns.com/dns-query";
|
|
1306
|
-
function make(options = {}) {
|
|
1307
|
-
return make$1({
|
|
1308
|
-
...options,
|
|
1309
|
-
endpoint: options.endpoint ?? endpoint
|
|
1310
|
-
});
|
|
1311
|
-
}
|
|
1312
|
-
const layer = (options = {}) => Layer.succeed(Service$5, make(options));
|
|
1313
|
-
const toAsync = (options = {}) => toAsync$2(make(options));
|
|
1314
|
-
//#endregion
|
|
1315
|
-
//#region src/verification/verify.ts
|
|
1316
|
-
var verify_exports = /* @__PURE__ */ __exportAll({
|
|
1317
|
-
ProviderObservation: () => ProviderObservation,
|
|
1318
|
-
PublicDnsObservation: () => PublicDnsObservation,
|
|
1319
|
-
Result: () => Result,
|
|
1320
|
-
record: () => record
|
|
1321
|
-
});
|
|
1322
|
-
const ProviderObservation = Schema.TaggedUnion({
|
|
1323
|
-
match: {},
|
|
1324
|
-
missing: {},
|
|
1325
|
-
mismatch: { records: Schema.Array(Schema$6) },
|
|
1326
|
-
failure: { message: Schema.String }
|
|
1327
|
-
});
|
|
1328
|
-
const PublicDnsObservation = Schema.TaggedUnion({
|
|
1329
|
-
propagated: {},
|
|
1330
|
-
missing: {},
|
|
1331
|
-
mismatch: { answers: Schema.Array(Schema.String) },
|
|
1332
|
-
timeout: {},
|
|
1333
|
-
failure: { message: Schema.String }
|
|
1334
|
-
});
|
|
1335
|
-
const Result = Schema.Struct({
|
|
1336
|
-
provider: ProviderObservation,
|
|
1337
|
-
publicDns: PublicDnsObservation,
|
|
1338
|
-
status: Schema.Literals([
|
|
1339
|
-
"verified",
|
|
1340
|
-
"pending",
|
|
1341
|
-
"mismatch",
|
|
1342
|
-
"unavailable"
|
|
1343
|
-
])
|
|
1344
|
-
});
|
|
1345
|
-
const record = Effect.fn("Verification.record")(function* (input) {
|
|
1346
|
-
const provider = yield* Service;
|
|
1347
|
-
const resolver = yield* Service$5;
|
|
1348
|
-
const [providerObservation, publicDns] = yield* Effect.all([observeProvider(provider, input.zone, input.record).pipe(Effect.catch((failure) => Effect.succeed({
|
|
1349
|
-
_tag: "failure",
|
|
1350
|
-
message: failure.message
|
|
1351
|
-
}))), resolver.resolve({
|
|
1352
|
-
name: input.record.name,
|
|
1353
|
-
type: input.record._tag
|
|
1354
|
-
}).pipe(Effect.match({
|
|
1355
|
-
onFailure: (failure) => failure.reason === "timeout" ? { _tag: "timeout" } : {
|
|
1356
|
-
_tag: "failure",
|
|
1357
|
-
message: failure.message
|
|
1358
|
-
},
|
|
1359
|
-
onSuccess: (resolution) => observePublicDns(input.record, resolution)
|
|
1360
|
-
}))], { concurrency: "unbounded" });
|
|
1361
|
-
return {
|
|
1362
|
-
provider: providerObservation,
|
|
1363
|
-
publicDns,
|
|
1364
|
-
status: providerObservation._tag === "match" && publicDns._tag === "propagated" ? "verified" : providerObservation._tag === "failure" || publicDns._tag === "failure" || publicDns._tag === "timeout" ? "unavailable" : providerObservation._tag === "mismatch" || publicDns._tag === "mismatch" ? "mismatch" : "pending"
|
|
1365
|
-
};
|
|
1366
|
-
});
|
|
1367
|
-
function observeProvider(provider, zone, requirement) {
|
|
1368
|
-
return provider.listRecords(zone).pipe(Effect.map((records) => {
|
|
1369
|
-
if (records.some((existing) => existing._tag !== "Opaque" && equals(existing, requirement))) return { _tag: "match" };
|
|
1370
|
-
const sameSet = records.filter((existing) => existing._tag !== "Opaque" && existing.name === requirement.name && existing._tag === requirement._tag);
|
|
1371
|
-
return sameSet.length === 0 ? { _tag: "missing" } : {
|
|
1372
|
-
_tag: "mismatch",
|
|
1373
|
-
records: sameSet
|
|
1374
|
-
};
|
|
1375
|
-
}));
|
|
1376
|
-
}
|
|
1377
|
-
function observePublicDns(requirement, resolution) {
|
|
1378
|
-
if (resolution._tag === "nodata") return { _tag: "missing" };
|
|
1379
|
-
const expected = parse(requirement._tag, recordValue(requirement));
|
|
1380
|
-
return resolution.answers.some((answer) => answer.name === requirement.name && answer.type === requirement._tag && parse(answer.type, answer.data) === expected) ? { _tag: "propagated" } : {
|
|
1381
|
-
_tag: "mismatch",
|
|
1382
|
-
answers: resolution.answers.map(({ data }) => data)
|
|
1383
|
-
};
|
|
1384
|
-
}
|
|
1385
|
-
function recordValue(requirement) {
|
|
1386
|
-
const data$1 = data(requirement);
|
|
1387
|
-
switch (requirement._tag) {
|
|
1388
|
-
case "A":
|
|
1389
|
-
case "AAAA": return String(data$1.address);
|
|
1390
|
-
case "CNAME":
|
|
1391
|
-
case "NS": return String(data$1.target);
|
|
1392
|
-
case "TXT": return String(data$1.value);
|
|
1393
|
-
case "MX": return `${data$1.priority} ${data$1.exchange}`;
|
|
1394
|
-
case "CAA": return `${data$1.flags} ${data$1.tag} ${data$1.value}`;
|
|
1395
|
-
case "SRV": return `${data$1.priority} ${data$1.weight} ${data$1.port} ${data$1.target}`;
|
|
1396
|
-
}
|
|
1397
|
-
}
|
|
1398
|
-
//#endregion
|
|
1399
|
-
export { encode$1 as $, decodeReceipt as A, decode$1 as B, plan_exports as C, Receipt$1 as D, Operation$1 as E, zones_exports as F, refresh as G, begin as H, connect as I, webCryptoLayer as J, revoke as K, token_exports as L, encodeReceipt as M, types_exports as N, Schema$1 as O, selection_exports as P, decode$2 as Q, Method as R, create$1 as S, Authorization$1 as T, complete as U, manifest_exports as V, oauth_exports as W, Schema$3 as X, Capability as Y, authorization_exports as Z, deletion_exports as _, doh_exports as a, Grant as at, apply$1 as b, Authorization as c, connection_exports as ct, PartialError as d, version as dt, Error$2 as et, Plan as f, create as g, authorize as h, toAsync as i, AuthorizationError$1 as it, encode as j, decode as k, Error$1 as l, decode$3 as lt, apply as m, verify_exports as n, detach as nt, toAsync$1 as o, Schema$4 as ot, Receipt as p, canonical_json_exports as q, cloudflare_doh_exports as r, updateGrant as rt, dns_data_exports as s, assertGrant as st, record as t, connection_lifecycle_exports as tt, Operation as u, encode$2 as ut, authorize$1 as v, renderManualInstructions as w, authorize$2 as x, connection_authorization_exports as y, Schema$2 as z };
|
|
1400
|
-
|
|
1401
|
-
//# sourceMappingURL=verify-BjrR_4NF.mjs.map
|