domainkit 0.2.2 → 0.3.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 +26 -26
- package/dist/index.d.mts +639 -187
- package/dist/index.mjs +301 -300
- package/dist/index.mjs.map +1 -1
- package/dist/promise.d.mts +464 -0
- package/dist/promise.mjs +636 -0
- package/dist/promise.mjs.map +1 -0
- package/dist/{resolver-CxE-VDw4.mjs → resolver-CvK7JoJM.mjs} +401 -54
- package/dist/resolver-CvK7JoJM.mjs.map +1 -0
- package/dist/{resolver-Dc0hwV-J.d.mts → resolver-uqe1h5Vz.d.mts} +1513 -25
- package/dist/testing.d.mts +4 -5
- package/dist/testing.mjs +17 -3
- package/dist/testing.mjs.map +1 -1
- package/dist/verify-B00c81-n.mjs +2420 -0
- package/dist/verify-B00c81-n.mjs.map +1 -0
- package/dist/{verify-Dls29YC-.d.mts → verify-CkgVrwyl.d.mts} +318 -21
- package/package.json +6 -30
- package/dist/auth-BR-19zj-.mjs +0 -738
- package/dist/auth-BR-19zj-.mjs.map +0 -1
- package/dist/auth-g-waCHcz.mjs +0 -671
- package/dist/auth-g-waCHcz.mjs.map +0 -1
- package/dist/authorization-OeaSR47S.mjs +0 -284
- package/dist/authorization-OeaSR47S.mjs.map +0 -1
- package/dist/canonical-json-CpvqGMcr.mjs +0 -57
- package/dist/canonical-json-CpvqGMcr.mjs.map +0 -1
- package/dist/cloudflare-BrZPU9li.d.mts +0 -32
- package/dist/cloudflare-CGmasR8A.mjs +0 -69
- package/dist/cloudflare-CGmasR8A.mjs.map +0 -1
- package/dist/cloudflare.d.mts +0 -3
- package/dist/cloudflare.mjs +0 -2
- package/dist/connection-BfkloPZy.d.mts +0 -186
- package/dist/connection-BwtOwpH6.mjs +0 -195
- package/dist/connection-BwtOwpH6.mjs.map +0 -1
- package/dist/connection-CjNQe_E-.mjs +0 -58
- package/dist/connection-CjNQe_E-.mjs.map +0 -1
- package/dist/connection-api-DNuEZ_jR.d.mts +0 -7
- package/dist/dns-record-BLeqo56C.mjs +0 -289
- package/dist/dns-record-BLeqo56C.mjs.map +0 -1
- package/dist/effect-cloudflare.d.mts +0 -2
- package/dist/effect-cloudflare.mjs +0 -2
- package/dist/effect-vercel.d.mts +0 -2
- package/dist/effect-vercel.mjs +0 -2
- package/dist/effect.d.mts +0 -36
- package/dist/effect.mjs +0 -51
- package/dist/effect.mjs.map +0 -1
- package/dist/index-BHWRTThN.d.mts +0 -159
- package/dist/index-D3PVknwE.d.mts +0 -145
- package/dist/resolver-CxE-VDw4.mjs.map +0 -1
- package/dist/vercel-BsNDu4Np.d.mts +0 -31
- package/dist/vercel-CfoJaaCu.mjs +0 -60
- package/dist/vercel-CfoJaaCu.mjs.map +0 -1
- package/dist/vercel.d.mts +0 -3
- package/dist/vercel.mjs +0 -2
- package/dist/verify-BKbvq7IN.mjs +0 -642
- package/dist/verify-BKbvq7IN.mjs.map +0 -1
- package/dist/zone-discovery-BPnH3sLU.d.mts +0 -1397
- package/dist/zones-DKHTpX9C.mjs +0 -19
- package/dist/zones-DKHTpX9C.mjs.map +0 -1
|
@@ -0,0 +1,2420 @@
|
|
|
1
|
+
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
|
|
2
|
+
import { I as Error$3, L as Service$3, Y as Opaque, gt as decode$8, ht as Schema$4, i as toAsync$3, it as equals, n as Service$2, nt as decode$9, pt as Service$1, t as Error$4, tt as data, ut as candidates, vt as parse$1, x as authorize$1, yt as Error$2 } from "./resolver-CvK7JoJM.mjs";
|
|
3
|
+
import { Clock, Context, Crypto, Data, Effect, Layer, Option, Schema, SchemaIssue, SchemaTransformation } from "effect";
|
|
4
|
+
import * as oauth from "oauth4webapi";
|
|
5
|
+
import * as DnsPacket from "@leichtgewicht/dns-packet";
|
|
6
|
+
//#region package.json
|
|
7
|
+
var version = "0.3.0";
|
|
8
|
+
//#endregion
|
|
9
|
+
//#region src/auth/provider-context.ts
|
|
10
|
+
var provider_context_exports = /* @__PURE__ */ __exportAll({
|
|
11
|
+
Envelope: () => Envelope,
|
|
12
|
+
codec: () => codec,
|
|
13
|
+
decode: () => decode$7,
|
|
14
|
+
encode: () => encode$4
|
|
15
|
+
});
|
|
16
|
+
/** A provider-owned, versioned, non-secret context value persisted by the host. */
|
|
17
|
+
const Envelope = Schema.Struct({
|
|
18
|
+
value: Schema.Json,
|
|
19
|
+
version: Schema.String
|
|
20
|
+
});
|
|
21
|
+
/** Build an adapter-owned context codec from a schema and stable version identifier. */
|
|
22
|
+
function codec(version, schema) {
|
|
23
|
+
return {
|
|
24
|
+
decode: Effect.fn("ProviderContext.decode")((envelope) => envelope.version !== version ? Effect.fail(new Error$2({ message: `Unsupported provider context version: ${envelope.version}` })) : Schema.decodeUnknownEffect(schema)(envelope.value).pipe(Effect.mapError((cause) => new Error$2({ message: cause.message })))),
|
|
25
|
+
encode: Effect.fn("ProviderContext.encode")((value) => Schema.encodeEffect(schema)(value).pipe(Effect.flatMap((encoded) => Schema.decodeUnknownEffect(Schema.Json)(encoded)), Effect.map((encoded) => ({
|
|
26
|
+
value: encoded,
|
|
27
|
+
version
|
|
28
|
+
})), Effect.mapError((cause) => new Error$2({ message: cause.message })))),
|
|
29
|
+
version
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
const decode$7 = Effect.fn("ProviderContext.Envelope.decode")((input) => Schema.decodeUnknownEffect(Envelope)(input).pipe(Effect.mapError((cause) => new Error$2({ message: cause.message }))));
|
|
33
|
+
const encode$4 = Schema.encodeSync(Envelope);
|
|
34
|
+
//#endregion
|
|
35
|
+
//#region src/auth/authorization.ts
|
|
36
|
+
var authorization_exports = /* @__PURE__ */ __exportAll({
|
|
37
|
+
Capability: () => Capability,
|
|
38
|
+
CapabilityEvidence: () => CapabilityEvidence,
|
|
39
|
+
Evidence: () => Evidence$1,
|
|
40
|
+
EvidenceSchema: () => EvidenceSchema,
|
|
41
|
+
Revocation: () => Revocation,
|
|
42
|
+
Schema: () => Schema$3,
|
|
43
|
+
decode: () => decode$6,
|
|
44
|
+
encode: () => encode$3,
|
|
45
|
+
evidenceFor: () => evidenceFor$1,
|
|
46
|
+
validate: () => validate$1
|
|
47
|
+
});
|
|
48
|
+
const Capability = Schema.Literals(["dns:read", "dns:write"]);
|
|
49
|
+
const Evidence$1 = Data.taggedEnum();
|
|
50
|
+
const EvidenceSchema = Schema.TaggedUnion({
|
|
51
|
+
Declared: {},
|
|
52
|
+
Exercised: { observedAt: Schema.DateFromString },
|
|
53
|
+
Introspected: { observedAt: Schema.DateFromString }
|
|
54
|
+
});
|
|
55
|
+
const CapabilityEvidence = Schema.Struct({
|
|
56
|
+
capability: Capability,
|
|
57
|
+
evidence: EvidenceSchema
|
|
58
|
+
});
|
|
59
|
+
const Revocation = Schema.TaggedUnion({
|
|
60
|
+
Active: {},
|
|
61
|
+
Pending: { requestedAt: Schema.DateFromString }
|
|
62
|
+
});
|
|
63
|
+
/** A provider credential authorization shared by one or more owner connections. */
|
|
64
|
+
const Schema$3 = Schema.Struct({
|
|
65
|
+
authorizedById: Schema.String,
|
|
66
|
+
capabilityEvidence: Schema.Array(CapabilityEvidence),
|
|
67
|
+
createdAt: Schema.DateFromString,
|
|
68
|
+
expiresAt: Schema.NullOr(Schema.DateFromString),
|
|
69
|
+
id: Schema.String,
|
|
70
|
+
method: Schema.Literals([
|
|
71
|
+
"integration",
|
|
72
|
+
"oauth2",
|
|
73
|
+
"token"
|
|
74
|
+
]),
|
|
75
|
+
providerAccountId: Schema.String,
|
|
76
|
+
providerContext: Envelope,
|
|
77
|
+
providerId: Schema.String,
|
|
78
|
+
requiredCapabilities: Schema.Array(Capability),
|
|
79
|
+
revocation: Revocation,
|
|
80
|
+
scopes: Schema.Array(Schema.String)
|
|
81
|
+
});
|
|
82
|
+
const decode$6 = Effect.fn("ProviderAuthorization.decode")((input) => Schema.decodeUnknownEffect(Schema$3)(input).pipe(Effect.mapError((cause) => new Error$2({ message: cause.message }))));
|
|
83
|
+
const validate$1 = Effect.fn("ProviderAuthorization.validate")((input) => Schema.decodeUnknownEffect(Schema.toType(Schema$3))(input).pipe(Effect.mapError((cause) => new Error$2({ message: cause.message }))));
|
|
84
|
+
const encode$3 = Schema.encodeSync(Schema$3);
|
|
85
|
+
function evidenceFor$1(authorization, capability) {
|
|
86
|
+
return authorization.capabilityEvidence.find((item) => item.capability === capability)?.evidence;
|
|
87
|
+
}
|
|
88
|
+
//#endregion
|
|
89
|
+
//#region src/auth/connection.ts
|
|
90
|
+
const Grant = Schema.TaggedUnion({
|
|
91
|
+
account: { excludedDomains: Schema.Array(Schema$4) },
|
|
92
|
+
domains: { domains: Schema.Array(Schema$4) }
|
|
93
|
+
});
|
|
94
|
+
/** Returns whether a grant includes one parsed domain. */
|
|
95
|
+
const coversDomain = (grant, domain) => grant._tag === "account" ? !grant.excludedDomains.includes(domain) : grant.domains.includes(domain);
|
|
96
|
+
/** Expands a grant to include the requested domains. */
|
|
97
|
+
const includeDomains = (current, requested) => {
|
|
98
|
+
if (current === void 0) return requested;
|
|
99
|
+
if (current._tag === "account" && requested._tag === "account") return {
|
|
100
|
+
_tag: "account",
|
|
101
|
+
excludedDomains: current.excludedDomains.filter((domain) => requested.excludedDomains.includes(domain))
|
|
102
|
+
};
|
|
103
|
+
if (current._tag === "account" && requested._tag === "domains") return {
|
|
104
|
+
_tag: "account",
|
|
105
|
+
excludedDomains: current.excludedDomains.filter((domain) => !requested.domains.includes(domain))
|
|
106
|
+
};
|
|
107
|
+
if (requested._tag === "account") {
|
|
108
|
+
const includedDomains = current._tag === "domains" ? current.domains : [];
|
|
109
|
+
return {
|
|
110
|
+
_tag: "account",
|
|
111
|
+
excludedDomains: requested.excludedDomains.filter((domain) => !includedDomains.includes(domain))
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
if (current._tag === "account") return current;
|
|
115
|
+
return {
|
|
116
|
+
_tag: "domains",
|
|
117
|
+
domains: [.../* @__PURE__ */ new Set([...current.domains, ...requested.domains])]
|
|
118
|
+
};
|
|
119
|
+
};
|
|
120
|
+
/** Contracts a grant so it no longer includes one parsed domain. */
|
|
121
|
+
const removeDomain$1 = (grant, domain) => grant._tag === "account" ? {
|
|
122
|
+
_tag: "account",
|
|
123
|
+
excludedDomains: [.../* @__PURE__ */ new Set([...grant.excludedDomains, domain])]
|
|
124
|
+
} : {
|
|
125
|
+
_tag: "domains",
|
|
126
|
+
domains: grant.domains.filter((candidate) => candidate !== domain)
|
|
127
|
+
};
|
|
128
|
+
/** An owner-scoped grant bound to one provider authorization. */
|
|
129
|
+
const Schema$2 = Schema.Struct({
|
|
130
|
+
authorizationId: Schema.String,
|
|
131
|
+
createdAt: Schema.DateFromString,
|
|
132
|
+
grant: Grant,
|
|
133
|
+
id: Schema.String,
|
|
134
|
+
ownerId: Schema.String
|
|
135
|
+
});
|
|
136
|
+
const decode$5 = Effect.fn("Connection.decode")((input) => Schema.decodeUnknownEffect(Schema$2)(input).pipe(Effect.mapError((cause) => new Error$2({ message: cause.message }))));
|
|
137
|
+
const validate = Effect.fn("Connection.validate")((input) => Schema.decodeUnknownEffect(Schema.toType(Schema$2))(input).pipe(Effect.mapError((cause) => new Error$2({ message: cause.message }))));
|
|
138
|
+
const encode$2 = Schema.encodeSync(Schema$2);
|
|
139
|
+
var AuthorizationError = class extends Schema.TaggedError()("AuthorizationError", {
|
|
140
|
+
category: Schema.Literal("authorization"),
|
|
141
|
+
message: Schema.String,
|
|
142
|
+
operation: Schema.String,
|
|
143
|
+
retry: Schema.Literals([
|
|
144
|
+
"never",
|
|
145
|
+
"after-user-action",
|
|
146
|
+
"safe",
|
|
147
|
+
"unknown"
|
|
148
|
+
])
|
|
149
|
+
}) {};
|
|
150
|
+
const authorizationError = (message, operation) => new AuthorizationError({
|
|
151
|
+
category: "authorization",
|
|
152
|
+
message,
|
|
153
|
+
operation,
|
|
154
|
+
retry: "after-user-action"
|
|
155
|
+
});
|
|
156
|
+
function assertGrant(connection, authorization, request) {
|
|
157
|
+
if (connection.authorizationId !== authorization.id || authorization.providerId !== request.providerId || authorization.revocation._tag !== "Active") throw authorizationError("Connection does not grant this provider account", "Connection.assertGrant");
|
|
158
|
+
if (authorization.expiresAt !== null) {
|
|
159
|
+
if (Number.isNaN(authorization.expiresAt.getTime())) throw authorizationError("Connection expiration is invalid", "Connection.assertGrant");
|
|
160
|
+
if (authorization.expiresAt <= (request.now ?? /* @__PURE__ */ new Date())) throw authorizationError("Connection has expired", "Connection.assertGrant");
|
|
161
|
+
}
|
|
162
|
+
if (!authorization.requiredCapabilities.includes(request.capability)) throw authorizationError(`Connection lacks the ${request.capability} capability`, "Connection.assertGrant");
|
|
163
|
+
if (evidenceFor$1(authorization, request.capability) === void 0) throw authorizationError(`Connection has no evidence for the ${request.capability} capability`, "Connection.assertGrant");
|
|
164
|
+
let domain;
|
|
165
|
+
try {
|
|
166
|
+
domain = parse$1(request.domain);
|
|
167
|
+
} catch (cause) {
|
|
168
|
+
if (cause instanceof Error$2) throw cause;
|
|
169
|
+
throw new Error$2({ message: cause instanceof Error ? cause.message : String(cause) });
|
|
170
|
+
}
|
|
171
|
+
if (!coversDomain(connection.grant, domain)) throw authorizationError("Connection does not grant this domain", "Connection.assertGrant");
|
|
172
|
+
return domain;
|
|
173
|
+
}
|
|
174
|
+
//#endregion
|
|
175
|
+
//#region src/auth/connect.ts
|
|
176
|
+
const Method$1 = Data.taggedEnum();
|
|
177
|
+
const StartResult = Data.taggedEnum();
|
|
178
|
+
var Error$1 = class extends Schema.TaggedError()("ConnectionError", {
|
|
179
|
+
category: Schema.Literals([
|
|
180
|
+
"authorization",
|
|
181
|
+
"provider",
|
|
182
|
+
"validation"
|
|
183
|
+
]),
|
|
184
|
+
message: Schema.String,
|
|
185
|
+
operation: Schema.String,
|
|
186
|
+
retry: Schema.Literals([
|
|
187
|
+
"never",
|
|
188
|
+
"after-user-action",
|
|
189
|
+
"safe",
|
|
190
|
+
"unknown"
|
|
191
|
+
])
|
|
192
|
+
}) {};
|
|
193
|
+
const RemoveDomainResult = Data.taggedEnum();
|
|
194
|
+
const persist = Effect.fn("Connection.persist")(function* (input) {
|
|
195
|
+
const repository = yield* Service$1;
|
|
196
|
+
const crypto = yield* Crypto.Crypto;
|
|
197
|
+
const now = new Date(yield* Clock.currentTimeMillis);
|
|
198
|
+
const missingCapability = input.requiredCapabilities.find((capability) => !input.authentication.capabilityEvidence.some((item) => item.capability === capability));
|
|
199
|
+
if (missingCapability !== void 0) return yield* new Error$1({
|
|
200
|
+
category: "authorization",
|
|
201
|
+
message: `Provider authorization lacks evidence for ${missingCapability}`,
|
|
202
|
+
operation: "Connection.persist",
|
|
203
|
+
retry: "after-user-action"
|
|
204
|
+
});
|
|
205
|
+
const existing = yield* repository.findByProviderAccount(input.providerId, input.authentication.providerAccountId);
|
|
206
|
+
const authorizationId = existing?.authorization.id ?? (yield* crypto.randomUUIDv4.pipe(Effect.mapError((cause) => new Error$1({
|
|
207
|
+
category: "validation",
|
|
208
|
+
message: cause.message,
|
|
209
|
+
operation: "Connection.persist",
|
|
210
|
+
retry: "safe"
|
|
211
|
+
}))));
|
|
212
|
+
const existingBinding = existing?.bindings.find((binding) => binding.ownerId === input.ownerId);
|
|
213
|
+
const binding = {
|
|
214
|
+
authorizationId,
|
|
215
|
+
createdAt: existingBinding?.createdAt ?? now,
|
|
216
|
+
grant: includeDomains(existingBinding?.grant, input.grant),
|
|
217
|
+
id: existingBinding?.id ?? (yield* crypto.randomUUIDv4.pipe(Effect.mapError((cause) => new Error$1({
|
|
218
|
+
category: "validation",
|
|
219
|
+
message: cause.message,
|
|
220
|
+
operation: "Connection.persist",
|
|
221
|
+
retry: "safe"
|
|
222
|
+
})))),
|
|
223
|
+
ownerId: input.ownerId
|
|
224
|
+
};
|
|
225
|
+
const aggregate = {
|
|
226
|
+
authorization: {
|
|
227
|
+
authorizedById: input.authorizedById,
|
|
228
|
+
capabilityEvidence: [...input.authentication.capabilityEvidence],
|
|
229
|
+
createdAt: existing?.authorization.createdAt ?? now,
|
|
230
|
+
expiresAt: input.authentication.expiresAt,
|
|
231
|
+
id: authorizationId,
|
|
232
|
+
method: input.method,
|
|
233
|
+
providerAccountId: input.authentication.providerAccountId,
|
|
234
|
+
providerContext: input.authentication.providerContext,
|
|
235
|
+
providerId: input.providerId,
|
|
236
|
+
requiredCapabilities: [...input.requiredCapabilities],
|
|
237
|
+
revocation: { _tag: "Active" },
|
|
238
|
+
scopes: [...input.authentication.scopes]
|
|
239
|
+
},
|
|
240
|
+
bindings: [...existing?.bindings.filter((candidate) => candidate.id !== binding.id) ?? [], binding],
|
|
241
|
+
credential: input.authentication.credential
|
|
242
|
+
};
|
|
243
|
+
return yield* repository.connect(aggregate);
|
|
244
|
+
});
|
|
245
|
+
const start$1 = Effect.fn("Connection.start")(function* (input) {
|
|
246
|
+
if (input.method._tag === "Interactive") {
|
|
247
|
+
const crypto = yield* Crypto.Crypto;
|
|
248
|
+
const now = yield* Clock.currentTimeMillis;
|
|
249
|
+
const continuationId = yield* crypto.randomUUIDv4.pipe(Effect.mapError((cause) => new Error$1({
|
|
250
|
+
category: "validation",
|
|
251
|
+
message: cause.message,
|
|
252
|
+
operation: "Connection.start",
|
|
253
|
+
retry: "safe"
|
|
254
|
+
})));
|
|
255
|
+
const started = yield* input.method.flow.start(continuationId);
|
|
256
|
+
yield* input.method.continuations.put({
|
|
257
|
+
authorizedById: input.authorizedById,
|
|
258
|
+
expiresAt: new Date(now + (input.method.ttlMs ?? 6e5)),
|
|
259
|
+
grant: input.grant,
|
|
260
|
+
id: continuationId,
|
|
261
|
+
method: input.method.flow.method,
|
|
262
|
+
ownerId: input.ownerId,
|
|
263
|
+
payload: started.payload,
|
|
264
|
+
providerId: input.method.flow.providerId,
|
|
265
|
+
requiredCapabilities: [...input.method.flow.requiredCapabilities]
|
|
266
|
+
});
|
|
267
|
+
return StartResult.Redirect({
|
|
268
|
+
authorizationUrl: started.authorizationUrl,
|
|
269
|
+
continuationId
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
const authentication = yield* input.method.authenticate(input.method.token);
|
|
273
|
+
const aggregate = yield* persist({
|
|
274
|
+
authentication,
|
|
275
|
+
authorizedById: input.authorizedById,
|
|
276
|
+
grant: input.grant,
|
|
277
|
+
method: "token",
|
|
278
|
+
ownerId: input.ownerId,
|
|
279
|
+
providerId: input.method.providerId,
|
|
280
|
+
requiredCapabilities: input.method.requiredCapabilities
|
|
281
|
+
});
|
|
282
|
+
return StartResult.Connected({ aggregate });
|
|
283
|
+
});
|
|
284
|
+
const complete$1 = Effect.fn("Connection.complete")(function* (input) {
|
|
285
|
+
const continuation = yield* input.continuations.consume(input.continuationId, new Date(yield* Clock.currentTimeMillis));
|
|
286
|
+
if (continuation === null) return yield* new Error$1({
|
|
287
|
+
category: "authorization",
|
|
288
|
+
message: "Connection continuation is expired, unknown, or already consumed",
|
|
289
|
+
operation: "Connection.complete",
|
|
290
|
+
retry: "after-user-action"
|
|
291
|
+
});
|
|
292
|
+
if (continuation.providerId !== input.flow.providerId || continuation.method !== input.flow.method) return yield* new Error$1({
|
|
293
|
+
category: "authorization",
|
|
294
|
+
message: "Connection continuation does not match the provider flow",
|
|
295
|
+
operation: "Connection.complete",
|
|
296
|
+
retry: "never"
|
|
297
|
+
});
|
|
298
|
+
const authentication = yield* input.flow.complete(continuation.payload, input.callbackUrl);
|
|
299
|
+
return yield* persist({
|
|
300
|
+
authentication,
|
|
301
|
+
authorizedById: continuation.authorizedById,
|
|
302
|
+
grant: continuation.grant,
|
|
303
|
+
method: continuation.method,
|
|
304
|
+
ownerId: continuation.ownerId,
|
|
305
|
+
providerId: continuation.providerId,
|
|
306
|
+
requiredCapabilities: continuation.requiredCapabilities
|
|
307
|
+
});
|
|
308
|
+
});
|
|
309
|
+
/** Extends one owner binding without repeating provider authentication. */
|
|
310
|
+
const extend = Effect.fn("Connection.extend")(function* (input) {
|
|
311
|
+
const repository = yield* Service$1;
|
|
312
|
+
const aggregate = yield* repository.getByConnectionId(input.connectionId);
|
|
313
|
+
const binding = aggregate?.bindings.find(({ id }) => id === input.connectionId);
|
|
314
|
+
if (aggregate === null || binding === void 0 || binding.ownerId !== input.ownerId) return yield* new Error$1({
|
|
315
|
+
category: "authorization",
|
|
316
|
+
message: "Connection does not belong to this owner",
|
|
317
|
+
operation: "Connection.extend",
|
|
318
|
+
retry: "never"
|
|
319
|
+
});
|
|
320
|
+
if (aggregate.authorization.revocation._tag !== "Active") return yield* new Error$1({
|
|
321
|
+
category: "authorization",
|
|
322
|
+
message: "Provider authorization is awaiting revocation",
|
|
323
|
+
operation: "Connection.extend",
|
|
324
|
+
retry: "after-user-action"
|
|
325
|
+
});
|
|
326
|
+
const now = new Date(yield* Clock.currentTimeMillis);
|
|
327
|
+
if (aggregate.authorization.expiresAt !== null && (Number.isNaN(aggregate.authorization.expiresAt.getTime()) || aggregate.authorization.expiresAt <= now)) return yield* new Error$1({
|
|
328
|
+
category: "authorization",
|
|
329
|
+
message: "Provider authorization has expired",
|
|
330
|
+
operation: "Connection.extend",
|
|
331
|
+
retry: "after-user-action"
|
|
332
|
+
});
|
|
333
|
+
return yield* repository.bind(aggregate.authorization.id, {
|
|
334
|
+
...binding,
|
|
335
|
+
grant: includeDomains(binding.grant, input.grant)
|
|
336
|
+
});
|
|
337
|
+
});
|
|
338
|
+
/** Removes one domain from an owner binding without revoking provider authorization. */
|
|
339
|
+
const removeDomain = Effect.fn("Connection.removeDomain")(function* (input) {
|
|
340
|
+
const repository = yield* Service$1;
|
|
341
|
+
const aggregate = yield* repository.getByConnectionId(input.connectionId);
|
|
342
|
+
const binding = aggregate?.bindings.find(({ id }) => id === input.connectionId);
|
|
343
|
+
if (aggregate === null || binding === void 0 || binding.ownerId !== input.ownerId) return yield* new Error$1({
|
|
344
|
+
category: "authorization",
|
|
345
|
+
message: "Connection does not belong to this owner",
|
|
346
|
+
operation: "Connection.removeDomain",
|
|
347
|
+
retry: "never"
|
|
348
|
+
});
|
|
349
|
+
const domain = yield* decode$8(input.domain).pipe(Effect.mapError((cause) => new Error$1({
|
|
350
|
+
category: "validation",
|
|
351
|
+
message: cause.message,
|
|
352
|
+
operation: "Connection.removeDomain",
|
|
353
|
+
retry: "never"
|
|
354
|
+
})));
|
|
355
|
+
const updated = yield* repository.modifyBinding(input.connectionId, (current) => {
|
|
356
|
+
if (!coversDomain(current.grant, domain)) return {
|
|
357
|
+
binding: current,
|
|
358
|
+
value: false
|
|
359
|
+
};
|
|
360
|
+
return {
|
|
361
|
+
binding: {
|
|
362
|
+
...current,
|
|
363
|
+
grant: removeDomain$1(current.grant, domain)
|
|
364
|
+
},
|
|
365
|
+
value: true
|
|
366
|
+
};
|
|
367
|
+
});
|
|
368
|
+
return updated.value ? RemoveDomainResult.Removed({ aggregate: updated.aggregate }) : RemoveDomainResult.AlreadyRemoved({ aggregate: updated.aggregate });
|
|
369
|
+
});
|
|
370
|
+
//#endregion
|
|
371
|
+
//#region src/auth/diagnostic.ts
|
|
372
|
+
var diagnostic_exports = /* @__PURE__ */ __exportAll({
|
|
373
|
+
Category: () => Category,
|
|
374
|
+
Fields: () => Fields,
|
|
375
|
+
RetryAdvice: () => RetryAdvice,
|
|
376
|
+
from: () => from
|
|
377
|
+
});
|
|
378
|
+
const Category = Schema.Literals([
|
|
379
|
+
"authorization",
|
|
380
|
+
"configuration",
|
|
381
|
+
"provider",
|
|
382
|
+
"storage",
|
|
383
|
+
"validation"
|
|
384
|
+
]);
|
|
385
|
+
const RetryAdvice = Schema.Literals([
|
|
386
|
+
"never",
|
|
387
|
+
"after-user-action",
|
|
388
|
+
"safe",
|
|
389
|
+
"unknown"
|
|
390
|
+
]);
|
|
391
|
+
const Fields = {
|
|
392
|
+
category: Category,
|
|
393
|
+
message: Schema.String,
|
|
394
|
+
operation: Schema.String,
|
|
395
|
+
retry: RetryAdvice
|
|
396
|
+
};
|
|
397
|
+
function from(error) {
|
|
398
|
+
return {
|
|
399
|
+
category: error.category,
|
|
400
|
+
message: error.message,
|
|
401
|
+
operation: error.operation,
|
|
402
|
+
retry: error.retry,
|
|
403
|
+
tag: error._tag
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
//#endregion
|
|
407
|
+
//#region src/auth/manifest.ts
|
|
408
|
+
var manifest_exports = /* @__PURE__ */ __exportAll({
|
|
409
|
+
Method: () => Method,
|
|
410
|
+
Schema: () => Schema$1,
|
|
411
|
+
decode: () => decode$4
|
|
412
|
+
});
|
|
413
|
+
const Method = Schema.TaggedUnion({
|
|
414
|
+
integration: {
|
|
415
|
+
capabilities: Schema.Array(Schema.Literals(["dns:read", "dns:write"])),
|
|
416
|
+
installUrl: Schema.String,
|
|
417
|
+
tokenEndpoint: Schema.String
|
|
418
|
+
},
|
|
419
|
+
oauth2: {
|
|
420
|
+
authorizationServer: Schema.Struct({
|
|
421
|
+
authorization_endpoint: Schema.String,
|
|
422
|
+
issuer: Schema.String,
|
|
423
|
+
revocation_endpoint: Schema.optionalKey(Schema.String),
|
|
424
|
+
token_endpoint: Schema.String
|
|
425
|
+
}),
|
|
426
|
+
capabilities: Schema.Array(Schema.Literals(["dns:read", "dns:write"])),
|
|
427
|
+
clientAuth: Schema.Literals([
|
|
428
|
+
"none",
|
|
429
|
+
"client_secret_basic",
|
|
430
|
+
"client_secret_post"
|
|
431
|
+
]),
|
|
432
|
+
scopes: Schema.Array(Schema.String)
|
|
433
|
+
},
|
|
434
|
+
token: {
|
|
435
|
+
capabilities: Schema.Array(Schema.Literals(["dns:read", "dns:write"])),
|
|
436
|
+
instructionsUrl: Schema.String
|
|
437
|
+
}
|
|
438
|
+
});
|
|
439
|
+
const Schema$1 = Schema.Struct({
|
|
440
|
+
methods: Schema.Array(Method),
|
|
441
|
+
providerId: Schema.String
|
|
442
|
+
});
|
|
443
|
+
const decode$4 = Effect.fn("ProviderAuth.decode")((input) => Schema.decodeUnknownEffect(Schema$1)(input).pipe(Effect.mapError((cause) => new Error$2({ message: cause.message }))));
|
|
444
|
+
//#endregion
|
|
445
|
+
//#region src/auth/secret.ts
|
|
446
|
+
var secret_exports = /* @__PURE__ */ __exportAll({
|
|
447
|
+
Value: () => Value,
|
|
448
|
+
make: () => make$6
|
|
449
|
+
});
|
|
450
|
+
/** A deliberately non-serializable credential value. */
|
|
451
|
+
var Value = class Value {
|
|
452
|
+
#value;
|
|
453
|
+
constructor(value) {
|
|
454
|
+
this.#value = value;
|
|
455
|
+
}
|
|
456
|
+
static from(value) {
|
|
457
|
+
if (value.length === 0) throw new Error("Secret values cannot be empty");
|
|
458
|
+
return new Value(value);
|
|
459
|
+
}
|
|
460
|
+
expose() {
|
|
461
|
+
return this.#value;
|
|
462
|
+
}
|
|
463
|
+
toJSON() {
|
|
464
|
+
return "[REDACTED]";
|
|
465
|
+
}
|
|
466
|
+
toString() {
|
|
467
|
+
return "[REDACTED]";
|
|
468
|
+
}
|
|
469
|
+
};
|
|
470
|
+
const make$6 = Value.from;
|
|
471
|
+
//#endregion
|
|
472
|
+
//#region src/discovery/selection.ts
|
|
473
|
+
var selection_exports = /* @__PURE__ */ __exportAll({
|
|
474
|
+
Evidence: () => Evidence,
|
|
475
|
+
Selection: () => Selection,
|
|
476
|
+
select: () => select
|
|
477
|
+
});
|
|
478
|
+
const Evidence = Schema.Struct({
|
|
479
|
+
accountId: Schema.String,
|
|
480
|
+
connectionId: Schema.String,
|
|
481
|
+
decisiveNameserverMatch: Schema.Boolean,
|
|
482
|
+
matchedNameservers: Schema.Array(Schema$4),
|
|
483
|
+
providerId: Schema.String,
|
|
484
|
+
zone: Schema$4
|
|
485
|
+
});
|
|
486
|
+
const Selection = Schema.TaggedUnion({
|
|
487
|
+
selected: {
|
|
488
|
+
candidate: Evidence,
|
|
489
|
+
evidence: Schema.Array(Evidence),
|
|
490
|
+
reason: Schema.Literals(["explicit", "unique-nameserver-match"])
|
|
491
|
+
},
|
|
492
|
+
manual: {
|
|
493
|
+
evidence: Schema.Array(Evidence),
|
|
494
|
+
reason: Schema.Literals(["ambiguous", "unsupported"])
|
|
495
|
+
}
|
|
496
|
+
});
|
|
497
|
+
function select(input) {
|
|
498
|
+
const domain = parse$1(input.domain);
|
|
499
|
+
const zoneCandidates = new Set(candidates(domain));
|
|
500
|
+
const authoritative = nameserverSet(input.authoritativeNameservers);
|
|
501
|
+
const eligible = input.connectedZones.filter((connected) => {
|
|
502
|
+
if (!zoneCandidates.has(parse$1(connected.zone))) return false;
|
|
503
|
+
try {
|
|
504
|
+
assertGrant(connected.connection, connected.authorization, {
|
|
505
|
+
capability: "dns:read",
|
|
506
|
+
domain,
|
|
507
|
+
...input.now === void 0 ? {} : { now: input.now },
|
|
508
|
+
providerId: connected.providerId
|
|
509
|
+
});
|
|
510
|
+
return true;
|
|
511
|
+
} catch {
|
|
512
|
+
return false;
|
|
513
|
+
}
|
|
514
|
+
});
|
|
515
|
+
const evidence = eligible.map((connected) => evidenceFor(connected, authoritative));
|
|
516
|
+
if (input.explicit !== void 0) {
|
|
517
|
+
const explicitZone = parse$1(input.explicit.zone);
|
|
518
|
+
const index = eligible.findIndex((connected) => connected.providerId === input.explicit?.providerId && connected.authorization.providerAccountId === input.explicit.accountId && parse$1(connected.zone) === explicitZone);
|
|
519
|
+
if (index < 0) throw authorizationError("Explicit provider zone is not connected or granted", "ProviderDiscovery.select");
|
|
520
|
+
const candidate = evidence[index];
|
|
521
|
+
if (candidate === void 0) throw new Error("Eligible provider evidence is missing");
|
|
522
|
+
return {
|
|
523
|
+
_tag: "selected",
|
|
524
|
+
candidate,
|
|
525
|
+
evidence,
|
|
526
|
+
reason: "explicit"
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
const decisive = evidence.filter(({ decisiveNameserverMatch }) => decisiveNameserverMatch);
|
|
530
|
+
const candidate = decisive[0];
|
|
531
|
+
return decisive.length === 1 && candidate !== void 0 ? {
|
|
532
|
+
_tag: "selected",
|
|
533
|
+
candidate,
|
|
534
|
+
evidence,
|
|
535
|
+
reason: "unique-nameserver-match"
|
|
536
|
+
} : {
|
|
537
|
+
_tag: "manual",
|
|
538
|
+
evidence,
|
|
539
|
+
reason: decisive.length > 1 ? "ambiguous" : "unsupported"
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
function evidenceFor(connected, authoritative) {
|
|
543
|
+
const configured = new Set(nameserverSet(connected.nameservers));
|
|
544
|
+
const matchedNameservers = authoritative.filter((nameserver) => configured.has(nameserver));
|
|
545
|
+
return {
|
|
546
|
+
accountId: connected.authorization.providerAccountId,
|
|
547
|
+
connectionId: connected.connection.id,
|
|
548
|
+
decisiveNameserverMatch: authoritative.length > 0 && matchedNameservers.length === authoritative.length,
|
|
549
|
+
matchedNameservers,
|
|
550
|
+
providerId: connected.providerId,
|
|
551
|
+
zone: parse$1(connected.zone)
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
function nameserverSet(nameservers) {
|
|
555
|
+
return [...new Set(nameservers.map(parse$1))].sort();
|
|
556
|
+
}
|
|
557
|
+
//#endregion
|
|
558
|
+
//#region src/plan/connection-authorization.ts
|
|
559
|
+
var connection_authorization_exports = /* @__PURE__ */ __exportAll({ authorize: () => authorize });
|
|
560
|
+
const authorize = Effect.fn("ConnectionAuthorization.authorize")(function* (input) {
|
|
561
|
+
const domain = yield* Effect.try({
|
|
562
|
+
try: () => assertGrant(input.connection, input.authorization, {
|
|
563
|
+
capability: "dns:write",
|
|
564
|
+
domain: input.domain,
|
|
565
|
+
providerId: input.plan.providerId
|
|
566
|
+
}),
|
|
567
|
+
catch: (cause) => cause instanceof AuthorizationError || cause instanceof Error$2 ? cause : authorizationError("Connection grant is invalid", "ConnectionAuthorization.authorize")
|
|
568
|
+
});
|
|
569
|
+
const authorization = yield* authorize$1(input.plan, input.operationIds, input.allowPartial === void 0 ? {} : { allowPartial: input.allowPartial });
|
|
570
|
+
const selected = new Set(authorization.operationIds);
|
|
571
|
+
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* authorizationError(`Plan operations are outside the granted domain ${domain}`, "ConnectionAuthorization.authorize");
|
|
572
|
+
return authorization;
|
|
573
|
+
});
|
|
574
|
+
//#endregion
|
|
575
|
+
//#region src/auth/authorization-code.ts
|
|
576
|
+
const start = Effect.fn("AuthorizationCode.start")(function* (input) {
|
|
577
|
+
const crypto = yield* Crypto.Crypto;
|
|
578
|
+
const codeVerifier = base64Url(yield* randomBytes(crypto, 32));
|
|
579
|
+
const challenge = base64Url(yield* digest(crypto, codeVerifier));
|
|
580
|
+
const authorizationUrl = new URL(input.method.authorizationServer.authorization_endpoint);
|
|
581
|
+
authorizationUrl.search = new URLSearchParams({
|
|
582
|
+
client_id: input.client.clientId,
|
|
583
|
+
code_challenge: challenge,
|
|
584
|
+
code_challenge_method: "S256",
|
|
585
|
+
redirect_uri: input.redirectUri,
|
|
586
|
+
response_type: "code",
|
|
587
|
+
scope: input.method.scopes.join(" "),
|
|
588
|
+
state: input.state
|
|
589
|
+
}).toString();
|
|
590
|
+
return {
|
|
591
|
+
authorizationUrl,
|
|
592
|
+
payload: make$6(JSON.stringify({
|
|
593
|
+
codeVerifier,
|
|
594
|
+
redirectUri: input.redirectUri,
|
|
595
|
+
state: input.state
|
|
596
|
+
}))
|
|
597
|
+
};
|
|
598
|
+
});
|
|
599
|
+
const complete = Effect.fn("AuthorizationCode.complete")(function* (input) {
|
|
600
|
+
const payload = yield* decodePayload(input.payload);
|
|
601
|
+
if (input.callbackUrl.searchParams.get("state") !== payload.state) return yield* failure$5("Authorization callback state does not match its continuation");
|
|
602
|
+
const server = input.method.authorizationServer;
|
|
603
|
+
const client = { client_id: input.client.clientId };
|
|
604
|
+
const authentication = yield* clientAuthentication(input.method, input.client);
|
|
605
|
+
return yield* Effect.tryPromise({
|
|
606
|
+
try: async (signal) => {
|
|
607
|
+
const callback = oauth.validateAuthResponse(server, client, input.callbackUrl, payload.state);
|
|
608
|
+
const response = await oauth.authorizationCodeGrantRequest(server, client, authentication, callback, payload.redirectUri, payload.codeVerifier, requestOptions(input.fetch, signal));
|
|
609
|
+
return oauth.processAuthorizationCodeResponse(server, client, response);
|
|
610
|
+
},
|
|
611
|
+
catch: (cause) => new Error$1({
|
|
612
|
+
category: "provider",
|
|
613
|
+
message: cause instanceof globalThis.Error ? cause.name : "OAuth provider request failed",
|
|
614
|
+
operation: "AuthorizationCode.complete",
|
|
615
|
+
retry: "unknown"
|
|
616
|
+
})
|
|
617
|
+
});
|
|
618
|
+
});
|
|
619
|
+
const PayloadSchema = Schema.Struct({
|
|
620
|
+
codeVerifier: Schema.String,
|
|
621
|
+
redirectUri: Schema.String,
|
|
622
|
+
state: Schema.String
|
|
623
|
+
});
|
|
624
|
+
function decodePayload(payload) {
|
|
625
|
+
return Schema.decodeUnknownEffect(Schema.fromJsonString(PayloadSchema))(payload.expose()).pipe(Effect.mapError(() => new Error$1({
|
|
626
|
+
category: "authorization",
|
|
627
|
+
message: "OAuth continuation payload is invalid",
|
|
628
|
+
operation: "AuthorizationCode",
|
|
629
|
+
retry: "after-user-action"
|
|
630
|
+
})));
|
|
631
|
+
}
|
|
632
|
+
function clientAuthentication(method, client) {
|
|
633
|
+
switch (method.clientAuth) {
|
|
634
|
+
case "none": return Effect.succeed(oauth.None());
|
|
635
|
+
case "client_secret_basic": return client.clientSecret === void 0 ? failure$5("OAuth client secret is required") : Effect.succeed(oauth.ClientSecretBasic(client.clientSecret.expose()));
|
|
636
|
+
case "client_secret_post": return client.clientSecret === void 0 ? failure$5("OAuth client secret is required") : Effect.succeed(oauth.ClientSecretPost(client.clientSecret.expose()));
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
function requestOptions(fetchImplementation, signal) {
|
|
640
|
+
return fetchImplementation === void 0 ? { signal } : {
|
|
641
|
+
signal,
|
|
642
|
+
[oauth.customFetch]: (url, options) => fetchImplementation(url, {
|
|
643
|
+
...options,
|
|
644
|
+
body: options.body
|
|
645
|
+
})
|
|
646
|
+
};
|
|
647
|
+
}
|
|
648
|
+
const failure$5 = (message) => Effect.fail(new Error$1({
|
|
649
|
+
category: "authorization",
|
|
650
|
+
message,
|
|
651
|
+
operation: "AuthorizationCode",
|
|
652
|
+
retry: "after-user-action"
|
|
653
|
+
}));
|
|
654
|
+
function randomBytes(crypto, size) {
|
|
655
|
+
return crypto.randomBytes(size).pipe(Effect.mapError((cause) => new Error$1({
|
|
656
|
+
category: "validation",
|
|
657
|
+
message: cause.message,
|
|
658
|
+
operation: "AuthorizationCode.start",
|
|
659
|
+
retry: "safe"
|
|
660
|
+
})));
|
|
661
|
+
}
|
|
662
|
+
function digest(crypto, value) {
|
|
663
|
+
return crypto.digest("SHA-256", new TextEncoder().encode(value)).pipe(Effect.mapError((cause) => new Error$1({
|
|
664
|
+
category: "validation",
|
|
665
|
+
message: cause.message,
|
|
666
|
+
operation: "AuthorizationCode.start",
|
|
667
|
+
retry: "safe"
|
|
668
|
+
})));
|
|
669
|
+
}
|
|
670
|
+
function base64Url(bytes) {
|
|
671
|
+
let binary = "";
|
|
672
|
+
for (const byte of bytes) binary += String.fromCharCode(byte);
|
|
673
|
+
return btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replaceAll("=", "");
|
|
674
|
+
}
|
|
675
|
+
//#endregion
|
|
676
|
+
//#region src/providers/cloudflare/protocol.ts
|
|
677
|
+
const ErrorDetail$1 = Schema.Struct({
|
|
678
|
+
code: Schema.Number,
|
|
679
|
+
message: Schema.String
|
|
680
|
+
});
|
|
681
|
+
const ResultInfo = Schema.Struct({
|
|
682
|
+
count: Schema.optionalKey(Schema.Number),
|
|
683
|
+
page: Schema.optionalKey(Schema.Number),
|
|
684
|
+
per_page: Schema.optionalKey(Schema.Number),
|
|
685
|
+
total_count: Schema.optionalKey(Schema.Number),
|
|
686
|
+
total_pages: Schema.optionalKey(Schema.Number)
|
|
687
|
+
});
|
|
688
|
+
const Message = Schema.Struct({
|
|
689
|
+
code: Schema.optionalKey(Schema.Number),
|
|
690
|
+
message: Schema.String
|
|
691
|
+
});
|
|
692
|
+
const BaseEnvelope = Schema.Struct({
|
|
693
|
+
errors: Schema.Array(ErrorDetail$1),
|
|
694
|
+
messages: Schema.Array(Message),
|
|
695
|
+
success: Schema.Boolean
|
|
696
|
+
});
|
|
697
|
+
const Account = Schema.Struct({
|
|
698
|
+
id: Schema.String,
|
|
699
|
+
name: Schema.String
|
|
700
|
+
});
|
|
701
|
+
const Zone = Schema.Struct({
|
|
702
|
+
account: Account,
|
|
703
|
+
id: Schema.String,
|
|
704
|
+
name: Schema.String,
|
|
705
|
+
name_servers: Schema.Array(Schema.String),
|
|
706
|
+
status: Schema.optionalKey(Schema.String)
|
|
707
|
+
});
|
|
708
|
+
const Record$1 = Schema.Struct({
|
|
709
|
+
content: Schema.optionalKey(Schema.String),
|
|
710
|
+
data: Schema.optionalKey(Schema.Unknown),
|
|
711
|
+
id: Schema.String,
|
|
712
|
+
name: Schema.String,
|
|
713
|
+
priority: Schema.optionalKey(Schema.Number),
|
|
714
|
+
proxied: Schema.optionalKey(Schema.Boolean),
|
|
715
|
+
ttl: Schema.Number,
|
|
716
|
+
type: Schema.String
|
|
717
|
+
});
|
|
718
|
+
const Token = Schema.Struct({
|
|
719
|
+
expires_on: Schema.optionalKey(Schema.String),
|
|
720
|
+
id: Schema.String,
|
|
721
|
+
not_before: Schema.optionalKey(Schema.String),
|
|
722
|
+
status: Schema.Literals([
|
|
723
|
+
"active",
|
|
724
|
+
"disabled",
|
|
725
|
+
"expired"
|
|
726
|
+
])
|
|
727
|
+
});
|
|
728
|
+
const ZoneListEnvelope = Schema.Struct({
|
|
729
|
+
errors: Schema.Array(ErrorDetail$1),
|
|
730
|
+
messages: Schema.Array(Message),
|
|
731
|
+
result: Schema.Array(Zone),
|
|
732
|
+
result_info: Schema.optionalKey(ResultInfo),
|
|
733
|
+
success: Schema.Boolean
|
|
734
|
+
});
|
|
735
|
+
const RecordListEnvelope$1 = Schema.Struct({
|
|
736
|
+
errors: Schema.Array(ErrorDetail$1),
|
|
737
|
+
messages: Schema.Array(Message),
|
|
738
|
+
result: Schema.Array(Record$1),
|
|
739
|
+
result_info: Schema.optionalKey(ResultInfo),
|
|
740
|
+
success: Schema.Boolean
|
|
741
|
+
});
|
|
742
|
+
const RecordEnvelope = Schema.Struct({
|
|
743
|
+
errors: Schema.Array(ErrorDetail$1),
|
|
744
|
+
messages: Schema.Array(Message),
|
|
745
|
+
result: Record$1,
|
|
746
|
+
success: Schema.Boolean
|
|
747
|
+
});
|
|
748
|
+
const TokenEnvelope = Schema.Struct({
|
|
749
|
+
errors: Schema.Array(ErrorDetail$1),
|
|
750
|
+
messages: Schema.Array(Message),
|
|
751
|
+
result: Token,
|
|
752
|
+
success: Schema.Boolean
|
|
753
|
+
});
|
|
754
|
+
//#endregion
|
|
755
|
+
//#region src/providers/cloudflare/records.ts
|
|
756
|
+
const Data$1 = Schema.Struct({
|
|
757
|
+
flags: Schema.optionalKey(Schema.Number),
|
|
758
|
+
port: Schema.optionalKey(Schema.Number),
|
|
759
|
+
priority: Schema.optionalKey(Schema.Number),
|
|
760
|
+
tag: Schema.optionalKey(Schema.String),
|
|
761
|
+
target: Schema.optionalKey(Schema.String),
|
|
762
|
+
value: Schema.optionalKey(Schema.String),
|
|
763
|
+
weight: Schema.optionalKey(Schema.Number)
|
|
764
|
+
});
|
|
765
|
+
const decode$3 = Effect.fn("CloudflareRecords.decode")((record) => Effect.gen(function* () {
|
|
766
|
+
const name = yield* decode$8(record.name).pipe(Effect.match({
|
|
767
|
+
onFailure: () => null,
|
|
768
|
+
onSuccess: (value) => value
|
|
769
|
+
}));
|
|
770
|
+
if (name === null) return yield* decodeOpaque$1(record);
|
|
771
|
+
const common = {
|
|
772
|
+
metadata: {
|
|
773
|
+
ownership: "provider",
|
|
774
|
+
provenance: record.proxied === true ? "cloudflare:proxied" : "cloudflare",
|
|
775
|
+
purpose: "existing DNS record"
|
|
776
|
+
},
|
|
777
|
+
name,
|
|
778
|
+
policy: record.type === "CNAME" ? "exclusive" : "append",
|
|
779
|
+
ttl: record.ttl === 1 ? null : record.ttl
|
|
780
|
+
};
|
|
781
|
+
const content = () => record.content === void 0 ? Effect.fail(failure$4("decodeRecord", `Cloudflare ${record.type} record has no content`)) : Effect.succeed(record.content);
|
|
782
|
+
switch (record.type) {
|
|
783
|
+
case "A":
|
|
784
|
+
case "AAAA": return yield* decode$9({
|
|
785
|
+
...common,
|
|
786
|
+
_tag: record.type,
|
|
787
|
+
address: yield* content()
|
|
788
|
+
}).pipe(Effect.mapError((cause) => failure$4("decodeRecord", cause.message)));
|
|
789
|
+
case "CNAME":
|
|
790
|
+
case "NS": return yield* decode$9({
|
|
791
|
+
...common,
|
|
792
|
+
_tag: record.type,
|
|
793
|
+
target: yield* content()
|
|
794
|
+
}).pipe(Effect.mapError((cause) => failure$4("decodeRecord", cause.message)));
|
|
795
|
+
case "TXT": return yield* decode$9({
|
|
796
|
+
...common,
|
|
797
|
+
_tag: "TXT",
|
|
798
|
+
value: yield* content()
|
|
799
|
+
}).pipe(Effect.mapError((cause) => failure$4("decodeRecord", cause.message)));
|
|
800
|
+
case "MX": return yield* decode$9({
|
|
801
|
+
...common,
|
|
802
|
+
_tag: "MX",
|
|
803
|
+
exchange: yield* content(),
|
|
804
|
+
priority: record.priority
|
|
805
|
+
}).pipe(Effect.mapError((cause) => failure$4("decodeRecord", cause.message)));
|
|
806
|
+
case "CAA": {
|
|
807
|
+
const data = yield* decodeData(record);
|
|
808
|
+
return yield* decode$9({
|
|
809
|
+
...common,
|
|
810
|
+
_tag: "CAA",
|
|
811
|
+
...data
|
|
812
|
+
}).pipe(Effect.mapError((cause) => failure$4("decodeRecord", cause.message)));
|
|
813
|
+
}
|
|
814
|
+
case "SRV": {
|
|
815
|
+
const data = yield* decodeData(record);
|
|
816
|
+
return yield* decode$9({
|
|
817
|
+
...common,
|
|
818
|
+
_tag: "SRV",
|
|
819
|
+
...data
|
|
820
|
+
}).pipe(Effect.mapError((cause) => failure$4("decodeRecord", cause.message)));
|
|
821
|
+
}
|
|
822
|
+
default: return yield* decodeOpaque$1(record);
|
|
823
|
+
}
|
|
824
|
+
}));
|
|
825
|
+
const decodeOpaque$1 = Effect.fn("CloudflareRecords.decodeOpaque")((record) => Schema.decodeUnknownEffect(Opaque)({
|
|
826
|
+
_tag: "Opaque",
|
|
827
|
+
name: record.name,
|
|
828
|
+
providerRecordId: record.id,
|
|
829
|
+
providerType: record.type
|
|
830
|
+
}).pipe(Effect.mapError((cause) => failure$4("decodeRecord", cause.message))));
|
|
831
|
+
function encode$1(record) {
|
|
832
|
+
const common = {
|
|
833
|
+
name: record.name,
|
|
834
|
+
proxied: false,
|
|
835
|
+
ttl: record.ttl ?? 1,
|
|
836
|
+
type: record._tag
|
|
837
|
+
};
|
|
838
|
+
switch (record._tag) {
|
|
839
|
+
case "A":
|
|
840
|
+
case "AAAA": return {
|
|
841
|
+
...common,
|
|
842
|
+
content: record.address
|
|
843
|
+
};
|
|
844
|
+
case "CNAME":
|
|
845
|
+
case "NS": return {
|
|
846
|
+
...common,
|
|
847
|
+
content: record.target
|
|
848
|
+
};
|
|
849
|
+
case "TXT": return {
|
|
850
|
+
...common,
|
|
851
|
+
content: record.value
|
|
852
|
+
};
|
|
853
|
+
case "MX": return {
|
|
854
|
+
...common,
|
|
855
|
+
content: record.exchange,
|
|
856
|
+
priority: record.priority
|
|
857
|
+
};
|
|
858
|
+
case "CAA": return {
|
|
859
|
+
...common,
|
|
860
|
+
data: {
|
|
861
|
+
flags: record.flags,
|
|
862
|
+
tag: record.tag,
|
|
863
|
+
value: record.value
|
|
864
|
+
}
|
|
865
|
+
};
|
|
866
|
+
case "SRV": return {
|
|
867
|
+
...common,
|
|
868
|
+
data: {
|
|
869
|
+
port: record.port,
|
|
870
|
+
priority: record.priority,
|
|
871
|
+
target: record.target,
|
|
872
|
+
weight: record.weight
|
|
873
|
+
}
|
|
874
|
+
};
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
const decodeData = Effect.fn("CloudflareRecords.decodeData")((record) => Schema.decodeUnknownEffect(Data$1)(record.data).pipe(Effect.mapError((cause) => failure$4("decodeRecord", cause.message))));
|
|
878
|
+
function failure$4(operation, message, fields = {}) {
|
|
879
|
+
return new Error$3({
|
|
880
|
+
message,
|
|
881
|
+
operation,
|
|
882
|
+
providerId: "cloudflare",
|
|
883
|
+
reason: fields.reason ?? "response"
|
|
884
|
+
});
|
|
885
|
+
}
|
|
886
|
+
//#endregion
|
|
887
|
+
//#region src/providers/cloudflare/client.ts
|
|
888
|
+
var client_exports$1 = /* @__PURE__ */ __exportAll({
|
|
889
|
+
discoverAuthorizationAccount: () => discoverAuthorizationAccount,
|
|
890
|
+
discovery: () => discovery$1,
|
|
891
|
+
make: () => make$5,
|
|
892
|
+
validateDomainToken: () => validateDomainToken
|
|
893
|
+
});
|
|
894
|
+
/** Exposes Cloudflare zone lookup as the optional provider discovery capability. */
|
|
895
|
+
function discovery$1(client) {
|
|
896
|
+
return {
|
|
897
|
+
listZones: (name) => client.listZones({ name }).pipe(Effect.map((zones) => zones.map((zone) => ({
|
|
898
|
+
...zone,
|
|
899
|
+
status: zone.status === "active" || zone.status === "pending" ? zone.status : "unknown"
|
|
900
|
+
})))),
|
|
901
|
+
provider: client
|
|
902
|
+
};
|
|
903
|
+
}
|
|
904
|
+
const makeTransport = (options) => {
|
|
905
|
+
const fetch = options.fetch ?? globalThis.fetch;
|
|
906
|
+
const baseUrl = (options.baseUrl ?? "https://api.cloudflare.com/client/v4").replace(/\/$/, "");
|
|
907
|
+
const request = Effect.fn("CloudflareClient.request")((path, operation, init) => Effect.gen(function* () {
|
|
908
|
+
const response = yield* Effect.tryPromise({
|
|
909
|
+
try: () => fetch(`${baseUrl}${path}`, {
|
|
910
|
+
...init,
|
|
911
|
+
headers: {
|
|
912
|
+
Accept: "application/json",
|
|
913
|
+
Authorization: `Bearer ${options.token.expose()}`,
|
|
914
|
+
...init?.body === void 0 ? {} : { "Content-Type": "application/json" },
|
|
915
|
+
...init?.headers
|
|
916
|
+
}
|
|
917
|
+
}),
|
|
918
|
+
catch: () => failure$3(operation, "Cloudflare request failed", { reason: "transport" })
|
|
919
|
+
});
|
|
920
|
+
return {
|
|
921
|
+
body: yield* Effect.tryPromise({
|
|
922
|
+
try: () => response.json(),
|
|
923
|
+
catch: () => {
|
|
924
|
+
const retryAfterMs = retryAfter(response.headers.get("retry-after"));
|
|
925
|
+
return failure$3(operation, response.ok ? "Cloudflare returned a non-JSON response" : `Cloudflare request failed with HTTP ${response.status}`, {
|
|
926
|
+
reason: response.ok ? "response" : reason$1(response.status),
|
|
927
|
+
...retryAfterMs === void 0 ? {} : { retryAfterMs },
|
|
928
|
+
status: response.status
|
|
929
|
+
});
|
|
930
|
+
}
|
|
931
|
+
}),
|
|
932
|
+
response
|
|
933
|
+
};
|
|
934
|
+
}));
|
|
935
|
+
const listZonesPage = Effect.fn("CloudflareClient.listZonesPage")((accountId, name, page) => Effect.gen(function* () {
|
|
936
|
+
const query = new URLSearchParams({
|
|
937
|
+
page: String(page),
|
|
938
|
+
per_page: "50"
|
|
939
|
+
});
|
|
940
|
+
query.set("type", "full,partial,secondary,internal");
|
|
941
|
+
if (accountId !== void 0) query.set("account.id", accountId);
|
|
942
|
+
if (name !== void 0) query.set("name", name);
|
|
943
|
+
const { body, response } = yield* request(`/zones?${query}`, "listZones");
|
|
944
|
+
yield* ensureSuccess(body, "listZones", response);
|
|
945
|
+
return yield* decode$2(ZoneListEnvelope, body, "listZones", response);
|
|
946
|
+
}));
|
|
947
|
+
return {
|
|
948
|
+
allZones: Effect.fn("CloudflareClient.allZones")((accountId, name) => Effect.gen(function* () {
|
|
949
|
+
const zones = [];
|
|
950
|
+
let page = 1;
|
|
951
|
+
while (true) {
|
|
952
|
+
const envelope = yield* listZonesPage(accountId, name, page);
|
|
953
|
+
zones.push(...envelope.result);
|
|
954
|
+
const totalPages = envelope.result_info?.total_pages;
|
|
955
|
+
if (totalPages !== void 0 && page >= totalPages || totalPages === void 0 && envelope.result.length < 50) return zones;
|
|
956
|
+
page += 1;
|
|
957
|
+
}
|
|
958
|
+
})),
|
|
959
|
+
inspectToken: Effect.fn("CloudflareClient.inspectToken")((path) => Effect.gen(function* () {
|
|
960
|
+
const { body, response } = yield* request(path, "validateToken");
|
|
961
|
+
yield* ensureSuccess(body, "validateToken", response);
|
|
962
|
+
const envelope = yield* decode$2(TokenEnvelope, body, "validateToken", response);
|
|
963
|
+
if (envelope.result.status !== "active") return yield* Effect.fail(failure$3("validateToken", `Cloudflare token is ${envelope.result.status}`, {
|
|
964
|
+
reason: "authentication",
|
|
965
|
+
status: response.status
|
|
966
|
+
}));
|
|
967
|
+
const expiresAt = envelope.result.expires_on === void 0 ? null : new Date(envelope.result.expires_on);
|
|
968
|
+
if (expiresAt !== null && Number.isNaN(expiresAt.valueOf())) return yield* Effect.fail(failure$3("validateToken", "Cloudflare token expiry is not a valid timestamp", {
|
|
969
|
+
reason: "response",
|
|
970
|
+
status: response.status
|
|
971
|
+
}));
|
|
972
|
+
return expiresAt;
|
|
973
|
+
})),
|
|
974
|
+
request
|
|
975
|
+
};
|
|
976
|
+
};
|
|
977
|
+
const discoverAccount = Effect.fn("CloudflareClient.discoverAccount")((options) => Effect.gen(function* () {
|
|
978
|
+
const { allZones } = makeTransport(options);
|
|
979
|
+
for (const candidate of candidates(options.domain)) {
|
|
980
|
+
const matches = yield* allZones(void 0, candidate);
|
|
981
|
+
const match = matches[0];
|
|
982
|
+
if (matches.length === 0) continue;
|
|
983
|
+
if (matches.length === 1 && match !== void 0) return match.account;
|
|
984
|
+
return yield* Effect.fail(failure$3("discoverAccount", `Cloudflare returned multiple accessible zones named ${candidate}`, { reason: "conflict" }));
|
|
985
|
+
}
|
|
986
|
+
return yield* Effect.fail(failure$3("discoverAccount", `Cloudflare did not expose an accessible zone for ${options.domain}`, { reason: "not_found" }));
|
|
987
|
+
}));
|
|
988
|
+
const discoverAuthorizationAccount = discoverAccount;
|
|
989
|
+
const validateDomainToken = Effect.fn("CloudflareClient.validateDomainToken")((options) => Effect.gen(function* () {
|
|
990
|
+
const transport = makeTransport(options);
|
|
991
|
+
const account = options.tokenKind === "account" ? yield* discoverAccount(options) : void 0;
|
|
992
|
+
const expiresAt = yield* transport.inspectToken(account === void 0 ? "/user/tokens/verify" : `/accounts/${encodeURIComponent(account.id)}/tokens/verify`);
|
|
993
|
+
return {
|
|
994
|
+
accountId: (account ?? (yield* discoverAccount(options))).id,
|
|
995
|
+
capabilities: options.capabilities,
|
|
996
|
+
expiresAt,
|
|
997
|
+
scopes: []
|
|
998
|
+
};
|
|
999
|
+
}));
|
|
1000
|
+
/** Creates an Effect-native Cloudflare client without owning the credential lifecycle. */
|
|
1001
|
+
function make$5(options) {
|
|
1002
|
+
const { allZones, inspectToken, request } = makeTransport(options);
|
|
1003
|
+
const resolveZone = Effect.fn("CloudflareClient.resolveZone")((name) => Effect.gen(function* () {
|
|
1004
|
+
const matches = yield* allZones(options.accountId, name);
|
|
1005
|
+
const match = matches[0];
|
|
1006
|
+
if (matches.length === 1 && match !== void 0) return match;
|
|
1007
|
+
return yield* Effect.fail(failure$3("resolveZone", matches.length === 0 ? `Cloudflare zone ${name} was not found in account ${options.accountId}` : `Cloudflare returned multiple zones named ${name} in account ${options.accountId}`, { reason: matches.length === 0 ? "not_found" : "response" }));
|
|
1008
|
+
}));
|
|
1009
|
+
const listRecords = Effect.fn("CloudflareClient.listRecords")((zoneName) => Effect.gen(function* () {
|
|
1010
|
+
const zone = yield* resolveZone(zoneName);
|
|
1011
|
+
const records = [];
|
|
1012
|
+
let page = 1;
|
|
1013
|
+
while (true) {
|
|
1014
|
+
const query = new URLSearchParams({
|
|
1015
|
+
page: String(page),
|
|
1016
|
+
per_page: "100"
|
|
1017
|
+
});
|
|
1018
|
+
const { body, response } = yield* request(`/zones/${encodeURIComponent(zone.id)}/dns_records?${query}`, "listRecords");
|
|
1019
|
+
yield* ensureSuccess(body, "listRecords", response);
|
|
1020
|
+
const envelope = yield* decode$2(RecordListEnvelope$1, body, "listRecords", response);
|
|
1021
|
+
records.push(...yield* Effect.forEach(envelope.result, decode$3));
|
|
1022
|
+
const totalPages = envelope.result_info?.total_pages;
|
|
1023
|
+
if (totalPages !== void 0 && page >= totalPages || totalPages === void 0 && envelope.result.length < 100) return records;
|
|
1024
|
+
page += 1;
|
|
1025
|
+
}
|
|
1026
|
+
}));
|
|
1027
|
+
const createRecord = Effect.fn("CloudflareClient.createRecord")((zoneName, record) => Effect.gen(function* () {
|
|
1028
|
+
const zone = yield* resolveZone(zoneName);
|
|
1029
|
+
const { body, response } = yield* request(`/zones/${encodeURIComponent(zone.id)}/dns_records`, "createRecord", {
|
|
1030
|
+
body: JSON.stringify(encode$1(record)),
|
|
1031
|
+
method: "POST"
|
|
1032
|
+
});
|
|
1033
|
+
yield* ensureSuccess(body, "createRecord", response);
|
|
1034
|
+
return { providerRecordId: (yield* decode$2(RecordEnvelope, body, "createRecord", response)).result.id };
|
|
1035
|
+
}));
|
|
1036
|
+
const getRecord = Effect.fn("CloudflareClient.getRecord")((zoneName, providerRecordId) => Effect.gen(function* () {
|
|
1037
|
+
const zone = yield* resolveZone(zoneName);
|
|
1038
|
+
const { body, response } = yield* request(`/zones/${encodeURIComponent(zone.id)}/dns_records/${encodeURIComponent(providerRecordId)}`, "getRecord");
|
|
1039
|
+
if (response.status === 404) return null;
|
|
1040
|
+
yield* ensureSuccess(body, "getRecord", response);
|
|
1041
|
+
const envelope = yield* decode$2(RecordEnvelope, body, "getRecord", response);
|
|
1042
|
+
return yield* decode$3(envelope.result);
|
|
1043
|
+
}));
|
|
1044
|
+
return {
|
|
1045
|
+
id: "cloudflare",
|
|
1046
|
+
createRecord,
|
|
1047
|
+
deleteRecord: Effect.fn("CloudflareClient.deleteRecord")((zoneName, providerRecordId) => Effect.gen(function* () {
|
|
1048
|
+
const zone = yield* resolveZone(zoneName);
|
|
1049
|
+
const { body, response } = yield* request(`/zones/${encodeURIComponent(zone.id)}/dns_records/${encodeURIComponent(providerRecordId)}`, "deleteRecord", { method: "DELETE" });
|
|
1050
|
+
yield* ensureSuccess(body, "deleteRecord", response);
|
|
1051
|
+
})),
|
|
1052
|
+
getRecord,
|
|
1053
|
+
listAccounts: Effect.fn("CloudflareClient.listAccounts")(() => allZones().pipe(Effect.map((zones) => {
|
|
1054
|
+
const accounts = /* @__PURE__ */ new Map();
|
|
1055
|
+
for (const zone of zones) accounts.set(zone.account.id, zone.account);
|
|
1056
|
+
return [...accounts.values()].sort((left, right) => left.name.localeCompare(right.name));
|
|
1057
|
+
}))),
|
|
1058
|
+
listRecords,
|
|
1059
|
+
listZones: Effect.fn("CloudflareClient.listZones")((input = {}) => allZones(input.accountId ?? options.accountId, input.name).pipe(Effect.flatMap((zones) => Effect.forEach(zones, projectZone$1)))),
|
|
1060
|
+
validateToken: Effect.fn("CloudflareClient.validateToken")(() => Effect.gen(function* () {
|
|
1061
|
+
const path = options.tokenKind === "account" ? `/accounts/${encodeURIComponent(options.accountId)}/tokens/verify` : "/user/tokens/verify";
|
|
1062
|
+
const expiresAt = yield* inspectToken(path);
|
|
1063
|
+
yield* allZones(options.accountId);
|
|
1064
|
+
return {
|
|
1065
|
+
accountId: options.accountId,
|
|
1066
|
+
capabilities: options.capabilities,
|
|
1067
|
+
expiresAt,
|
|
1068
|
+
scopes: []
|
|
1069
|
+
};
|
|
1070
|
+
}))
|
|
1071
|
+
};
|
|
1072
|
+
}
|
|
1073
|
+
const projectZone$1 = Effect.fn("CloudflareClient.projectZone")((zone) => Effect.gen(function* () {
|
|
1074
|
+
const name = yield* decode$8(zone.name).pipe(Effect.mapError((cause) => failure$3("listZones", cause.message, { reason: "response" })));
|
|
1075
|
+
const nameservers = yield* Effect.forEach(zone.name_servers, (nameserver) => decode$8(nameserver).pipe(Effect.mapError((cause) => failure$3("listZones", cause.message, { reason: "response" }))));
|
|
1076
|
+
return {
|
|
1077
|
+
accountId: zone.account.id,
|
|
1078
|
+
id: zone.id,
|
|
1079
|
+
name,
|
|
1080
|
+
nameservers,
|
|
1081
|
+
status: zone.status ?? null
|
|
1082
|
+
};
|
|
1083
|
+
}));
|
|
1084
|
+
function decode$2(schema, input, operation, response) {
|
|
1085
|
+
return Schema.decodeUnknownEffect(schema)(input).pipe(Effect.mapError((cause) => failure$3(operation, `Cloudflare response did not match its API contract: ${cause.message}`, {
|
|
1086
|
+
reason: "response",
|
|
1087
|
+
status: response.status
|
|
1088
|
+
})));
|
|
1089
|
+
}
|
|
1090
|
+
function ensureSuccess(input, operation, response) {
|
|
1091
|
+
return Schema.decodeUnknownEffect(BaseEnvelope)(input).pipe(Effect.mapError(() => failure$3(operation, "Cloudflare response did not contain a valid API envelope", {
|
|
1092
|
+
reason: "response",
|
|
1093
|
+
status: response.status
|
|
1094
|
+
})), Effect.flatMap((envelope) => {
|
|
1095
|
+
if (response.ok && envelope.success) return Effect.void;
|
|
1096
|
+
const detail = envelope.errors[0];
|
|
1097
|
+
const retryAfterMs = retryAfter(response.headers.get("retry-after"));
|
|
1098
|
+
return Effect.fail(failure$3(operation, detail?.message ?? `Cloudflare request failed with HTTP ${response.status}`, {
|
|
1099
|
+
...detail === void 0 ? {} : { code: detail.code },
|
|
1100
|
+
reason: reason$1(response.status, detail?.code),
|
|
1101
|
+
...retryAfterMs === void 0 ? {} : { retryAfterMs },
|
|
1102
|
+
status: response.status
|
|
1103
|
+
}));
|
|
1104
|
+
}));
|
|
1105
|
+
}
|
|
1106
|
+
function reason$1(status, code) {
|
|
1107
|
+
if (status === 401) return "authentication";
|
|
1108
|
+
if (status === 403) return "authorization";
|
|
1109
|
+
if (status === 404) return "not_found";
|
|
1110
|
+
if (status === 409 || code === 81056 || code === 81057 || code === 81058) return "conflict";
|
|
1111
|
+
if (status === 429) return "rate_limit";
|
|
1112
|
+
if (status >= 500) return "response";
|
|
1113
|
+
return "request";
|
|
1114
|
+
}
|
|
1115
|
+
function retryAfter(value) {
|
|
1116
|
+
if (value === null) return void 0;
|
|
1117
|
+
const seconds = Number(value);
|
|
1118
|
+
if (Number.isFinite(seconds)) return Math.max(0, seconds * 1e3);
|
|
1119
|
+
const date = Date.parse(value);
|
|
1120
|
+
return Number.isNaN(date) ? void 0 : Math.max(0, date - Date.now());
|
|
1121
|
+
}
|
|
1122
|
+
function failure$3(operation, message, fields = {}) {
|
|
1123
|
+
return new Error$3({
|
|
1124
|
+
...fields,
|
|
1125
|
+
message,
|
|
1126
|
+
operation,
|
|
1127
|
+
providerId: "cloudflare"
|
|
1128
|
+
});
|
|
1129
|
+
}
|
|
1130
|
+
//#endregion
|
|
1131
|
+
//#region src/providers/cloudflare/auth.ts
|
|
1132
|
+
var auth_exports$1 = /* @__PURE__ */ __exportAll({
|
|
1133
|
+
contextCodec: () => contextCodec$1,
|
|
1134
|
+
manifest: () => manifest$1,
|
|
1135
|
+
oauthAuthentication: () => oauthAuthentication,
|
|
1136
|
+
oauthFlow: () => oauthFlow,
|
|
1137
|
+
oauthMethod: () => oauthMethod,
|
|
1138
|
+
subjectResolver: () => subjectResolver,
|
|
1139
|
+
tokenConnectionMethod: () => tokenConnectionMethod$1,
|
|
1140
|
+
tokenMethod: () => tokenMethod$1,
|
|
1141
|
+
tokenValidator: () => tokenValidator
|
|
1142
|
+
});
|
|
1143
|
+
const authorizationServer = {
|
|
1144
|
+
authorization_endpoint: "https://dash.cloudflare.com/oauth2/auth",
|
|
1145
|
+
issuer: "https://dash.cloudflare.com",
|
|
1146
|
+
revocation_endpoint: "https://dash.cloudflare.com/oauth2/revoke",
|
|
1147
|
+
token_endpoint: "https://dash.cloudflare.com/oauth2/token"
|
|
1148
|
+
};
|
|
1149
|
+
const contextCodec$1 = codec("cloudflare.v1", Schema.Struct({ tokenKind: Schema.Literals(["account", "user"]) }));
|
|
1150
|
+
/** Describes Cloudflare's caller-created API-token authorization method. */
|
|
1151
|
+
function tokenMethod$1(capabilities) {
|
|
1152
|
+
return {
|
|
1153
|
+
_tag: "token",
|
|
1154
|
+
capabilities: [...capabilities],
|
|
1155
|
+
instructionsUrl: "https://developers.cloudflare.com/fundamentals/api/get-started/create-token/"
|
|
1156
|
+
};
|
|
1157
|
+
}
|
|
1158
|
+
/** Describes a registered Cloudflare OAuth client and its assigned scope IDs. */
|
|
1159
|
+
function oauthMethod(options) {
|
|
1160
|
+
return {
|
|
1161
|
+
_tag: "oauth2",
|
|
1162
|
+
authorizationServer,
|
|
1163
|
+
capabilities: [...options.capabilities],
|
|
1164
|
+
clientAuth: options.clientAuth,
|
|
1165
|
+
scopes: [...options.scopes]
|
|
1166
|
+
};
|
|
1167
|
+
}
|
|
1168
|
+
/** Creates the Cloudflare token and OAuth authorization manifest. */
|
|
1169
|
+
function manifest$1(options) {
|
|
1170
|
+
return {
|
|
1171
|
+
methods: [tokenMethod$1(options.capabilities), oauthMethod(options)],
|
|
1172
|
+
providerId: "cloudflare"
|
|
1173
|
+
};
|
|
1174
|
+
}
|
|
1175
|
+
/** Resolves the Cloudflare account selected by a completed OAuth grant. */
|
|
1176
|
+
function subjectResolver(options) {
|
|
1177
|
+
return (tokens, accessToken) => Effect.gen(function* () {
|
|
1178
|
+
const accountId = options.accountId ?? (yield* discoverAuthorizationAccount({
|
|
1179
|
+
...options.baseUrl === void 0 ? {} : { baseUrl: options.baseUrl },
|
|
1180
|
+
domain: options.domain,
|
|
1181
|
+
...options.fetch === void 0 ? {} : { fetch: options.fetch },
|
|
1182
|
+
token: accessToken
|
|
1183
|
+
}).pipe(Effect.map((account) => account.id)));
|
|
1184
|
+
if (options.accountId !== void 0) yield* make$5({
|
|
1185
|
+
accountId: options.accountId,
|
|
1186
|
+
...options.baseUrl === void 0 ? {} : { baseUrl: options.baseUrl },
|
|
1187
|
+
capabilities: options.capabilities,
|
|
1188
|
+
...options.fetch === void 0 ? {} : { fetch: options.fetch },
|
|
1189
|
+
token: accessToken,
|
|
1190
|
+
tokenKind: "user"
|
|
1191
|
+
}).listZones();
|
|
1192
|
+
const now = yield* Clock.currentTimeMillis;
|
|
1193
|
+
return {
|
|
1194
|
+
accountId,
|
|
1195
|
+
expiresAt: typeof tokens.expires_in === "number" ? new Date(now + tokens.expires_in * 1e3) : null
|
|
1196
|
+
};
|
|
1197
|
+
});
|
|
1198
|
+
}
|
|
1199
|
+
/** Creates a host callback that validates a Cloudflare API token. */
|
|
1200
|
+
function tokenValidator(options) {
|
|
1201
|
+
return (token) => options.accountId === void 0 ? validateDomainToken({
|
|
1202
|
+
...options.baseUrl === void 0 ? {} : { baseUrl: options.baseUrl },
|
|
1203
|
+
capabilities: options.capabilities,
|
|
1204
|
+
domain: options.domain,
|
|
1205
|
+
...options.fetch === void 0 ? {} : { fetch: options.fetch },
|
|
1206
|
+
token,
|
|
1207
|
+
...options.tokenKind === void 0 ? {} : { tokenKind: options.tokenKind }
|
|
1208
|
+
}) : make$5({
|
|
1209
|
+
accountId: options.accountId,
|
|
1210
|
+
...options.baseUrl === void 0 ? {} : { baseUrl: options.baseUrl },
|
|
1211
|
+
capabilities: options.capabilities,
|
|
1212
|
+
...options.fetch === void 0 ? {} : { fetch: options.fetch },
|
|
1213
|
+
token,
|
|
1214
|
+
...options.tokenKind === void 0 ? {} : { tokenKind: options.tokenKind }
|
|
1215
|
+
}).validateToken();
|
|
1216
|
+
}
|
|
1217
|
+
/** Creates a provider-independent token connection method backed by Cloudflare validation. */
|
|
1218
|
+
function tokenConnectionMethod$1(options) {
|
|
1219
|
+
const requiredCapabilities = [...options.capabilities];
|
|
1220
|
+
return Method$1.Token({
|
|
1221
|
+
authenticate: Effect.fn("CloudflareAuth.authenticateToken")(function* (token) {
|
|
1222
|
+
const validated = yield* tokenValidator(options)(token);
|
|
1223
|
+
const observedAt = new Date(yield* Clock.currentTimeMillis);
|
|
1224
|
+
return {
|
|
1225
|
+
capabilityEvidence: validated.capabilities.map((capability) => ({
|
|
1226
|
+
capability,
|
|
1227
|
+
evidence: Evidence$1.Introspected({ observedAt })
|
|
1228
|
+
})),
|
|
1229
|
+
credential: {
|
|
1230
|
+
accessToken: token,
|
|
1231
|
+
refreshToken: null,
|
|
1232
|
+
tokenType: "bearer"
|
|
1233
|
+
},
|
|
1234
|
+
expiresAt: validated.expiresAt,
|
|
1235
|
+
providerAccountId: validated.accountId,
|
|
1236
|
+
providerContext: yield* contextCodec$1.encode({ tokenKind: options.tokenKind ?? "user" }),
|
|
1237
|
+
scopes: [...validated.scopes]
|
|
1238
|
+
};
|
|
1239
|
+
}),
|
|
1240
|
+
providerId: "cloudflare",
|
|
1241
|
+
requiredCapabilities,
|
|
1242
|
+
token: options.token
|
|
1243
|
+
});
|
|
1244
|
+
}
|
|
1245
|
+
/** Converts a completed Cloudflare OAuth grant into the canonical connection authentication. */
|
|
1246
|
+
const oauthAuthentication = Effect.fn("CloudflareAuth.oauthAuthentication")(function* (options) {
|
|
1247
|
+
const resolved = yield* subjectResolver(options)(options.tokens, options.accessToken);
|
|
1248
|
+
const observedAt = new Date(yield* Clock.currentTimeMillis);
|
|
1249
|
+
return {
|
|
1250
|
+
capabilityEvidence: options.capabilities.map((capability) => ({
|
|
1251
|
+
capability,
|
|
1252
|
+
evidence: Evidence$1.Introspected({ observedAt })
|
|
1253
|
+
})),
|
|
1254
|
+
credential: {
|
|
1255
|
+
accessToken: options.accessToken,
|
|
1256
|
+
refreshToken: options.tokens.refresh_token === void 0 ? null : Value.from(options.tokens.refresh_token),
|
|
1257
|
+
tokenType: options.tokens.token_type
|
|
1258
|
+
},
|
|
1259
|
+
expiresAt: resolved.expiresAt,
|
|
1260
|
+
providerAccountId: resolved.accountId,
|
|
1261
|
+
providerContext: yield* contextCodec$1.encode({ tokenKind: "user" }),
|
|
1262
|
+
scopes: (options.tokens.scope ?? "").split(" ").filter(Boolean)
|
|
1263
|
+
};
|
|
1264
|
+
});
|
|
1265
|
+
/** Creates Cloudflare's OAuth implementation of the common interactive connection capability. */
|
|
1266
|
+
function oauthFlow(options) {
|
|
1267
|
+
const method = oauthMethod({
|
|
1268
|
+
capabilities: options.capabilities,
|
|
1269
|
+
clientAuth: options.clientAuth,
|
|
1270
|
+
scopes: options.scopes
|
|
1271
|
+
});
|
|
1272
|
+
return {
|
|
1273
|
+
complete: Effect.fn("CloudflareAuth.oauthFlow.complete")(function* (payload, callbackUrl) {
|
|
1274
|
+
const tokens = yield* complete({
|
|
1275
|
+
callbackUrl,
|
|
1276
|
+
client: options.client,
|
|
1277
|
+
...options.fetch === void 0 ? {} : { fetch: options.fetch },
|
|
1278
|
+
method,
|
|
1279
|
+
payload,
|
|
1280
|
+
providerId: "cloudflare"
|
|
1281
|
+
});
|
|
1282
|
+
return yield* oauthAuthentication({
|
|
1283
|
+
...options.accountId === void 0 ? { domain: options.domain } : { accountId: options.accountId },
|
|
1284
|
+
...options.baseUrl === void 0 ? {} : { baseUrl: options.baseUrl },
|
|
1285
|
+
capabilities: options.capabilities,
|
|
1286
|
+
...options.fetch === void 0 ? {} : { fetch: options.fetch },
|
|
1287
|
+
accessToken: make$6(tokens.access_token),
|
|
1288
|
+
tokens
|
|
1289
|
+
});
|
|
1290
|
+
}),
|
|
1291
|
+
method: "oauth2",
|
|
1292
|
+
providerId: "cloudflare",
|
|
1293
|
+
requiredCapabilities: [...options.capabilities],
|
|
1294
|
+
start: (continuationId) => start({
|
|
1295
|
+
client: options.client,
|
|
1296
|
+
method,
|
|
1297
|
+
redirectUri: options.redirectUri,
|
|
1298
|
+
state: continuationId
|
|
1299
|
+
})
|
|
1300
|
+
};
|
|
1301
|
+
}
|
|
1302
|
+
//#endregion
|
|
1303
|
+
//#region src/providers/vercel/protocol.ts
|
|
1304
|
+
const Pagination = Schema.Struct({
|
|
1305
|
+
count: Schema.Number,
|
|
1306
|
+
next: Schema.NullOr(Schema.Number),
|
|
1307
|
+
prev: Schema.NullOr(Schema.Number)
|
|
1308
|
+
});
|
|
1309
|
+
const ErrorDetail = Schema.Struct({
|
|
1310
|
+
code: Schema.String,
|
|
1311
|
+
message: Schema.String,
|
|
1312
|
+
limit: Schema.optionalKey(Schema.Struct({
|
|
1313
|
+
remaining: Schema.Number,
|
|
1314
|
+
reset: Schema.optionalKey(Schema.Number),
|
|
1315
|
+
resetMs: Schema.optionalKey(Schema.Number),
|
|
1316
|
+
total: Schema.Number
|
|
1317
|
+
}))
|
|
1318
|
+
});
|
|
1319
|
+
const ErrorEnvelope = Schema.Struct({ error: ErrorDetail });
|
|
1320
|
+
const User = Schema.Struct({
|
|
1321
|
+
id: Schema.String,
|
|
1322
|
+
name: Schema.NullOr(Schema.String),
|
|
1323
|
+
username: Schema.String
|
|
1324
|
+
});
|
|
1325
|
+
const UserEnvelope = Schema.Struct({ user: User });
|
|
1326
|
+
const Team = Schema.Struct({
|
|
1327
|
+
id: Schema.String,
|
|
1328
|
+
name: Schema.NullOr(Schema.String),
|
|
1329
|
+
slug: Schema.String
|
|
1330
|
+
});
|
|
1331
|
+
const TeamListEnvelope = Schema.Struct({
|
|
1332
|
+
pagination: Pagination,
|
|
1333
|
+
teams: Schema.Array(Team)
|
|
1334
|
+
});
|
|
1335
|
+
const Domain = Schema.Struct({
|
|
1336
|
+
id: Schema.String,
|
|
1337
|
+
intendedNameservers: Schema.Array(Schema.String),
|
|
1338
|
+
name: Schema.String,
|
|
1339
|
+
nameservers: Schema.Array(Schema.String),
|
|
1340
|
+
serviceType: Schema.Literals([
|
|
1341
|
+
"zeit.world",
|
|
1342
|
+
"external",
|
|
1343
|
+
"na"
|
|
1344
|
+
]),
|
|
1345
|
+
teamId: Schema.NullOr(Schema.String),
|
|
1346
|
+
userId: Schema.String,
|
|
1347
|
+
verified: Schema.Boolean,
|
|
1348
|
+
zone: Schema.optionalKey(Schema.Boolean)
|
|
1349
|
+
});
|
|
1350
|
+
const DomainConfig = Schema.Struct({
|
|
1351
|
+
misconfigured: Schema.Boolean,
|
|
1352
|
+
serviceType: Schema.Literals([
|
|
1353
|
+
"zeit.world",
|
|
1354
|
+
"external",
|
|
1355
|
+
"na"
|
|
1356
|
+
])
|
|
1357
|
+
});
|
|
1358
|
+
const DomainEnvelope = Schema.Struct({ domain: Domain });
|
|
1359
|
+
const DomainListEnvelope = Schema.Struct({
|
|
1360
|
+
domains: Schema.Array(Domain),
|
|
1361
|
+
pagination: Pagination
|
|
1362
|
+
});
|
|
1363
|
+
const Record = Schema.Struct({
|
|
1364
|
+
id: Schema.String,
|
|
1365
|
+
mxPriority: Schema.optionalKey(Schema.Number),
|
|
1366
|
+
name: Schema.String,
|
|
1367
|
+
priority: Schema.optionalKey(Schema.Number),
|
|
1368
|
+
ttl: Schema.optionalKey(Schema.Number),
|
|
1369
|
+
type: Schema.String,
|
|
1370
|
+
value: Schema.String
|
|
1371
|
+
});
|
|
1372
|
+
const RecordListEnvelope = Schema.Struct({
|
|
1373
|
+
pagination: Schema.optionalKey(Pagination),
|
|
1374
|
+
records: Schema.Array(Record)
|
|
1375
|
+
});
|
|
1376
|
+
const CreateRecordEnvelope = Schema.Struct({
|
|
1377
|
+
uid: Schema.String,
|
|
1378
|
+
updated: Schema.optionalKey(Schema.Number)
|
|
1379
|
+
});
|
|
1380
|
+
const IntegrationToken = Schema.Struct({
|
|
1381
|
+
access_token: Schema.String,
|
|
1382
|
+
installation_id: Schema.optionalKey(Schema.String),
|
|
1383
|
+
team_id: Schema.NullOr(Schema.String),
|
|
1384
|
+
token_type: Schema.optionalKey(Schema.String),
|
|
1385
|
+
user_id: Schema.String
|
|
1386
|
+
});
|
|
1387
|
+
//#endregion
|
|
1388
|
+
//#region src/providers/vercel/records.ts
|
|
1389
|
+
const decode$1 = Effect.fn("VercelRecords.decode")((zone, record) => Effect.gen(function* () {
|
|
1390
|
+
const absolute = absoluteNameCandidate(zone, record.name);
|
|
1391
|
+
const name = yield* decode$8(absolute).pipe(Effect.match({
|
|
1392
|
+
onFailure: () => null,
|
|
1393
|
+
onSuccess: (value) => value
|
|
1394
|
+
}));
|
|
1395
|
+
if (name === null) return yield* decodeOpaque(record, absolute);
|
|
1396
|
+
const common = {
|
|
1397
|
+
metadata: {
|
|
1398
|
+
ownership: "provider",
|
|
1399
|
+
provenance: "vercel",
|
|
1400
|
+
purpose: "existing DNS record"
|
|
1401
|
+
},
|
|
1402
|
+
name,
|
|
1403
|
+
policy: record.type === "CNAME" ? "exclusive" : "append",
|
|
1404
|
+
ttl: record.ttl ?? 60
|
|
1405
|
+
};
|
|
1406
|
+
const fromRecord = (input) => decode$9(input).pipe(Effect.mapError((cause) => failure$2("decodeRecord", cause.message)));
|
|
1407
|
+
switch (record.type) {
|
|
1408
|
+
case "A":
|
|
1409
|
+
case "AAAA": return yield* fromRecord({
|
|
1410
|
+
...common,
|
|
1411
|
+
_tag: record.type,
|
|
1412
|
+
address: record.value
|
|
1413
|
+
});
|
|
1414
|
+
case "CNAME":
|
|
1415
|
+
case "NS": return yield* fromRecord({
|
|
1416
|
+
...common,
|
|
1417
|
+
_tag: record.type,
|
|
1418
|
+
target: record.value
|
|
1419
|
+
});
|
|
1420
|
+
case "TXT": return yield* fromRecord({
|
|
1421
|
+
...common,
|
|
1422
|
+
_tag: "TXT",
|
|
1423
|
+
value: record.value
|
|
1424
|
+
});
|
|
1425
|
+
case "MX": return yield* fromRecord({
|
|
1426
|
+
...common,
|
|
1427
|
+
_tag: "MX",
|
|
1428
|
+
exchange: record.value,
|
|
1429
|
+
priority: record.mxPriority ?? record.priority
|
|
1430
|
+
});
|
|
1431
|
+
case "CAA": {
|
|
1432
|
+
const match = record.value.match(/^(\d{1,3})\s+(issue|issuewild|iodef)\s+(?:"((?:\\.|[^"])*)"|(\S.*))$/);
|
|
1433
|
+
if (match === null) return yield* Effect.fail(failure$2("decodeRecord", "Vercel CAA record has an invalid value"));
|
|
1434
|
+
return yield* fromRecord({
|
|
1435
|
+
...common,
|
|
1436
|
+
_tag: "CAA",
|
|
1437
|
+
flags: Number(match[1]),
|
|
1438
|
+
tag: match[2],
|
|
1439
|
+
value: unescapeQuoted(match[3] ?? match[4] ?? "")
|
|
1440
|
+
});
|
|
1441
|
+
}
|
|
1442
|
+
case "SRV": {
|
|
1443
|
+
const parts = record.value.trim().split(/\s+/);
|
|
1444
|
+
const [priority, weight, port, target] = parts.length === 4 ? parts : [
|
|
1445
|
+
String(record.priority ?? ""),
|
|
1446
|
+
parts[0],
|
|
1447
|
+
parts[1],
|
|
1448
|
+
parts[2]
|
|
1449
|
+
];
|
|
1450
|
+
return yield* fromRecord({
|
|
1451
|
+
...common,
|
|
1452
|
+
_tag: "SRV",
|
|
1453
|
+
port: Number(port),
|
|
1454
|
+
priority: Number(priority),
|
|
1455
|
+
target,
|
|
1456
|
+
weight: Number(weight)
|
|
1457
|
+
});
|
|
1458
|
+
}
|
|
1459
|
+
default: return yield* decodeOpaque(record, name);
|
|
1460
|
+
}
|
|
1461
|
+
}));
|
|
1462
|
+
const decodeOpaque = Effect.fn("VercelRecords.decodeOpaque")((record, name) => Schema.decodeUnknownEffect(Opaque)({
|
|
1463
|
+
_tag: "Opaque",
|
|
1464
|
+
name,
|
|
1465
|
+
providerRecordId: record.id,
|
|
1466
|
+
providerType: record.type
|
|
1467
|
+
}).pipe(Effect.mapError((cause) => failure$2("decodeRecord", cause.message))));
|
|
1468
|
+
function encode(zone, record) {
|
|
1469
|
+
const common = {
|
|
1470
|
+
name: relativeName(zone, record.name),
|
|
1471
|
+
...record.ttl === null ? {} : { ttl: record.ttl },
|
|
1472
|
+
type: record._tag
|
|
1473
|
+
};
|
|
1474
|
+
switch (record._tag) {
|
|
1475
|
+
case "A":
|
|
1476
|
+
case "AAAA": return {
|
|
1477
|
+
...common,
|
|
1478
|
+
value: record.address
|
|
1479
|
+
};
|
|
1480
|
+
case "CNAME":
|
|
1481
|
+
case "NS": return {
|
|
1482
|
+
...common,
|
|
1483
|
+
value: record.target
|
|
1484
|
+
};
|
|
1485
|
+
case "TXT": return {
|
|
1486
|
+
...common,
|
|
1487
|
+
value: record.value
|
|
1488
|
+
};
|
|
1489
|
+
case "MX": return {
|
|
1490
|
+
...common,
|
|
1491
|
+
mxPriority: record.priority,
|
|
1492
|
+
value: record.exchange
|
|
1493
|
+
};
|
|
1494
|
+
case "CAA": return {
|
|
1495
|
+
...common,
|
|
1496
|
+
value: `${record.flags} ${record.tag} "${escapeQuoted(record.value)}"`
|
|
1497
|
+
};
|
|
1498
|
+
case "SRV": return {
|
|
1499
|
+
...common,
|
|
1500
|
+
srv: {
|
|
1501
|
+
port: record.port,
|
|
1502
|
+
priority: record.priority,
|
|
1503
|
+
target: record.target,
|
|
1504
|
+
weight: record.weight
|
|
1505
|
+
}
|
|
1506
|
+
};
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
1509
|
+
function absoluteNameCandidate(zone, name) {
|
|
1510
|
+
return name === "" || name === "@" ? zone : name === zone || name.endsWith(`.${zone}`) ? name : `${name}.${zone}`;
|
|
1511
|
+
}
|
|
1512
|
+
function relativeName(zone, name) {
|
|
1513
|
+
if (name === zone) return "";
|
|
1514
|
+
const suffix = `.${zone}`;
|
|
1515
|
+
if (!name.endsWith(suffix)) throw failure$2("encodeRecord", `DNS record ${name} is outside Vercel zone ${zone}`, { reason: "request" });
|
|
1516
|
+
return name.slice(0, -suffix.length);
|
|
1517
|
+
}
|
|
1518
|
+
function escapeQuoted(value) {
|
|
1519
|
+
return value.replaceAll("\\", "\\\\").replaceAll("\"", "\\\"");
|
|
1520
|
+
}
|
|
1521
|
+
function unescapeQuoted(value) {
|
|
1522
|
+
return value.replace(/\\([\\"])/g, "$1");
|
|
1523
|
+
}
|
|
1524
|
+
function failure$2(operation, message, fields = {}) {
|
|
1525
|
+
return new Error$3({
|
|
1526
|
+
message,
|
|
1527
|
+
operation,
|
|
1528
|
+
providerId: "vercel",
|
|
1529
|
+
reason: fields.reason ?? "response"
|
|
1530
|
+
});
|
|
1531
|
+
}
|
|
1532
|
+
//#endregion
|
|
1533
|
+
//#region src/providers/vercel/client.ts
|
|
1534
|
+
var client_exports = /* @__PURE__ */ __exportAll({
|
|
1535
|
+
discovery: () => discovery,
|
|
1536
|
+
make: () => make$4
|
|
1537
|
+
});
|
|
1538
|
+
/** Exposes Vercel zone lookup as the optional provider discovery capability. */
|
|
1539
|
+
function discovery(client) {
|
|
1540
|
+
return {
|
|
1541
|
+
listZones: (name) => client.listZones({ name }),
|
|
1542
|
+
provider: client
|
|
1543
|
+
};
|
|
1544
|
+
}
|
|
1545
|
+
/** Creates an Effect-native Vercel client without owning the credential lifecycle. */
|
|
1546
|
+
function make$4(options) {
|
|
1547
|
+
const fetch = options.fetch ?? globalThis.fetch;
|
|
1548
|
+
const baseUrl = (options.baseUrl ?? "https://api.vercel.com").replace(/\/$/, "");
|
|
1549
|
+
const withContext = (path, values = {}) => {
|
|
1550
|
+
const url = new URL(`${baseUrl}${path}`);
|
|
1551
|
+
for (const [key, value] of Object.entries(values)) url.searchParams.set(key, value);
|
|
1552
|
+
if (options.context._tag === "team") url.searchParams.set("teamId", options.context.teamId);
|
|
1553
|
+
return url;
|
|
1554
|
+
};
|
|
1555
|
+
const request = Effect.fn("VercelClient.request")((url, operation, init) => Effect.gen(function* () {
|
|
1556
|
+
const response = yield* Effect.tryPromise({
|
|
1557
|
+
try: () => fetch(url, {
|
|
1558
|
+
...init,
|
|
1559
|
+
headers: {
|
|
1560
|
+
Accept: "application/json",
|
|
1561
|
+
Authorization: `Bearer ${options.token.expose()}`,
|
|
1562
|
+
...init?.body === void 0 ? {} : { "Content-Type": "application/json" },
|
|
1563
|
+
...init?.headers
|
|
1564
|
+
}
|
|
1565
|
+
}),
|
|
1566
|
+
catch: () => failure$1(operation, "Vercel request failed", { reason: "transport" })
|
|
1567
|
+
});
|
|
1568
|
+
const body = yield* Effect.tryPromise({
|
|
1569
|
+
try: () => response.json(),
|
|
1570
|
+
catch: () => failure$1(operation, response.ok ? "Vercel returned a non-JSON response" : `Vercel request failed with HTTP ${response.status}`, {
|
|
1571
|
+
reason: response.ok ? "response" : reason(response.status),
|
|
1572
|
+
...rateLimit(response),
|
|
1573
|
+
status: response.status
|
|
1574
|
+
})
|
|
1575
|
+
});
|
|
1576
|
+
if (!response.ok) return yield* Effect.fail(apiFailure(body, operation, response));
|
|
1577
|
+
return {
|
|
1578
|
+
body,
|
|
1579
|
+
response
|
|
1580
|
+
};
|
|
1581
|
+
}));
|
|
1582
|
+
const allTeams = Effect.fn("VercelClient.allTeams")(() => Effect.gen(function* () {
|
|
1583
|
+
const teams = [];
|
|
1584
|
+
let until = null;
|
|
1585
|
+
while (true) {
|
|
1586
|
+
const url = new URL(`${baseUrl}/v2/teams`);
|
|
1587
|
+
url.searchParams.set("limit", "100");
|
|
1588
|
+
if (until !== null) url.searchParams.set("until", String(until));
|
|
1589
|
+
const { body, response } = yield* request(url, "listAccounts");
|
|
1590
|
+
const envelope = yield* decode(TeamListEnvelope, body, "listAccounts", response);
|
|
1591
|
+
teams.push(...envelope.teams);
|
|
1592
|
+
if (envelope.pagination.next === null) return teams;
|
|
1593
|
+
until = envelope.pagination.next;
|
|
1594
|
+
}
|
|
1595
|
+
}));
|
|
1596
|
+
const allDomains = Effect.fn("VercelClient.allDomains")(() => Effect.gen(function* () {
|
|
1597
|
+
const domains = [];
|
|
1598
|
+
let until = null;
|
|
1599
|
+
while (true) {
|
|
1600
|
+
const values = { limit: "100" };
|
|
1601
|
+
if (until !== null) values.until = String(until);
|
|
1602
|
+
const { body, response } = yield* request(withContext("/v5/domains", values), "listZones");
|
|
1603
|
+
const envelope = yield* decode(DomainListEnvelope, body, "listZones", response);
|
|
1604
|
+
domains.push(...envelope.domains);
|
|
1605
|
+
if (envelope.pagination.next === null) return domains;
|
|
1606
|
+
until = envelope.pagination.next;
|
|
1607
|
+
}
|
|
1608
|
+
}));
|
|
1609
|
+
const discoverZone = Effect.fn("VercelClient.discoverZone")((name) => Effect.gen(function* () {
|
|
1610
|
+
const configResult = yield* request(withContext(`/v6/domains/${encodeURIComponent(name)}/config`), "resolveZone");
|
|
1611
|
+
const config = yield* decode(DomainConfig, configResult.body, "resolveZone", configResult.response);
|
|
1612
|
+
const domainResult = yield* request(withContext(`/v5/domains/${encodeURIComponent(name)}`), "resolveZone");
|
|
1613
|
+
const envelope = yield* decode(DomainEnvelope, domainResult.body, "resolveZone", domainResult.response);
|
|
1614
|
+
if (!hasDnsStorage(envelope.domain, config.serviceType)) return null;
|
|
1615
|
+
return envelope.domain;
|
|
1616
|
+
}).pipe(Effect.catchTag("ProviderError", (cause) => cause.reason === "not_found" ? Effect.succeed(null) : Effect.fail(cause))));
|
|
1617
|
+
const resolveZone = Effect.fn("VercelClient.resolveZone")((name) => Effect.gen(function* () {
|
|
1618
|
+
const domain = yield* discoverZone(name);
|
|
1619
|
+
if (domain === null) return yield* Effect.fail(failure$1("resolveZone", `Vercel zone ${name} is not configured for authoritative DNS in the selected account`, { reason: "not_found" }));
|
|
1620
|
+
return yield* decode$8(domain.name).pipe(Effect.mapError((cause) => failure$1("resolveZone", `Vercel returned an invalid storage zone: ${cause.message}`)));
|
|
1621
|
+
}));
|
|
1622
|
+
const listRecords = Effect.fn("VercelClient.listRecords")((zoneName) => Effect.gen(function* () {
|
|
1623
|
+
const storageZone = yield* resolveZone(zoneName);
|
|
1624
|
+
const records = [];
|
|
1625
|
+
let until = null;
|
|
1626
|
+
while (true) {
|
|
1627
|
+
const values = { limit: "100" };
|
|
1628
|
+
if (until !== null) values.until = String(until);
|
|
1629
|
+
const { body, response } = yield* request(withContext(`/v5/domains/${encodeURIComponent(storageZone)}/records`, values), "listRecords");
|
|
1630
|
+
const envelope = yield* decode(RecordListEnvelope, body, "listRecords", response);
|
|
1631
|
+
records.push(...yield* Effect.forEach(envelope.records, (record) => decode$1(storageZone, record)));
|
|
1632
|
+
const next = envelope.pagination?.next ?? null;
|
|
1633
|
+
if (next === null) return records.filter((record) => record.name === zoneName || record.name.endsWith(`.${zoneName}`));
|
|
1634
|
+
until = next;
|
|
1635
|
+
}
|
|
1636
|
+
}));
|
|
1637
|
+
const createRecord = Effect.fn("VercelClient.createRecord")((zoneName, record) => Effect.gen(function* () {
|
|
1638
|
+
const storageZone = yield* resolveZone(zoneName);
|
|
1639
|
+
const body = yield* Effect.try({
|
|
1640
|
+
try: () => encode(storageZone, record),
|
|
1641
|
+
catch: (cause) => cause instanceof Error$3 ? cause : failure$1("createRecord", "Vercel record could not be encoded", { reason: "request" })
|
|
1642
|
+
});
|
|
1643
|
+
const result = yield* request(withContext(`/v2/domains/${encodeURIComponent(storageZone)}/records`), "createRecord", {
|
|
1644
|
+
body: JSON.stringify(body),
|
|
1645
|
+
method: "POST"
|
|
1646
|
+
});
|
|
1647
|
+
return { providerRecordId: (yield* decode(CreateRecordEnvelope, result.body, "createRecord", result.response)).uid };
|
|
1648
|
+
}));
|
|
1649
|
+
const getRecord = Effect.fn("VercelClient.getRecord")((zoneName, providerRecordId) => Effect.gen(function* () {
|
|
1650
|
+
const storageZone = yield* resolveZone(zoneName);
|
|
1651
|
+
let until = null;
|
|
1652
|
+
while (true) {
|
|
1653
|
+
const values = { limit: "100" };
|
|
1654
|
+
if (until !== null) values.until = String(until);
|
|
1655
|
+
const { body, response } = yield* request(withContext(`/v5/domains/${encodeURIComponent(storageZone)}/records`, values), "getRecord");
|
|
1656
|
+
const envelope = yield* decode(RecordListEnvelope, body, "getRecord", response);
|
|
1657
|
+
const record = envelope.records.find(({ id }) => id === providerRecordId);
|
|
1658
|
+
if (record !== void 0) return yield* decode$1(storageZone, record);
|
|
1659
|
+
const next = envelope.pagination?.next ?? null;
|
|
1660
|
+
if (next === null) return null;
|
|
1661
|
+
until = next;
|
|
1662
|
+
}
|
|
1663
|
+
}));
|
|
1664
|
+
return {
|
|
1665
|
+
id: "vercel",
|
|
1666
|
+
createRecord,
|
|
1667
|
+
deleteRecord: Effect.fn("VercelClient.deleteRecord")((zoneName, providerRecordId) => Effect.gen(function* () {
|
|
1668
|
+
const storageZone = yield* resolveZone(zoneName);
|
|
1669
|
+
yield* request(withContext(`/v2/domains/${encodeURIComponent(storageZone)}/records/${encodeURIComponent(providerRecordId)}`), "deleteRecord", { method: "DELETE" });
|
|
1670
|
+
})),
|
|
1671
|
+
getRecord,
|
|
1672
|
+
listAccounts: Effect.fn("VercelClient.listAccounts")(() => Effect.gen(function* () {
|
|
1673
|
+
const userResult = yield* request(new URL(`${baseUrl}/v2/user`), "listAccounts");
|
|
1674
|
+
const { user } = yield* decode(UserEnvelope, userResult.body, "listAccounts", userResult.response);
|
|
1675
|
+
const teams = yield* allTeams();
|
|
1676
|
+
return [{
|
|
1677
|
+
id: user.id,
|
|
1678
|
+
name: user.name ?? user.username,
|
|
1679
|
+
type: "personal"
|
|
1680
|
+
}, ...teams.map((team) => ({
|
|
1681
|
+
id: team.id,
|
|
1682
|
+
name: team.name ?? team.slug,
|
|
1683
|
+
type: "team"
|
|
1684
|
+
}))];
|
|
1685
|
+
})),
|
|
1686
|
+
listRecords,
|
|
1687
|
+
listZones: Effect.fn("VercelClient.listZones")((input = {}) => Effect.gen(function* () {
|
|
1688
|
+
const domains = (yield* allDomains()).filter((domain) => hasDnsStorage(domain) && (input.name === void 0 || domain.name === input.name));
|
|
1689
|
+
if (domains.length > 0 || input.name === void 0) return yield* Effect.forEach(domains, projectZone);
|
|
1690
|
+
const discovered = yield* discoverZone(input.name);
|
|
1691
|
+
return discovered === null ? [] : [yield* projectZone(discovered)];
|
|
1692
|
+
})),
|
|
1693
|
+
validateToken: Effect.fn("VercelClient.validateToken")(() => Effect.gen(function* () {
|
|
1694
|
+
let accountId;
|
|
1695
|
+
if (options.context._tag === "team") {
|
|
1696
|
+
yield* allDomains();
|
|
1697
|
+
accountId = options.context.teamId;
|
|
1698
|
+
} else {
|
|
1699
|
+
const result = yield* request(new URL(`${baseUrl}/v2/user`), "validateToken");
|
|
1700
|
+
const { user } = yield* decode(UserEnvelope, result.body, "validateToken", result.response);
|
|
1701
|
+
accountId = user.id;
|
|
1702
|
+
}
|
|
1703
|
+
return {
|
|
1704
|
+
accountId,
|
|
1705
|
+
capabilities: options.capabilities,
|
|
1706
|
+
expiresAt: null,
|
|
1707
|
+
scopes: []
|
|
1708
|
+
};
|
|
1709
|
+
}))
|
|
1710
|
+
};
|
|
1711
|
+
}
|
|
1712
|
+
function hasDnsStorage(domain, configuredServiceType = domain.serviceType) {
|
|
1713
|
+
return configuredServiceType === "zeit.world" || domain.zone === true || domain.intendedNameservers.length > 0;
|
|
1714
|
+
}
|
|
1715
|
+
const projectZone = Effect.fn("VercelClient.projectZone")((domain) => Effect.gen(function* () {
|
|
1716
|
+
const name = yield* decode$8(domain.name).pipe(Effect.mapError((cause) => failure$1("listZones", cause.message, { reason: "response" })));
|
|
1717
|
+
const nameservers = yield* Effect.forEach(domain.intendedNameservers, (nameserver) => decode$8(nameserver).pipe(Effect.mapError((cause) => failure$1("listZones", cause.message, { reason: "response" }))));
|
|
1718
|
+
return {
|
|
1719
|
+
accountId: domain.teamId ?? domain.userId,
|
|
1720
|
+
id: domain.id,
|
|
1721
|
+
name,
|
|
1722
|
+
nameservers,
|
|
1723
|
+
status: domain.verified ? "active" : "pending"
|
|
1724
|
+
};
|
|
1725
|
+
}));
|
|
1726
|
+
function decode(schema, input, operation, response) {
|
|
1727
|
+
return Schema.decodeUnknownEffect(schema)(input).pipe(Effect.mapError((cause) => failure$1(operation, `Vercel response did not match its API contract: ${cause.message}`, {
|
|
1728
|
+
reason: "response",
|
|
1729
|
+
status: response.status
|
|
1730
|
+
})));
|
|
1731
|
+
}
|
|
1732
|
+
function apiFailure(input, operation, response) {
|
|
1733
|
+
const detail = Schema.decodeUnknownOption(ErrorEnvelope)(input);
|
|
1734
|
+
return failure$1(operation, detail._tag === "Some" ? detail.value.error.message : `Vercel request failed with HTTP ${response.status}`, {
|
|
1735
|
+
...detail._tag === "Some" ? { code: detail.value.error.code } : {},
|
|
1736
|
+
reason: reason(response.status, detail._tag === "Some" ? detail.value.error.code : void 0),
|
|
1737
|
+
...rateLimit(response, detail._tag === "Some" ? detail.value.error : void 0),
|
|
1738
|
+
status: response.status
|
|
1739
|
+
});
|
|
1740
|
+
}
|
|
1741
|
+
function reason(status, code) {
|
|
1742
|
+
if (status === 401 || code === "invalid_token") return "authentication";
|
|
1743
|
+
if (status === 403) return "authorization";
|
|
1744
|
+
if (status === 404 || code === "not_found") return "not_found";
|
|
1745
|
+
if (status === 409 || code === "conflict") return "conflict";
|
|
1746
|
+
if (status === 429 || code === "rate_limited") return "rate_limit";
|
|
1747
|
+
if (status >= 500) return "response";
|
|
1748
|
+
return "request";
|
|
1749
|
+
}
|
|
1750
|
+
function rateLimit(response, detail) {
|
|
1751
|
+
const resetMs = detail?.limit?.resetMs;
|
|
1752
|
+
const header = response.headers.get("x-ratelimit-reset");
|
|
1753
|
+
const reset = resetMs ?? (header === null ? void 0 : Number(header) * 1e3);
|
|
1754
|
+
return reset === void 0 || !Number.isFinite(reset) ? {} : { retryAfterMs: Math.max(0, reset - Date.now()) };
|
|
1755
|
+
}
|
|
1756
|
+
function failure$1(operation, message, fields = {}) {
|
|
1757
|
+
return new Error$3({
|
|
1758
|
+
...fields,
|
|
1759
|
+
message,
|
|
1760
|
+
operation,
|
|
1761
|
+
providerId: "vercel"
|
|
1762
|
+
});
|
|
1763
|
+
}
|
|
1764
|
+
//#endregion
|
|
1765
|
+
//#region src/providers/vercel/auth.ts
|
|
1766
|
+
var auth_exports = /* @__PURE__ */ __exportAll({
|
|
1767
|
+
contextCodec: () => contextCodec,
|
|
1768
|
+
exchangeCode: () => exchangeCode,
|
|
1769
|
+
integrationAuthentication: () => integrationAuthentication,
|
|
1770
|
+
integrationFlow: () => integrationFlow,
|
|
1771
|
+
integrationMethod: () => integrationMethod,
|
|
1772
|
+
manifest: () => manifest,
|
|
1773
|
+
tokenConnectionMethod: () => tokenConnectionMethod,
|
|
1774
|
+
tokenMethod: () => tokenMethod
|
|
1775
|
+
});
|
|
1776
|
+
const contextCodec = codec("vercel.v1", Schema.TaggedUnion({
|
|
1777
|
+
personal: { installationId: Schema.NullOr(Schema.String) },
|
|
1778
|
+
team: {
|
|
1779
|
+
installationId: Schema.NullOr(Schema.String),
|
|
1780
|
+
teamId: Schema.String
|
|
1781
|
+
}
|
|
1782
|
+
}));
|
|
1783
|
+
/** Describes Vercel's caller-created personal access-token method. */
|
|
1784
|
+
function tokenMethod(capabilities) {
|
|
1785
|
+
return {
|
|
1786
|
+
_tag: "token",
|
|
1787
|
+
capabilities: [...capabilities],
|
|
1788
|
+
instructionsUrl: "https://vercel.com/account/settings/tokens"
|
|
1789
|
+
};
|
|
1790
|
+
}
|
|
1791
|
+
/** Describes Vercel's provider-specific integration installation flow. */
|
|
1792
|
+
function integrationMethod(options) {
|
|
1793
|
+
return {
|
|
1794
|
+
_tag: "integration",
|
|
1795
|
+
capabilities: [...options.capabilities],
|
|
1796
|
+
installUrl: `https://vercel.com/integrations/${encodeURIComponent(options.slug)}/new`,
|
|
1797
|
+
tokenEndpoint: "https://api.vercel.com/oauth/access_token"
|
|
1798
|
+
};
|
|
1799
|
+
}
|
|
1800
|
+
/** Creates the Vercel token and integration authorization manifest. */
|
|
1801
|
+
function manifest(options) {
|
|
1802
|
+
return {
|
|
1803
|
+
methods: [tokenMethod(options.capabilities), integrationMethod(options)],
|
|
1804
|
+
providerId: "vercel"
|
|
1805
|
+
};
|
|
1806
|
+
}
|
|
1807
|
+
/** Exchanges Vercel's one-time integration code and preserves its account context. */
|
|
1808
|
+
const exchangeCode = Effect.fn("VercelAuth.exchangeCode")((options) => Effect.gen(function* () {
|
|
1809
|
+
const fetch = options.fetch ?? globalThis.fetch;
|
|
1810
|
+
const baseUrl = (options.baseUrl ?? "https://api.vercel.com").replace(/\/$/, "");
|
|
1811
|
+
const body = new URLSearchParams({
|
|
1812
|
+
client_id: options.clientId,
|
|
1813
|
+
client_secret: options.clientSecret.expose(),
|
|
1814
|
+
code: options.code.expose(),
|
|
1815
|
+
redirect_uri: options.redirectUri
|
|
1816
|
+
});
|
|
1817
|
+
const response = yield* Effect.tryPromise({
|
|
1818
|
+
try: () => fetch(`${baseUrl}/oauth/access_token`, {
|
|
1819
|
+
body,
|
|
1820
|
+
headers: {
|
|
1821
|
+
Accept: "application/json",
|
|
1822
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
1823
|
+
},
|
|
1824
|
+
method: "POST"
|
|
1825
|
+
}),
|
|
1826
|
+
catch: () => failure("exchangeCode", "Vercel token exchange failed", "transport")
|
|
1827
|
+
});
|
|
1828
|
+
const input = yield* Effect.tryPromise({
|
|
1829
|
+
try: () => response.json(),
|
|
1830
|
+
catch: () => failure("exchangeCode", response.ok ? "Vercel returned a non-JSON token response" : `Vercel token exchange failed with HTTP ${response.status}`, response.ok ? "response" : response.status === 401 ? "authentication" : "request", response.status)
|
|
1831
|
+
});
|
|
1832
|
+
if (!response.ok) {
|
|
1833
|
+
const detail = Schema.decodeUnknownOption(ErrorEnvelope)(input);
|
|
1834
|
+
return yield* Effect.fail(failure("exchangeCode", detail._tag === "Some" ? detail.value.error.message : `Vercel token exchange failed with HTTP ${response.status}`, response.status === 401 ? "authentication" : "request", response.status, detail._tag === "Some" ? detail.value.error.code : void 0));
|
|
1835
|
+
}
|
|
1836
|
+
const token = yield* Schema.decodeUnknownEffect(IntegrationToken)(input).pipe(Effect.mapError((cause) => failure("exchangeCode", `Vercel token response did not match its API contract: ${cause.message}`, "response", response.status)));
|
|
1837
|
+
return {
|
|
1838
|
+
accessToken: make$6(token.access_token),
|
|
1839
|
+
context: token.team_id === null ? { _tag: "personal" } : {
|
|
1840
|
+
_tag: "team",
|
|
1841
|
+
teamId: token.team_id
|
|
1842
|
+
},
|
|
1843
|
+
installationId: token.installation_id ?? null,
|
|
1844
|
+
userId: token.user_id
|
|
1845
|
+
};
|
|
1846
|
+
}));
|
|
1847
|
+
/** Creates a provider-independent token connection method backed by Vercel validation. */
|
|
1848
|
+
function tokenConnectionMethod(options) {
|
|
1849
|
+
const requiredCapabilities = [...options.capabilities];
|
|
1850
|
+
return Method$1.Token({
|
|
1851
|
+
authenticate: Effect.fn("VercelAuth.authenticateToken")(function* (token) {
|
|
1852
|
+
const validated = yield* make$4({
|
|
1853
|
+
...options,
|
|
1854
|
+
token
|
|
1855
|
+
}).validateToken();
|
|
1856
|
+
const observedAt = new Date(yield* Clock.currentTimeMillis);
|
|
1857
|
+
return {
|
|
1858
|
+
capabilityEvidence: validated.capabilities.map((capability) => ({
|
|
1859
|
+
capability,
|
|
1860
|
+
evidence: Evidence$1.Introspected({ observedAt })
|
|
1861
|
+
})),
|
|
1862
|
+
credential: {
|
|
1863
|
+
accessToken: token,
|
|
1864
|
+
refreshToken: null,
|
|
1865
|
+
tokenType: "bearer"
|
|
1866
|
+
},
|
|
1867
|
+
expiresAt: validated.expiresAt,
|
|
1868
|
+
providerAccountId: validated.accountId,
|
|
1869
|
+
providerContext: yield* contextCodec.encode(options.context._tag === "team" ? {
|
|
1870
|
+
_tag: "team",
|
|
1871
|
+
installationId: null,
|
|
1872
|
+
teamId: options.context.teamId
|
|
1873
|
+
} : {
|
|
1874
|
+
_tag: "personal",
|
|
1875
|
+
installationId: null
|
|
1876
|
+
}),
|
|
1877
|
+
scopes: [...validated.scopes]
|
|
1878
|
+
};
|
|
1879
|
+
}),
|
|
1880
|
+
providerId: "vercel",
|
|
1881
|
+
requiredCapabilities,
|
|
1882
|
+
token: options.token
|
|
1883
|
+
});
|
|
1884
|
+
}
|
|
1885
|
+
/** Converts a completed Vercel Integration exchange into canonical connection authentication. */
|
|
1886
|
+
const integrationAuthentication = Effect.fn("VercelAuth.integrationAuthentication")(function* (options) {
|
|
1887
|
+
const exchanged = yield* exchangeCode(options);
|
|
1888
|
+
const observedAt = new Date(yield* Clock.currentTimeMillis);
|
|
1889
|
+
return {
|
|
1890
|
+
capabilityEvidence: options.capabilities.map((capability) => ({
|
|
1891
|
+
capability,
|
|
1892
|
+
evidence: Evidence$1.Introspected({ observedAt })
|
|
1893
|
+
})),
|
|
1894
|
+
credential: {
|
|
1895
|
+
accessToken: exchanged.accessToken,
|
|
1896
|
+
refreshToken: null,
|
|
1897
|
+
tokenType: "bearer"
|
|
1898
|
+
},
|
|
1899
|
+
expiresAt: null,
|
|
1900
|
+
providerAccountId: exchanged.context._tag === "team" ? exchanged.context.teamId : exchanged.userId,
|
|
1901
|
+
providerContext: yield* contextCodec.encode(exchanged.context._tag === "team" ? {
|
|
1902
|
+
_tag: "team",
|
|
1903
|
+
installationId: exchanged.installationId,
|
|
1904
|
+
teamId: exchanged.context.teamId
|
|
1905
|
+
} : {
|
|
1906
|
+
_tag: "personal",
|
|
1907
|
+
installationId: exchanged.installationId
|
|
1908
|
+
}),
|
|
1909
|
+
scopes: []
|
|
1910
|
+
};
|
|
1911
|
+
});
|
|
1912
|
+
/** Creates Vercel Integration's implementation of the common interactive connection capability. */
|
|
1913
|
+
function integrationFlow(options) {
|
|
1914
|
+
const method = integrationMethod(options);
|
|
1915
|
+
const Payload = Schema.fromJsonString(Schema.Struct({ state: Schema.String }));
|
|
1916
|
+
return {
|
|
1917
|
+
complete: Effect.fn("VercelAuth.integrationFlow.complete")(function* (payload, callbackUrl) {
|
|
1918
|
+
const continuation = yield* Schema.decodeUnknownEffect(Payload)(payload.expose()).pipe(Effect.mapError(() => new Error$1({
|
|
1919
|
+
category: "authorization",
|
|
1920
|
+
message: "Vercel Integration continuation payload is invalid",
|
|
1921
|
+
operation: "VercelAuth.integrationFlow.complete",
|
|
1922
|
+
retry: "after-user-action"
|
|
1923
|
+
})));
|
|
1924
|
+
const code = callbackUrl.searchParams.get("code");
|
|
1925
|
+
if (callbackUrl.searchParams.get("state") !== continuation.state || code === null) return yield* new Error$1({
|
|
1926
|
+
category: "authorization",
|
|
1927
|
+
message: "Vercel Integration callback does not match its continuation",
|
|
1928
|
+
operation: "VercelAuth.integrationFlow.complete",
|
|
1929
|
+
retry: "after-user-action"
|
|
1930
|
+
});
|
|
1931
|
+
return yield* integrationAuthentication({
|
|
1932
|
+
...options.baseUrl === void 0 ? {} : { baseUrl: options.baseUrl },
|
|
1933
|
+
capabilities: options.capabilities,
|
|
1934
|
+
clientId: options.clientId,
|
|
1935
|
+
clientSecret: options.clientSecret,
|
|
1936
|
+
code: make$6(code),
|
|
1937
|
+
...options.fetch === void 0 ? {} : { fetch: options.fetch },
|
|
1938
|
+
redirectUri: options.redirectUri
|
|
1939
|
+
});
|
|
1940
|
+
}),
|
|
1941
|
+
method: "integration",
|
|
1942
|
+
providerId: "vercel",
|
|
1943
|
+
requiredCapabilities: [...options.capabilities],
|
|
1944
|
+
start: (continuationId) => {
|
|
1945
|
+
const authorizationUrl = new URL(method.installUrl);
|
|
1946
|
+
authorizationUrl.searchParams.set("source", "external");
|
|
1947
|
+
authorizationUrl.searchParams.set("state", continuationId);
|
|
1948
|
+
return Effect.succeed({
|
|
1949
|
+
authorizationUrl,
|
|
1950
|
+
payload: make$6(JSON.stringify({ state: continuationId }))
|
|
1951
|
+
});
|
|
1952
|
+
}
|
|
1953
|
+
};
|
|
1954
|
+
}
|
|
1955
|
+
function failure(operation, message, reason, status, code) {
|
|
1956
|
+
return new Error$3({
|
|
1957
|
+
...code === void 0 ? {} : { code },
|
|
1958
|
+
message,
|
|
1959
|
+
operation,
|
|
1960
|
+
providerId: "vercel",
|
|
1961
|
+
reason,
|
|
1962
|
+
...status === void 0 ? {} : { status }
|
|
1963
|
+
});
|
|
1964
|
+
}
|
|
1965
|
+
//#endregion
|
|
1966
|
+
//#region src/verification/dns-data.ts
|
|
1967
|
+
var dns_data_exports = /* @__PURE__ */ __exportAll({ parse: () => parse });
|
|
1968
|
+
const CanonicalAddress = Schema.String.pipe(Schema.decodeTo(Schema.String, SchemaTransformation.transform({
|
|
1969
|
+
decode: (value) => value.trim().toLowerCase(),
|
|
1970
|
+
encode: (value) => value
|
|
1971
|
+
})));
|
|
1972
|
+
const CanonicalDomain = Schema.String.pipe(Schema.decodeTo(Schema.String, SchemaTransformation.transformOrFail({
|
|
1973
|
+
decode: (value, options) => Effect.try({
|
|
1974
|
+
try: () => parse$1(value).toString(),
|
|
1975
|
+
catch: (cause) => new SchemaIssue.InvalidValue({ message: cause instanceof Error ? cause.message : String(cause) }, value, options)
|
|
1976
|
+
}),
|
|
1977
|
+
encode: (value) => Effect.succeed(value)
|
|
1978
|
+
})));
|
|
1979
|
+
const CanonicalMx = tokenizedDomain(2, 1);
|
|
1980
|
+
const CanonicalSrv = tokenizedDomain(4, 3);
|
|
1981
|
+
const CanonicalTxt = Schema.String.pipe(Schema.decodeTo(Schema.String, SchemaTransformation.transform({
|
|
1982
|
+
decode: (value) => {
|
|
1983
|
+
const trimmed = value.trim();
|
|
1984
|
+
const chunks = [...trimmed.matchAll(/"((?:\\.|[^"])*)"/g)];
|
|
1985
|
+
return chunks.length === 0 ? trimmed : chunks.map((match) => (match[1] ?? "").replace(/\\"/g, "\"").replace(/\\\\/g, "\\")).join("");
|
|
1986
|
+
},
|
|
1987
|
+
encode: (value) => value
|
|
1988
|
+
})));
|
|
1989
|
+
const CanonicalCaa = Schema.String.pipe(Schema.decodeTo(Schema.String, SchemaTransformation.transform({
|
|
1990
|
+
decode: (value) => value.trim().replace(/\s+"([\s\S]*)"$/, " $1"),
|
|
1991
|
+
encode: (value) => value
|
|
1992
|
+
})));
|
|
1993
|
+
function parse(type, input) {
|
|
1994
|
+
const schema = (() => {
|
|
1995
|
+
switch (type) {
|
|
1996
|
+
case "A":
|
|
1997
|
+
case "AAAA": return CanonicalAddress;
|
|
1998
|
+
case "CNAME":
|
|
1999
|
+
case "NS": return CanonicalDomain;
|
|
2000
|
+
case "MX": return CanonicalMx;
|
|
2001
|
+
case "SRV": return CanonicalSrv;
|
|
2002
|
+
case "TXT": return CanonicalTxt;
|
|
2003
|
+
case "CAA": return CanonicalCaa;
|
|
2004
|
+
}
|
|
2005
|
+
})();
|
|
2006
|
+
try {
|
|
2007
|
+
return Schema.decodeUnknownSync(schema)(input);
|
|
2008
|
+
} catch (cause) {
|
|
2009
|
+
throw new Error$2({ message: cause instanceof Error ? cause.message : String(cause) });
|
|
2010
|
+
}
|
|
2011
|
+
}
|
|
2012
|
+
function tokenizedDomain(length, domainIndex) {
|
|
2013
|
+
return Schema.String.pipe(Schema.decodeTo(Schema.String, SchemaTransformation.transformOrFail({
|
|
2014
|
+
decode: (value, options) => Effect.try({
|
|
2015
|
+
try: () => {
|
|
2016
|
+
const parts = value.trim().split(/\s+/);
|
|
2017
|
+
if (parts.length !== length) throw new Error("DNS record data has an invalid field count");
|
|
2018
|
+
const domain = parts[domainIndex];
|
|
2019
|
+
if (domain === void 0) throw new Error("DNS record target is missing");
|
|
2020
|
+
parts[domainIndex] = parse$1(domain);
|
|
2021
|
+
return parts.join(" ");
|
|
2022
|
+
},
|
|
2023
|
+
catch: (cause) => new SchemaIssue.InvalidValue({ message: cause instanceof Error ? cause.message : String(cause) }, value, options)
|
|
2024
|
+
}),
|
|
2025
|
+
encode: (value) => Effect.succeed(value)
|
|
2026
|
+
})));
|
|
2027
|
+
}
|
|
2028
|
+
//#endregion
|
|
2029
|
+
//#region src/verification/doh.ts
|
|
2030
|
+
var doh_exports = /* @__PURE__ */ __exportAll({
|
|
2031
|
+
layer: () => layer$2,
|
|
2032
|
+
make: () => make$3,
|
|
2033
|
+
toAsync: () => toAsync$2
|
|
2034
|
+
});
|
|
2035
|
+
function make$3(options) {
|
|
2036
|
+
const fetch = options.fetch ?? globalThis.fetch;
|
|
2037
|
+
const timeoutMs = options.timeoutMs ?? 5e3;
|
|
2038
|
+
return { resolve: Effect.fn("DnsOverHttps.resolve")((query) => Effect.tryPromise({
|
|
2039
|
+
try: (effectSignal) => request({
|
|
2040
|
+
effectSignal,
|
|
2041
|
+
fetch,
|
|
2042
|
+
query,
|
|
2043
|
+
timeoutMs,
|
|
2044
|
+
endpoint: options.endpoint
|
|
2045
|
+
}),
|
|
2046
|
+
catch: (cause) => cause instanceof Error$4 ? cause : new Error$4({
|
|
2047
|
+
cause,
|
|
2048
|
+
message: cause instanceof Error ? cause.message : "DNS resolution failed",
|
|
2049
|
+
reason: "transport"
|
|
2050
|
+
})
|
|
2051
|
+
}).pipe(Effect.flatMap((message) => decodeResolution(query, message)))) };
|
|
2052
|
+
}
|
|
2053
|
+
const layer$2 = (options) => Layer.succeed(Service$2, make$3(options));
|
|
2054
|
+
const toAsync$2 = (options) => toAsync$3(make$3(options));
|
|
2055
|
+
function decodeResolution(query, message) {
|
|
2056
|
+
return Effect.try({
|
|
2057
|
+
try: () => DnsPacket.decode(message),
|
|
2058
|
+
catch: (cause) => new Error$4({
|
|
2059
|
+
cause,
|
|
2060
|
+
message: "DoH returned an invalid DNS message",
|
|
2061
|
+
reason: "response"
|
|
2062
|
+
})
|
|
2063
|
+
}).pipe(Effect.flatMap((packet) => validatePacket(query, packet)), Effect.flatMap(decodePacket));
|
|
2064
|
+
}
|
|
2065
|
+
function decodePacket(packet) {
|
|
2066
|
+
if (packet.rcode === "NXDOMAIN") return Effect.succeed({ _tag: "nodata" });
|
|
2067
|
+
if (packet.rcode !== "NOERROR") return Effect.fail(new Error$4({
|
|
2068
|
+
message: `DNS resolver returned ${packet.rcode ?? "an unknown response code"}`,
|
|
2069
|
+
reason: "response"
|
|
2070
|
+
}));
|
|
2071
|
+
if (packet.answers === void 0 || packet.answers.length === 0) return Effect.succeed({ _tag: "nodata" });
|
|
2072
|
+
return Effect.forEach(packet.answers, decodeAnswer, { concurrency: "unbounded" }).pipe(Effect.map((answers) => {
|
|
2073
|
+
const supportedAnswers = answers.filter((answer) => answer !== null);
|
|
2074
|
+
return supportedAnswers.length === 0 ? { _tag: "nodata" } : {
|
|
2075
|
+
_tag: "answer",
|
|
2076
|
+
answers: supportedAnswers
|
|
2077
|
+
};
|
|
2078
|
+
}));
|
|
2079
|
+
}
|
|
2080
|
+
function validatePacket(query, packet) {
|
|
2081
|
+
return Effect.try({
|
|
2082
|
+
try: () => {
|
|
2083
|
+
if (packet.type !== "response") throw new Error("DoH returned a DNS query");
|
|
2084
|
+
if (packet.id !== 0) throw new Error("DoH response used an unexpected message ID");
|
|
2085
|
+
if (packet.flag_tc) throw new Error("DoH returned a truncated DNS response");
|
|
2086
|
+
const questions = packet.questions ?? [];
|
|
2087
|
+
if (questions.length !== 1) throw new Error("DoH returned an unexpected question count");
|
|
2088
|
+
const question = questions[0];
|
|
2089
|
+
if (question === void 0) throw new Error("DoH response omitted the DNS question");
|
|
2090
|
+
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");
|
|
2091
|
+
return packet;
|
|
2092
|
+
},
|
|
2093
|
+
catch: (cause) => new Error$4({
|
|
2094
|
+
cause,
|
|
2095
|
+
message: cause instanceof Error ? cause.message : "DoH returned an invalid DNS response",
|
|
2096
|
+
reason: "response"
|
|
2097
|
+
})
|
|
2098
|
+
});
|
|
2099
|
+
}
|
|
2100
|
+
function decodeAnswer(answer) {
|
|
2101
|
+
return Effect.gen(function* () {
|
|
2102
|
+
if (answer.class !== "IN") return null;
|
|
2103
|
+
const decoded = yield* Effect.try({
|
|
2104
|
+
try: () => answerData(answer),
|
|
2105
|
+
catch: (cause) => new Error$4({
|
|
2106
|
+
cause,
|
|
2107
|
+
message: "DoH returned invalid DNS record data",
|
|
2108
|
+
reason: "response"
|
|
2109
|
+
})
|
|
2110
|
+
});
|
|
2111
|
+
if (decoded === void 0) return null;
|
|
2112
|
+
const name = yield* decode$8(answer.name);
|
|
2113
|
+
const data = yield* Effect.try({
|
|
2114
|
+
try: () => parse(decoded.type, decoded.data),
|
|
2115
|
+
catch: (cause) => new Error$4({
|
|
2116
|
+
cause,
|
|
2117
|
+
message: "DoH returned invalid DNS record data",
|
|
2118
|
+
reason: "response"
|
|
2119
|
+
})
|
|
2120
|
+
});
|
|
2121
|
+
if (answer.ttl === void 0) return yield* new Error$4({
|
|
2122
|
+
message: "DoH response omitted the DNS record TTL",
|
|
2123
|
+
reason: "response"
|
|
2124
|
+
});
|
|
2125
|
+
return {
|
|
2126
|
+
data,
|
|
2127
|
+
name,
|
|
2128
|
+
ttl: answer.ttl,
|
|
2129
|
+
type: decoded.type
|
|
2130
|
+
};
|
|
2131
|
+
}).pipe(Effect.mapError((cause) => cause instanceof Error$4 ? cause : new Error$4({
|
|
2132
|
+
cause,
|
|
2133
|
+
message: cause.message,
|
|
2134
|
+
reason: "response"
|
|
2135
|
+
})));
|
|
2136
|
+
}
|
|
2137
|
+
function answerData(answer) {
|
|
2138
|
+
switch (answer.type) {
|
|
2139
|
+
case "A":
|
|
2140
|
+
case "AAAA":
|
|
2141
|
+
case "CNAME":
|
|
2142
|
+
case "NS": return {
|
|
2143
|
+
data: answer.data,
|
|
2144
|
+
type: answer.type
|
|
2145
|
+
};
|
|
2146
|
+
case "CAA": return {
|
|
2147
|
+
data: `${answer.data.flags ?? 0} ${answer.data.tag} ${answer.data.value}`,
|
|
2148
|
+
type: answer.type
|
|
2149
|
+
};
|
|
2150
|
+
case "MX": return {
|
|
2151
|
+
data: `${requiredNumber(answer.data.preference, "MX preference")} ${answer.data.exchange}`,
|
|
2152
|
+
type: answer.type
|
|
2153
|
+
};
|
|
2154
|
+
case "SRV": return {
|
|
2155
|
+
data: [
|
|
2156
|
+
requiredNumber(answer.data.priority, "SRV priority"),
|
|
2157
|
+
requiredNumber(answer.data.weight, "SRV weight"),
|
|
2158
|
+
requiredNumber(answer.data.port, "SRV port"),
|
|
2159
|
+
answer.data.target
|
|
2160
|
+
].join(" "),
|
|
2161
|
+
type: answer.type
|
|
2162
|
+
};
|
|
2163
|
+
case "TXT": return {
|
|
2164
|
+
data: decodeTxt(answer.data),
|
|
2165
|
+
type: answer.type
|
|
2166
|
+
};
|
|
2167
|
+
default: return;
|
|
2168
|
+
}
|
|
2169
|
+
}
|
|
2170
|
+
function decodeTxt(data) {
|
|
2171
|
+
const encoded = (Array.isArray(data) ? data : [data]).map((chunk) => typeof chunk === "string" ? new TextEncoder().encode(chunk) : chunk);
|
|
2172
|
+
const length = encoded.reduce((total, chunk) => total + chunk.length, 0);
|
|
2173
|
+
const joined = new Uint8Array(length);
|
|
2174
|
+
let offset = 0;
|
|
2175
|
+
for (const chunk of encoded) {
|
|
2176
|
+
joined.set(chunk, offset);
|
|
2177
|
+
offset += chunk.length;
|
|
2178
|
+
}
|
|
2179
|
+
return new TextDecoder("utf-8", { fatal: true }).decode(joined);
|
|
2180
|
+
}
|
|
2181
|
+
function requiredNumber(value, field) {
|
|
2182
|
+
if (value === void 0) throw new Error(`DoH response omitted ${field}`);
|
|
2183
|
+
return value;
|
|
2184
|
+
}
|
|
2185
|
+
async function request(input) {
|
|
2186
|
+
const controller = new AbortController();
|
|
2187
|
+
let timedOut = false;
|
|
2188
|
+
const abortFromEffect = () => controller.abort(input.effectSignal.reason);
|
|
2189
|
+
const abortFromHost = () => controller.abort(input.query.signal?.reason);
|
|
2190
|
+
input.effectSignal.addEventListener("abort", abortFromEffect, { once: true });
|
|
2191
|
+
input.query.signal?.addEventListener("abort", abortFromHost, { once: true });
|
|
2192
|
+
const timer = setTimeout(() => {
|
|
2193
|
+
timedOut = true;
|
|
2194
|
+
controller.abort(/* @__PURE__ */ new Error("DNS resolution timed out"));
|
|
2195
|
+
}, input.timeoutMs);
|
|
2196
|
+
try {
|
|
2197
|
+
const query = Uint8Array.from(DnsPacket.encode({
|
|
2198
|
+
flags: DnsPacket.RECURSION_DESIRED,
|
|
2199
|
+
id: 0,
|
|
2200
|
+
questions: [{
|
|
2201
|
+
name: input.query.name,
|
|
2202
|
+
type: input.query.type
|
|
2203
|
+
}],
|
|
2204
|
+
type: "query"
|
|
2205
|
+
}));
|
|
2206
|
+
const response = await input.fetch(new URL(input.endpoint), {
|
|
2207
|
+
body: query,
|
|
2208
|
+
headers: {
|
|
2209
|
+
accept: "application/dns-message",
|
|
2210
|
+
"content-type": "application/dns-message"
|
|
2211
|
+
},
|
|
2212
|
+
method: "POST",
|
|
2213
|
+
signal: controller.signal
|
|
2214
|
+
});
|
|
2215
|
+
if (!response.ok) throw new Error$4({
|
|
2216
|
+
message: `DoH returned HTTP ${response.status}`,
|
|
2217
|
+
reason: "transport"
|
|
2218
|
+
});
|
|
2219
|
+
const contentType = response.headers.get("content-type")?.split(";", 1)[0]?.trim().toLowerCase();
|
|
2220
|
+
if (contentType !== "application/dns-message") throw new Error$4({
|
|
2221
|
+
message: `DoH returned unsupported content type ${contentType ?? "<missing>"}`,
|
|
2222
|
+
reason: "response"
|
|
2223
|
+
});
|
|
2224
|
+
return new Uint8Array(await response.arrayBuffer());
|
|
2225
|
+
} catch (cause) {
|
|
2226
|
+
if (timedOut) throw new Error$4({
|
|
2227
|
+
message: "DNS query timed out",
|
|
2228
|
+
reason: "timeout"
|
|
2229
|
+
});
|
|
2230
|
+
throw cause;
|
|
2231
|
+
} finally {
|
|
2232
|
+
clearTimeout(timer);
|
|
2233
|
+
input.effectSignal.removeEventListener("abort", abortFromEffect);
|
|
2234
|
+
input.query.signal?.removeEventListener("abort", abortFromHost);
|
|
2235
|
+
}
|
|
2236
|
+
}
|
|
2237
|
+
//#endregion
|
|
2238
|
+
//#region src/verification/cloudflare-doh.ts
|
|
2239
|
+
var cloudflare_doh_exports = /* @__PURE__ */ __exportAll({
|
|
2240
|
+
layer: () => layer$1,
|
|
2241
|
+
make: () => make$2,
|
|
2242
|
+
toAsync: () => toAsync$1
|
|
2243
|
+
});
|
|
2244
|
+
const endpoint$1 = "https://cloudflare-dns.com/dns-query";
|
|
2245
|
+
function make$2(options = {}) {
|
|
2246
|
+
return make$3({
|
|
2247
|
+
...options,
|
|
2248
|
+
endpoint: options.endpoint ?? endpoint$1
|
|
2249
|
+
});
|
|
2250
|
+
}
|
|
2251
|
+
const layer$1 = (options = {}) => Layer.succeed(Service$2, make$2(options));
|
|
2252
|
+
const toAsync$1 = (options = {}) => toAsync$3(make$2(options));
|
|
2253
|
+
//#endregion
|
|
2254
|
+
//#region src/verification/google-doh.ts
|
|
2255
|
+
var google_doh_exports = /* @__PURE__ */ __exportAll({
|
|
2256
|
+
layer: () => layer,
|
|
2257
|
+
make: () => make$1,
|
|
2258
|
+
toAsync: () => toAsync
|
|
2259
|
+
});
|
|
2260
|
+
const endpoint = "https://dns.google/dns-query";
|
|
2261
|
+
function make$1(options = {}) {
|
|
2262
|
+
return make$3({
|
|
2263
|
+
...options,
|
|
2264
|
+
endpoint: options.endpoint ?? endpoint
|
|
2265
|
+
});
|
|
2266
|
+
}
|
|
2267
|
+
const layer = (options = {}) => Layer.succeed(Service$2, make$1(options));
|
|
2268
|
+
const toAsync = (options = {}) => toAsync$3(make$1(options));
|
|
2269
|
+
//#endregion
|
|
2270
|
+
//#region src/verification/resolver-pool.ts
|
|
2271
|
+
var resolver_pool_exports = /* @__PURE__ */ __exportAll({
|
|
2272
|
+
Observation: () => Observation,
|
|
2273
|
+
Policy: () => Policy,
|
|
2274
|
+
Service: () => Service,
|
|
2275
|
+
defaultLayer: () => defaultLayer,
|
|
2276
|
+
defaultMake: () => defaultMake,
|
|
2277
|
+
make: () => make
|
|
2278
|
+
});
|
|
2279
|
+
const Observation = Data.taggedEnum();
|
|
2280
|
+
const PolicyVariants = Data.taggedEnum();
|
|
2281
|
+
const QuorumMinimum = Schema.Int.check(Schema.isGreaterThanOrEqualTo(1));
|
|
2282
|
+
const PolicySchema = Schema.TaggedUnion({
|
|
2283
|
+
AllMatch: {},
|
|
2284
|
+
AnyMatch: {},
|
|
2285
|
+
Quorum: { minimum: QuorumMinimum }
|
|
2286
|
+
});
|
|
2287
|
+
const Policy = {
|
|
2288
|
+
AllMatch: PolicyVariants.AllMatch,
|
|
2289
|
+
AnyMatch: PolicyVariants.AnyMatch,
|
|
2290
|
+
Quorum: ({ minimum }) => PolicyVariants.Quorum({ minimum: Schema.decodeUnknownSync(QuorumMinimum)(minimum) }),
|
|
2291
|
+
Schema: PolicySchema
|
|
2292
|
+
};
|
|
2293
|
+
var Service = class extends Context.Service()("@domainkit/DnsResolverPool") {};
|
|
2294
|
+
function make(entries) {
|
|
2295
|
+
return Service.of({ observe: Effect.fn("DnsResolverPool.observe")((query) => Effect.forEach(entries, (entry) => observe$1(entry, query), { concurrency: "unbounded" })) });
|
|
2296
|
+
}
|
|
2297
|
+
/** Cloudflare and Google RFC wire-format DoH resolvers. */
|
|
2298
|
+
function defaultMake(options = {}) {
|
|
2299
|
+
const timeoutMs = options.timeoutMs ?? 5e3;
|
|
2300
|
+
return make([{
|
|
2301
|
+
id: "cloudflare",
|
|
2302
|
+
resolver: make$2(options),
|
|
2303
|
+
timeoutMs
|
|
2304
|
+
}, {
|
|
2305
|
+
id: "google",
|
|
2306
|
+
resolver: make$1(options),
|
|
2307
|
+
timeoutMs
|
|
2308
|
+
}]);
|
|
2309
|
+
}
|
|
2310
|
+
const defaultLayer = (options = {}) => Layer.succeed(Service, defaultMake(options));
|
|
2311
|
+
function observe$1(entry, query) {
|
|
2312
|
+
const timeoutMs = entry.timeoutMs ?? 5e3;
|
|
2313
|
+
return entry.resolver.resolve(query).pipe(Effect.timeout(timeoutMs), Effect.match({
|
|
2314
|
+
onFailure: (failure) => {
|
|
2315
|
+
if (failure._tag === "TimeoutError" || failure._tag === "ResolverError" && failure.reason === "timeout") return Observation.TimedOut({
|
|
2316
|
+
resolverId: entry.id,
|
|
2317
|
+
timeoutMs
|
|
2318
|
+
});
|
|
2319
|
+
return Observation.Failed({
|
|
2320
|
+
message: failure.message,
|
|
2321
|
+
reason: failure.reason === "response" ? "response" : "transport",
|
|
2322
|
+
resolverId: entry.id
|
|
2323
|
+
});
|
|
2324
|
+
},
|
|
2325
|
+
onSuccess: (resolution) => resolution._tag === "nodata" ? Observation.NoData({ resolverId: entry.id }) : Observation.Answer({
|
|
2326
|
+
answers: resolution.answers,
|
|
2327
|
+
resolverId: entry.id
|
|
2328
|
+
})
|
|
2329
|
+
}));
|
|
2330
|
+
}
|
|
2331
|
+
//#endregion
|
|
2332
|
+
//#region src/verification/verify.ts
|
|
2333
|
+
var verify_exports = /* @__PURE__ */ __exportAll({
|
|
2334
|
+
Provider: () => Provider,
|
|
2335
|
+
ProviderObservation: () => ProviderObservation,
|
|
2336
|
+
PublicDns: () => PublicDns,
|
|
2337
|
+
PublicDnsObservation: () => PublicDnsObservation,
|
|
2338
|
+
Result: () => Result,
|
|
2339
|
+
observe: () => observe
|
|
2340
|
+
});
|
|
2341
|
+
const PublicDns = Data.taggedEnum();
|
|
2342
|
+
const Provider = Data.taggedEnum();
|
|
2343
|
+
const ProviderObservation = Data.taggedEnum();
|
|
2344
|
+
const PublicDnsObservation = Data.taggedEnum();
|
|
2345
|
+
const Result = Data.taggedEnum();
|
|
2346
|
+
function observe(input) {
|
|
2347
|
+
return observeEffect(input);
|
|
2348
|
+
}
|
|
2349
|
+
const observeEffect = Effect.fn("Verification.observe")(function* (input) {
|
|
2350
|
+
const publicDns = input.publicDns ?? PublicDns.Enabled({});
|
|
2351
|
+
const provider = input.provider ?? Provider.Disabled();
|
|
2352
|
+
const publicEffect = publicDns._tag === "Disabled" ? Effect.succeed(null) : observePublicDns(input.record, publicDns.policy ?? Policy.AnyMatch());
|
|
2353
|
+
const providerEffect = provider._tag === "Disabled" ? Effect.succeed(null) : Effect.flatMap(Service$3, (service) => observeProvider(service, provider.zone, input.record).pipe(Effect.catch((failure) => Effect.succeed(ProviderObservation.Unavailable({ message: failure.message })))));
|
|
2354
|
+
const [providerObservation, publicDnsObservation] = yield* Effect.all([providerEffect, publicEffect], { concurrency: "unbounded" });
|
|
2355
|
+
return aggregate({
|
|
2356
|
+
provider: providerObservation,
|
|
2357
|
+
publicDns: publicDnsObservation
|
|
2358
|
+
});
|
|
2359
|
+
});
|
|
2360
|
+
function observeProvider(provider, zone, requirement) {
|
|
2361
|
+
return provider.listRecords(zone).pipe(Effect.map((records) => {
|
|
2362
|
+
if (records.some((existing) => existing._tag !== "Opaque" && equals(existing, requirement))) return ProviderObservation.Matched();
|
|
2363
|
+
const sameSet = records.filter((existing) => existing._tag !== "Opaque" && existing.name === requirement.name && existing._tag === requirement._tag);
|
|
2364
|
+
return sameSet.length === 0 ? ProviderObservation.Pending() : ProviderObservation.Mismatch({ records: sameSet });
|
|
2365
|
+
}));
|
|
2366
|
+
}
|
|
2367
|
+
function observePublicDns(requirement, policy) {
|
|
2368
|
+
return Effect.gen(function* () {
|
|
2369
|
+
const validatedPolicy = yield* Schema.decodeUnknownEffect(Policy.Schema)(policy).pipe(Effect.mapError((cause) => new Error$2({ message: cause.message })));
|
|
2370
|
+
const configuredPool = yield* Effect.serviceOption(Service);
|
|
2371
|
+
const evidence = yield* Option.getOrElse(configuredPool, () => defaultMake()).observe({
|
|
2372
|
+
name: requirement.name,
|
|
2373
|
+
type: requirement._tag
|
|
2374
|
+
});
|
|
2375
|
+
if (validatedPolicy._tag === "Quorum" && validatedPolicy.minimum > evidence.length) return yield* new Error$2({ message: `Quorum minimum ${validatedPolicy.minimum} exceeds the ${evidence.length} configured resolvers` });
|
|
2376
|
+
return evaluatePublicDns(requirement, validatedPolicy, evidence);
|
|
2377
|
+
});
|
|
2378
|
+
}
|
|
2379
|
+
function evaluatePublicDns(requirement, policy, evidence) {
|
|
2380
|
+
const matchedResolverIds = evidence.flatMap((observation) => observation._tag === "Answer" && answersMatch(requirement, observation.answers) ? [observation.resolverId] : []);
|
|
2381
|
+
const required = policy._tag === "AnyMatch" ? 1 : policy._tag === "AllMatch" ? evidence.length : policy.minimum;
|
|
2382
|
+
if (required > 0 && matchedResolverIds.length >= required) return PublicDnsObservation.Verified({
|
|
2383
|
+
evidence,
|
|
2384
|
+
matchedResolverIds
|
|
2385
|
+
});
|
|
2386
|
+
const unavailable = evidence.filter((observation) => observation._tag === "TimedOut" || observation._tag === "Failed").length;
|
|
2387
|
+
if (unavailable > 0 && matchedResolverIds.length + unavailable >= required) return PublicDnsObservation.Unavailable({ evidence });
|
|
2388
|
+
if (evidence.some((observation) => observation._tag === "Answer" && !answersMatch(requirement, observation.answers))) return PublicDnsObservation.Mismatch({ evidence });
|
|
2389
|
+
if (evidence.some((observation) => observation._tag === "NoData")) return PublicDnsObservation.Pending({ evidence });
|
|
2390
|
+
return PublicDnsObservation.Unavailable({ evidence });
|
|
2391
|
+
}
|
|
2392
|
+
function answersMatch(requirement, answers) {
|
|
2393
|
+
const expected = parse(requirement._tag, recordValue(requirement));
|
|
2394
|
+
return answers.some((answer) => answer.name === requirement.name && answer.type === requirement._tag && parse(requirement._tag, answer.data) === expected);
|
|
2395
|
+
}
|
|
2396
|
+
function aggregate(evidence) {
|
|
2397
|
+
const requested = [evidence.provider, evidence.publicDns].filter((observation) => observation !== null);
|
|
2398
|
+
if (requested.length === 0) return Result.NotObserved(evidence);
|
|
2399
|
+
if (requested.every((observation) => observation._tag === "Matched" || observation._tag === "Verified")) return Result.Verified(evidence);
|
|
2400
|
+
if (requested.some((observation) => observation._tag === "Unavailable")) return Result.Unavailable(evidence);
|
|
2401
|
+
if (requested.some((observation) => observation._tag === "Mismatch")) return Result.Mismatch(evidence);
|
|
2402
|
+
return Result.Pending(evidence);
|
|
2403
|
+
}
|
|
2404
|
+
function recordValue(requirement) {
|
|
2405
|
+
const data$1 = data(requirement);
|
|
2406
|
+
switch (requirement._tag) {
|
|
2407
|
+
case "A":
|
|
2408
|
+
case "AAAA": return String(data$1.address);
|
|
2409
|
+
case "CNAME":
|
|
2410
|
+
case "NS": return String(data$1.target);
|
|
2411
|
+
case "TXT": return String(data$1.value);
|
|
2412
|
+
case "MX": return `${data$1.priority} ${data$1.exchange}`;
|
|
2413
|
+
case "CAA": return `${data$1.flags} ${data$1.tag} ${data$1.value}`;
|
|
2414
|
+
case "SRV": return `${data$1.priority} ${data$1.weight} ${data$1.port} ${data$1.target}`;
|
|
2415
|
+
}
|
|
2416
|
+
}
|
|
2417
|
+
//#endregion
|
|
2418
|
+
export { RemoveDomainResult as $, make$4 as A, discovery$1 as B, integrationFlow as C, encode$3 as Ct, tokenMethod as D, tokenConnectionMethod as E, subjectResolver as F, secret_exports as G, authorize as H, tokenConnectionMethod$1 as I, decode$4 as J, Method as K, tokenMethod$1 as L, manifest$1 as M, oauthFlow as N, client_exports as O, oauthMethod as P, Method$1 as Q, tokenValidator as R, exchangeCode as S, decode$6 as St, manifest as T, version as Tt, connection_authorization_exports as U, make$5 as V, selection_exports as W, diagnostic_exports as X, manifest_exports as Y, Error$1 as Z, toAsync$1 as _, CapabilityEvidence as _t, Result as a, AuthorizationError as at, dns_data_exports as b, Schema$3 as bt, Observation as c, assertGrant as ct, defaultMake as d, decode$5 as dt, StartResult as et, make as f, encode$2 as ft, cloudflare_doh_exports as g, Capability as gt, toAsync as h, validate as ht, PublicDnsObservation as i, start$1 as it, auth_exports$1 as j, discovery as k, Policy as l, authorizationError as lt, google_doh_exports as m, removeDomain$1 as mt, ProviderObservation as n, extend as nt, observe as o, Grant as ot, resolver_pool_exports as p, includeDomains as pt, Schema$1 as q, PublicDns as r, removeDomain as rt, verify_exports as s, Schema$2 as st, Provider as t, complete$1 as tt, Service as u, coversDomain as ut, doh_exports as v, Evidence$1 as vt, integrationMethod as w, provider_context_exports as wt, auth_exports as x, authorization_exports as xt, toAsync$2 as y, EvidenceSchema as yt, client_exports$1 as z };
|
|
2419
|
+
|
|
2420
|
+
//# sourceMappingURL=verify-B00c81-n.mjs.map
|