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