domainkit 0.7.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 +214 -39
- 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 -356
- 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-10vHuShV.d.mts +0 -1401
- package/dist/connect-oI8hcMxc.mjs +0 -185
- package/dist/connect-oI8hcMxc.mjs.map +0 -1
- package/dist/connection-C0xsU41C.d.mts +0 -186
- package/dist/connection-api-DW7xUJdj.d.mts +0 -26
- package/dist/index.d.mts +0 -882
- 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-DPJs8h0a.mjs +0 -859
- package/dist/resolver-DPJs8h0a.mjs.map +0 -1
- package/dist/resolver-DRrLm_hB.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-C3V_GtzL.d.mts +0 -1268
- package/dist/verify-DDnK8U7C.mjs +0 -2396
- package/dist/verify-DDnK8U7C.mjs.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Cloudflare.mjs","names":["Client.listZones","OAuth.authorizationUrl","OAuth.exchangeCode","Client.provider","Errors.fail","Reason.Unauthenticated","OAuth.refresh","OAuth.revoke","Provider.make","Provider.tokenAuth","Client.verifyToken","Client.tokenExpiry","DomainName.decode","Provider.resolveAmong","Reason.Unsupported","Client.dns"],"sources":["../src/Cloudflare.ts"],"sourcesContent":["/** Cloudflare: personal API tokens and OAuth. */\nimport { type Config, Effect, Redacted, Schema } from \"effect\";\n\nimport * as Errors from \"./internal/error.ts\";\nimport * as Reason from \"./Reason.ts\";\nimport * as DomainName from \"./DomainName.ts\";\nimport * as Client from \"./internal/cloudflare/client.ts\";\nimport type * as Protocol from \"./internal/cloudflare/protocol.ts\";\nimport { resolve } from \"./internal/config.ts\";\nimport type { Fetch } from \"./internal/http.ts\";\nimport * as OAuth from \"./internal/oauth.ts\";\nimport * as Provider from \"./Provider.ts\";\n\n/**\n * Authorization context: `accountId` is `null` when the credential spans several accounts or\n * none yet. Target context: the zone's account and zone id.\n */\nexport const AccountContext = Schema.Struct({\n accountId: Schema.NullOr(Schema.String),\n /** How the token was verified: user tokens span accounts, account tokens are pinned. */\n tokenKind: Schema.optionalKey(Schema.Literals([\"user\", \"account\"])),\n zoneId: Schema.optionalKey(Schema.String),\n});\n\n/** Token-method input: the API token, and the account id for account-owned tokens. */\nexport const TokenFields = Schema.Struct({\n token: Schema.RedactedFromValue(Schema.String),\n accountId: Schema.optionalKey(Schema.String),\n});\nexport type AccountContext = typeof AccountContext.Type;\n\nexport interface Options {\n /** Omit to offer tokens only. */\n readonly oauth?: {\n readonly clientId: string | Config.Config<string>;\n readonly clientSecret: Redacted.Redacted<string> | Config.Config<Redacted.Redacted<string>>;\n /** Scope ids assigned to the OAuth client. Default: `zone:read`, `dns_records:edit`, `offline_access`. */\n readonly scopes?: ReadonlyArray<string>;\n /** Default `client_secret_basic`. */\n readonly clientAuth?: OAuth.ClientAuth;\n };\n readonly fetch?: Fetch;\n readonly baseUrl?: string;\n}\n\nexport const server: OAuth.Server = {\n authorization_endpoint: \"https://dash.cloudflare.com/oauth2/auth\",\n issuer: \"https://dash.cloudflare.com\",\n revocation_endpoint: \"https://dash.cloudflare.com/oauth2/revoke\",\n token_endpoint: \"https://dash.cloudflare.com/oauth2/token\",\n};\n\nconst defaultScopes = [\"zone:read\", \"dns_records:edit\", \"offline_access\"];\nconst capabilities = [\"dns:read\", \"dns:write\"] as const;\n\n/** OAuth credentials pack both tokens into `secret`; token credentials are the token itself. */\nconst Secret = Schema.fromJsonString(\n Schema.Struct({ accessToken: Schema.String, refreshToken: Schema.NullOr(Schema.String) }),\n);\n\nconst parseSecret = (secret: Redacted.Redacted<string>) => {\n const raw = Redacted.value(secret);\n const parsed = raw.startsWith(\"{\") ? Schema.decodeUnknownOption(Secret)(raw) : undefined;\n return parsed !== undefined && parsed._tag === \"Some\"\n ? parsed.value\n : { accessToken: raw, refreshToken: null };\n};\n\nconst packSecret = (tokens: OAuth.Tokens) =>\n Redacted.make(\n Schema.encodeSync(Secret)({\n accessToken: tokens.accessToken,\n refreshToken: tokens.refreshToken,\n }),\n );\n\nexport const provider = (options: Options = {}): Provider.Definition<AccountContext> => {\n const fetch = options.fetch ?? globalThis.fetch;\n const baseUrl = (options.baseUrl ?? \"https://api.cloudflare.com/client/v4\").replace(/\\/$/, \"\");\n const client = (token: string): Client.Options => ({ token, fetch, baseUrl });\n\n const discoverAccount = (token: string) =>\n Client.listZones(client(token)).pipe(\n Effect.map((zones) => {\n const accounts = new Set(zones.map((zone) => zone.account.id));\n const [only] = accounts;\n return accounts.size === 1 && only !== undefined ? only : null;\n }),\n );\n\n const targetOf = (zone: Protocol.Zone): Provider.Target => ({\n zone: zone.name,\n context: { accountId: zone.account.id, zoneId: zone.id } satisfies AccountContext,\n label: `${zone.name} (${zone.account.name})`,\n nameservers: zone.name_servers ?? [],\n });\n\n const oauthClient = (oauth: NonNullable<Options[\"oauth\"]>) =>\n Effect.gen(function* () {\n const clientId = yield* resolve(oauth.clientId, \"oauth.clientId\");\n const clientSecret = yield* resolve(oauth.clientSecret, \"oauth.clientSecret\");\n return {\n clientId,\n clientSecret,\n clientAuth: oauth.clientAuth ?? \"client_secret_basic\",\n } satisfies OAuth.Client;\n });\n\n const issued = (\n tokens: OAuth.Tokens,\n ): Effect.Effect<Provider.IssuedCredential, Errors.DomainKitError> =>\n discoverAccount(tokens.accessToken).pipe(\n Effect.map((accountId) => ({\n secret: packSecret(tokens),\n context: { accountId } satisfies AccountContext,\n expiresAt: tokens.expiresAt,\n })),\n );\n\n const oauth = options.oauth === undefined ? undefined : oauthAuth(options.oauth);\n\n function oauthAuth(settings: NonNullable<Options[\"oauth\"]>): Provider.OAuthAuth {\n const scopes = settings.scopes ?? defaultScopes;\n return {\n label: \"Sign in with Cloudflare\",\n scopes,\n start: (input) =>\n oauthClient(settings).pipe(\n Effect.map(({ clientId }) => ({\n authorizationUrl: OAuth.authorizationUrl({\n server,\n clientId,\n scopes,\n state: input.state,\n callbackUrl: input.callbackUrl,\n codeChallenge: input.codeChallenge,\n }),\n })),\n ),\n complete: (input) =>\n oauthClient(settings).pipe(\n Effect.flatMap((oauthClientValue) =>\n OAuth.exchangeCode({\n provider: Client.provider,\n server,\n client: oauthClientValue,\n code: input.code,\n state: input.params.state ?? \"\",\n callbackUrl: input.callbackUrl,\n codeVerifier: input.codeVerifier,\n fetch,\n }),\n ),\n Effect.flatMap(issued),\n ),\n refresh: (credential) =>\n Effect.gen(function* () {\n const { refreshToken } = parseSecret(credential.secret);\n if (refreshToken === null) {\n return yield* Errors.fail(\n new Reason.Unauthenticated({\n message: \"Cloudflare credential has no refresh token\",\n }),\n );\n }\n const tokens = yield* OAuth.refresh({\n provider: Client.provider,\n server,\n client: yield* oauthClient(settings),\n refreshToken,\n fetch,\n });\n return {\n secret: packSecret(tokens),\n context: credential.context,\n expiresAt: tokens.expiresAt,\n };\n }),\n revoke: (credential) =>\n Effect.gen(function* () {\n const { accessToken } = parseSecret(credential.secret);\n yield* OAuth.revoke({\n provider: Client.provider,\n server,\n client: yield* oauthClient(settings),\n token: accessToken,\n fetch,\n });\n }),\n };\n }\n\n return Provider.make<AccountContext>({\n id: \"cloudflare\",\n name: \"Cloudflare\",\n context: AccountContext,\n contextVersion: \"cloudflare.v1\",\n auth: {\n token: Provider.tokenAuth({\n label: \"API token\",\n docsUrl: \"https://developers.cloudflare.com/fundamentals/api/get-started/create-token/\",\n requiredCapabilities: capabilities,\n fields: TokenFields,\n authenticate: ({ token, accountId }) =>\n Effect.gen(function* () {\n const raw = Redacted.value(token);\n if (accountId !== undefined) {\n const expiresAt = yield* Client.verifyToken(\n client(raw),\n `/accounts/${encodeURIComponent(accountId)}/tokens/verify`,\n );\n return {\n secret: token,\n context: { accountId, tokenKind: \"account\" } satisfies AccountContext,\n expiresAt,\n };\n }\n const discovered = yield* discoverAccount(raw);\n const verified = yield* Client.tokenExpiry(client(raw), discovered);\n return {\n secret: token,\n context: { accountId: discovered, tokenKind: verified.kind } satisfies AccountContext,\n expiresAt: verified.expiresAt,\n };\n }),\n }),\n ...(oauth === undefined ? {} : { oauth }),\n },\n session: (credential) => {\n const { accessToken } = parseSecret(credential.secret);\n const clientOptions = client(accessToken);\n const context = Schema.decodeUnknownOption(AccountContext)(credential.context);\n const accountId =\n context._tag === \"Some\" ? (context.value.accountId ?? undefined) : undefined;\n const listTargets = () =>\n Client.listZones(clientOptions, accountId).pipe(Effect.map((zones) => zones.map(targetOf)));\n return {\n capabilities: () => Effect.succeed(capabilities),\n listTargets,\n resolveTarget: (domain) =>\n Effect.gen(function* () {\n const name = yield* DomainName.decode(domain);\n const targets = yield* listTargets();\n return Provider.resolveAmong(name, targets);\n }),\n dns: (target) => {\n const zone = Schema.decodeUnknownOption(AccountContext)(target.context);\n const zoneId = zone._tag === \"Some\" ? zone.value.zoneId : undefined;\n if (zoneId === undefined) {\n const failure = Errors.fail(\n new Reason.Unsupported({\n provider: \"cloudflare\",\n operation: \"dns\",\n message: \"Cloudflare target has no zone id; only zone targets host records\",\n }),\n );\n return {\n list: () => failure,\n create: () => failure,\n get: () => failure,\n delete: () => failure,\n };\n }\n return Client.dns(clientOptions, zoneId);\n },\n };\n },\n });\n};\n"],"mappings":"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"}
|
package/dist/Connect.mjs
ADDED
|
@@ -0,0 +1,463 @@
|
|
|
1
|
+
import { __exportAll } from "./_virtual/_rolldown/runtime.mjs";
|
|
2
|
+
import { candidates, decode } from "./DomainName.mjs";
|
|
3
|
+
import { Forbidden, InvalidInput, NotFound, Reconnect, Unauthenticated } from "./Reason.mjs";
|
|
4
|
+
import { DomainKitError, decode as decode$1, fail } from "./internal/error.mjs";
|
|
5
|
+
import { Service as Service$1 } from "./Custody.mjs";
|
|
6
|
+
import { fresh } from "./internal/ids.mjs";
|
|
7
|
+
import { pkce } from "./internal/oauth.mjs";
|
|
8
|
+
import { Service as Service$2 } from "./Principal.mjs";
|
|
9
|
+
import { Resolution, decodeContext, describeMethods, encodeContext } from "./Provider.mjs";
|
|
10
|
+
import { Service as Service$3 } from "./Providers.mjs";
|
|
11
|
+
import { Service as Service$4 } from "./Resolver.mjs";
|
|
12
|
+
import { Attachment, Authorization, Continuation, Credential, Service as Service$5 } from "./Storage.mjs";
|
|
13
|
+
import { Context, Data, DateTime, Duration, Effect, Layer, Option, Redacted, Schema } from "effect";
|
|
14
|
+
//#region src/Connect.ts
|
|
15
|
+
/**
|
|
16
|
+
* Connect a provider, attach domains to it, and keep the credential alive. Interactive methods
|
|
17
|
+
* (OAuth, integration) return a redirect and complete on callback; token methods connect in one
|
|
18
|
+
* call. Reuse of an existing connection for a new domain is `attach`.
|
|
19
|
+
*/
|
|
20
|
+
var Connect_exports = /* @__PURE__ */ __exportAll({
|
|
21
|
+
Discovery: () => Discovery,
|
|
22
|
+
Method: () => Method,
|
|
23
|
+
Policy: () => Policy,
|
|
24
|
+
Selection: () => Selection,
|
|
25
|
+
Service: () => Service,
|
|
26
|
+
Started: () => Started,
|
|
27
|
+
attach: () => attach,
|
|
28
|
+
complete: () => complete,
|
|
29
|
+
defaults: () => defaults,
|
|
30
|
+
detach: () => detach,
|
|
31
|
+
disconnect: () => disconnect,
|
|
32
|
+
discover: () => discover,
|
|
33
|
+
inspect: () => inspect,
|
|
34
|
+
layer: () => layer,
|
|
35
|
+
make: () => make,
|
|
36
|
+
refresh: () => refresh,
|
|
37
|
+
session: () => session,
|
|
38
|
+
start: () => start
|
|
39
|
+
});
|
|
40
|
+
const MethodEnum = Data.taggedEnum();
|
|
41
|
+
/** `token` accepts a lone string (`{ token }`) or a record; every value is wrapped in `Redacted`. */
|
|
42
|
+
const Method = {
|
|
43
|
+
...MethodEnum,
|
|
44
|
+
token: (values) => MethodEnum.Token({ values: typeof values === "string" ? { token: Redacted.make(values) } : Object.fromEntries(Object.entries(values).map(([name, value]) => [name, typeof value === "string" ? Redacted.make(value) : value])) }),
|
|
45
|
+
oauth: (options = {}) => MethodEnum.OAuth(options),
|
|
46
|
+
integration: (options = {}) => MethodEnum.Integration(options)
|
|
47
|
+
};
|
|
48
|
+
const Started = Data.taggedEnum();
|
|
49
|
+
const Selection = Data.taggedEnum();
|
|
50
|
+
const Discovery = Data.taggedEnum();
|
|
51
|
+
var Service = class extends Context.Service()("@domainkit/Connect") {};
|
|
52
|
+
const defaults = {
|
|
53
|
+
allowReuse: () => Effect.succeed(true),
|
|
54
|
+
refreshBeforeMs: 6e5,
|
|
55
|
+
continuationTtlMs: 9e5
|
|
56
|
+
};
|
|
57
|
+
var Policy = class extends Context.Reference("@domainkit/Connect/Policy", { defaultValue: () => defaults }) {};
|
|
58
|
+
const Payload = Schema.Struct({
|
|
59
|
+
method: Schema.Literals(["oauth", "integration"]),
|
|
60
|
+
codeVerifier: Schema.NullOr(Schema.String),
|
|
61
|
+
callbackUrl: Schema.String,
|
|
62
|
+
domain: Schema.NullOr(Schema.String)
|
|
63
|
+
});
|
|
64
|
+
const Target = Schema.Struct({
|
|
65
|
+
zone: Schema.String,
|
|
66
|
+
context: Schema.Unknown,
|
|
67
|
+
label: Schema.String
|
|
68
|
+
});
|
|
69
|
+
const invalid = (message, field) => fail(new InvalidInput({
|
|
70
|
+
message,
|
|
71
|
+
...field === void 0 ? {} : { field }
|
|
72
|
+
}));
|
|
73
|
+
const make = Effect.gen(function* () {
|
|
74
|
+
const storage = yield* Service$5;
|
|
75
|
+
const custody = yield* Service$1;
|
|
76
|
+
const providers = yield* Service$3;
|
|
77
|
+
const resolver = yield* Service$4;
|
|
78
|
+
const seal = (issued) => Effect.gen(function* () {
|
|
79
|
+
const ciphertext = yield* custody.seal(issued.secret);
|
|
80
|
+
return new Credential({
|
|
81
|
+
ciphertext,
|
|
82
|
+
expiresAt: issued.expiresAt,
|
|
83
|
+
rotatedAt: yield* DateTime.now
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
/** Plaintext credential with the context decoded from its stored envelope. */
|
|
87
|
+
const open = (authorization) => Effect.gen(function* () {
|
|
88
|
+
const definition = yield* providers.get(authorization.provider);
|
|
89
|
+
const row = yield* storage.authorizations.credential(authorization.id);
|
|
90
|
+
return {
|
|
91
|
+
secret: yield* custody.open(row.ciphertext),
|
|
92
|
+
context: yield* decodeContext(definition, authorization.context)
|
|
93
|
+
};
|
|
94
|
+
});
|
|
95
|
+
const refresherFor = (definition, method) => method === "oauth" ? definition.auth.oauth?.refresh : method === "integration" ? definition.auth.integration?.refresh : void 0;
|
|
96
|
+
const revokerFor = (definition, method) => method === "oauth" ? definition.auth.oauth?.revoke : method === "integration" ? definition.auth.integration?.revoke : void 0;
|
|
97
|
+
const revokeAt = (authorization) => Effect.gen(function* () {
|
|
98
|
+
const definition = yield* providers.get(authorization.provider);
|
|
99
|
+
const revoke = revokerFor(definition, authorization.method);
|
|
100
|
+
if (revoke === void 0) return;
|
|
101
|
+
yield* revoke(yield* open(authorization));
|
|
102
|
+
});
|
|
103
|
+
/** Finish revocations a crash interrupted; best effort, never blocks the caller. */
|
|
104
|
+
const recover = storage.authorizations.recoverRevocations(revokeAt).pipe(Effect.ignore);
|
|
105
|
+
const refresh = (connectionId) => Effect.gen(function* () {
|
|
106
|
+
const policy = yield* Policy;
|
|
107
|
+
const connection = yield* storage.connections.get(connectionId);
|
|
108
|
+
const authorization = yield* storage.authorizations.get(connection.authorizationId);
|
|
109
|
+
const definition = yield* providers.get(authorization.provider);
|
|
110
|
+
const refresher = refresherFor(definition, authorization.method);
|
|
111
|
+
const dueAt = (row, now) => row.expiresAt !== null && DateTime.toEpochMillis(row.expiresAt) - DateTime.toEpochMillis(now) <= policy.refreshBeforeMs;
|
|
112
|
+
const expired = (row, now) => row.expiresAt !== null && DateTime.toEpochMillis(row.expiresAt) <= DateTime.toEpochMillis(now);
|
|
113
|
+
const current = yield* storage.authorizations.credential(authorization.id);
|
|
114
|
+
const now = yield* DateTime.now;
|
|
115
|
+
if (!dueAt(current, now)) return "current";
|
|
116
|
+
if (refresher === void 0) return expired(current, now) ? "reconnect" : "current";
|
|
117
|
+
return yield* storage.withLock(`refresh:${authorization.id}`, Effect.gen(function* () {
|
|
118
|
+
const live = yield* storage.authorizations.get(authorization.id).pipe(Effect.map(Option.some), Effect.catchIf((error) => error.reason._tag === "NotFound", () => Effect.succeed(Option.none())));
|
|
119
|
+
if (Option.isNone(live) || live.value.revocation !== "active") return "reconnect";
|
|
120
|
+
const latest = yield* storage.authorizations.credential(authorization.id);
|
|
121
|
+
if (!dueAt(latest, yield* DateTime.now)) return "current";
|
|
122
|
+
const secret = yield* custody.open(latest.ciphertext);
|
|
123
|
+
const context = yield* decodeContext(definition, live.value.context);
|
|
124
|
+
const issued = yield* refresher({
|
|
125
|
+
secret,
|
|
126
|
+
context
|
|
127
|
+
});
|
|
128
|
+
yield* storage.authorizations.rotate(authorization.id, yield* seal(issued)).pipe(Effect.tapError((error) => error.reason._tag === "NotFound" ? (revokerFor(definition, authorization.method)?.(issued) ?? Effect.void).pipe(Effect.ignore) : Effect.void));
|
|
129
|
+
return "refreshed";
|
|
130
|
+
})).pipe(Effect.catchIf((error) => error.reason._tag === "Busy" && !expired(current, now), () => Effect.succeed("current")), Effect.catchIf((error) => error.reason._tag === "Unauthenticated", () => Effect.succeed("reconnect")));
|
|
131
|
+
});
|
|
132
|
+
const sessionFor = (connection) => Effect.gen(function* () {
|
|
133
|
+
const authorization = yield* storage.authorizations.get(connection.authorizationId);
|
|
134
|
+
const reconnect = fail(new Reconnect({
|
|
135
|
+
provider: authorization.provider,
|
|
136
|
+
connectionId: connection.id
|
|
137
|
+
}));
|
|
138
|
+
if (authorization.revocation !== "active") return yield* reconnect;
|
|
139
|
+
if ((yield* refresh(connection.id)) === "reconnect") return yield* reconnect;
|
|
140
|
+
return (yield* providers.get(authorization.provider)).session(yield* open(authorization));
|
|
141
|
+
});
|
|
142
|
+
const connectWith = (input) => Effect.gen(function* () {
|
|
143
|
+
const principal = yield* Service$2;
|
|
144
|
+
const session = input.definition.session(input.issued);
|
|
145
|
+
const held = yield* session.capabilities();
|
|
146
|
+
const missing = input.requiredCapabilities.filter((capability) => !held.includes(capability));
|
|
147
|
+
if (missing.length > 0) return yield* fail(new Forbidden({ message: `${input.definition.name} credential lacks ${missing.join(", ")}` }));
|
|
148
|
+
const authorization = new Authorization({
|
|
149
|
+
id: yield* fresh("auth"),
|
|
150
|
+
ownerId: principal.ownerId,
|
|
151
|
+
provider: input.definition.id,
|
|
152
|
+
method: input.method,
|
|
153
|
+
capabilities: held,
|
|
154
|
+
context: yield* encodeContext(input.definition, input.issued.context),
|
|
155
|
+
revocation: "active",
|
|
156
|
+
createdBy: principal.actorId,
|
|
157
|
+
createdAt: yield* DateTime.now
|
|
158
|
+
});
|
|
159
|
+
yield* storage.authorizations.upsert({
|
|
160
|
+
authorization,
|
|
161
|
+
credential: yield* seal(input.issued)
|
|
162
|
+
});
|
|
163
|
+
return {
|
|
164
|
+
connection: yield* storage.connections.create(authorization.id),
|
|
165
|
+
session
|
|
166
|
+
};
|
|
167
|
+
});
|
|
168
|
+
const attachWith = (input) => Effect.gen(function* () {
|
|
169
|
+
const domain = yield* decode(input.domain);
|
|
170
|
+
const existing = yield* storage.attachments.byDomain(domain);
|
|
171
|
+
if (Option.isSome(existing)) {
|
|
172
|
+
if (existing.value.connectionId === input.connection.id) return existing.value;
|
|
173
|
+
return yield* invalid(`${domain} is already attached to another connection`, "domain");
|
|
174
|
+
}
|
|
175
|
+
const resolution = input.target === void 0 ? yield* input.session.resolveTarget(domain) : Resolution.Resolved({ target: input.target });
|
|
176
|
+
switch (resolution._tag) {
|
|
177
|
+
case "SelectionRequired": return Selection.SelectionRequired({ candidates: resolution.candidates });
|
|
178
|
+
case "NotFound": return yield* fail(new NotFound({
|
|
179
|
+
entity: "zone",
|
|
180
|
+
id: domain
|
|
181
|
+
}));
|
|
182
|
+
case "Resolved": return yield* storage.attachments.create({
|
|
183
|
+
connectionId: input.connection.id,
|
|
184
|
+
domain,
|
|
185
|
+
zone: resolution.target.zone,
|
|
186
|
+
target: {
|
|
187
|
+
...resolution.target,
|
|
188
|
+
context: yield* encodeContext(input.definition, resolution.target.context)
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
const started = (connection, attached) => attached === null ? Started.Connected({
|
|
194
|
+
connection,
|
|
195
|
+
attachment: null
|
|
196
|
+
}) : attached instanceof Attachment ? Started.Connected({
|
|
197
|
+
connection,
|
|
198
|
+
attachment: attached
|
|
199
|
+
}) : Started.SelectionRequired({
|
|
200
|
+
connection,
|
|
201
|
+
candidates: attached.candidates
|
|
202
|
+
});
|
|
203
|
+
/** Authoritative nameservers for the closest zone that answers an NS query. */
|
|
204
|
+
const authoritative = (candidates) => Effect.gen(function* () {
|
|
205
|
+
for (const zone of candidates) {
|
|
206
|
+
const names = (yield* resolver.resolve(zone, "NS")).flatMap((outcome) => outcome._tag === "Answered" ? outcome.answer.records.flatMap((record) => record._tag === "NS" ? [record.nameserver] : []) : []);
|
|
207
|
+
if (names.length > 0) return [...new Set(names)].sort();
|
|
208
|
+
}
|
|
209
|
+
return [];
|
|
210
|
+
});
|
|
211
|
+
const discover = (input) => Effect.gen(function* () {
|
|
212
|
+
const domain = yield* decode(input);
|
|
213
|
+
const candidates$1 = candidates(domain);
|
|
214
|
+
const nameservers = yield* authoritative(candidates$1);
|
|
215
|
+
const reachable = [];
|
|
216
|
+
for (const connection of yield* storage.connections.list()) {
|
|
217
|
+
const targets = yield* sessionFor(connection).pipe(Effect.flatMap((session) => session.listTargets()), Effect.catch(() => Effect.succeed([])));
|
|
218
|
+
for (const target of targets) if (candidates$1.includes(target.zone)) reachable.push({
|
|
219
|
+
connectionId: connection.id,
|
|
220
|
+
target
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
const normalize = (name) => name.toLowerCase().replace(/\.$/, "");
|
|
224
|
+
for (const zone of candidates$1) {
|
|
225
|
+
const matches = reachable.filter(({ target }) => target.zone === zone).sort((left, right) => left.connectionId.localeCompare(right.connectionId));
|
|
226
|
+
if (matches.length === 0) continue;
|
|
227
|
+
const [only] = matches;
|
|
228
|
+
if (matches.length === 1 && only !== void 0) return Discovery.Resolved(only);
|
|
229
|
+
const decisive = matches.filter(({ target }) => {
|
|
230
|
+
const configured = new Set((target.nameservers ?? []).map(normalize));
|
|
231
|
+
return nameservers.length > 0 && nameservers.every((name) => configured.has(name));
|
|
232
|
+
});
|
|
233
|
+
const [winner] = decisive;
|
|
234
|
+
if (decisive.length === 1 && winner !== void 0) return Discovery.Resolved(winner);
|
|
235
|
+
return Discovery.SelectionRequired({ candidates: matches });
|
|
236
|
+
}
|
|
237
|
+
return Discovery.NotFound({ nameservers });
|
|
238
|
+
});
|
|
239
|
+
const inspect = (input) => Effect.gen(function* () {
|
|
240
|
+
const domain = yield* decode(input);
|
|
241
|
+
const policy = yield* Policy;
|
|
242
|
+
const attachment = Option.getOrNull(yield* storage.attachments.byDomain(domain));
|
|
243
|
+
const connection = attachment === null ? null : yield* storage.connections.get(attachment.connectionId);
|
|
244
|
+
const authorization = connection === null ? null : yield* storage.authorizations.get(connection.authorizationId);
|
|
245
|
+
const latest = attachment === null ? Option.none() : yield* storage.attempts.latest(attachment.id, "provisioning");
|
|
246
|
+
const reusable = [];
|
|
247
|
+
for (const candidate of yield* storage.connections.list()) {
|
|
248
|
+
if (candidate.id === connection?.id) continue;
|
|
249
|
+
if (!(yield* policy.allowReuse({
|
|
250
|
+
connection: candidate,
|
|
251
|
+
domain
|
|
252
|
+
}))) continue;
|
|
253
|
+
const candidateAuthorization = yield* storage.authorizations.get(candidate.authorizationId);
|
|
254
|
+
if (candidateAuthorization.revocation !== "active") continue;
|
|
255
|
+
reusable.push({
|
|
256
|
+
connection: candidate,
|
|
257
|
+
provider: candidateAuthorization.provider,
|
|
258
|
+
method: candidateAuthorization.method
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
return {
|
|
262
|
+
domain,
|
|
263
|
+
attachment,
|
|
264
|
+
connection,
|
|
265
|
+
authorization: authorization === null ? null : {
|
|
266
|
+
id: authorization.id,
|
|
267
|
+
provider: authorization.provider,
|
|
268
|
+
method: authorization.method,
|
|
269
|
+
capabilities: authorization.capabilities,
|
|
270
|
+
revocation: authorization.revocation
|
|
271
|
+
},
|
|
272
|
+
lastReceiptId: Option.isSome(latest) ? latest.value.receipt?.id ?? null : null,
|
|
273
|
+
reusable,
|
|
274
|
+
providers: providers.list().map((definition) => ({
|
|
275
|
+
id: definition.id,
|
|
276
|
+
name: definition.name,
|
|
277
|
+
methods: describeMethods(definition)
|
|
278
|
+
}))
|
|
279
|
+
};
|
|
280
|
+
});
|
|
281
|
+
const start = (input) => Effect.gen(function* () {
|
|
282
|
+
yield* recover;
|
|
283
|
+
const principal = yield* Service$2;
|
|
284
|
+
const policy = yield* Policy;
|
|
285
|
+
const definition = yield* providers.get(input.provider);
|
|
286
|
+
if (input.domain !== void 0) yield* decode(input.domain);
|
|
287
|
+
switch (input.method._tag) {
|
|
288
|
+
case "Token": {
|
|
289
|
+
const token = definition.auth.token;
|
|
290
|
+
if (token === void 0) return yield* invalid(`${definition.name} does not accept tokens`, "method");
|
|
291
|
+
const issued = yield* token.authenticate(input.method.values);
|
|
292
|
+
const { connection, session } = yield* connectWith({
|
|
293
|
+
definition,
|
|
294
|
+
method: "token",
|
|
295
|
+
issued,
|
|
296
|
+
requiredCapabilities: token.requiredCapabilities
|
|
297
|
+
});
|
|
298
|
+
const attached = input.domain === void 0 ? null : yield* attachWith({
|
|
299
|
+
definition,
|
|
300
|
+
connection,
|
|
301
|
+
session,
|
|
302
|
+
domain: input.domain
|
|
303
|
+
});
|
|
304
|
+
return started(connection, attached);
|
|
305
|
+
}
|
|
306
|
+
case "OAuth":
|
|
307
|
+
case "Integration": {
|
|
308
|
+
const interactive = input.method._tag === "OAuth" ? definition.auth.oauth : definition.auth.integration;
|
|
309
|
+
if (interactive === void 0) return yield* invalid(`${definition.name} does not offer ${input.method._tag.toLowerCase()} connections`, "method");
|
|
310
|
+
if (input.callbackUrl === void 0) return yield* invalid("Interactive connections need a callbackUrl", "callbackUrl");
|
|
311
|
+
const continuationId = yield* fresh("cont");
|
|
312
|
+
const pkce$1 = input.method._tag === "OAuth" ? yield* pkce() : null;
|
|
313
|
+
const redirect = input.method._tag === "OAuth" ? yield* interactive.start({
|
|
314
|
+
state: continuationId,
|
|
315
|
+
callbackUrl: input.callbackUrl,
|
|
316
|
+
codeChallenge: pkce$1?.codeChallenge ?? ""
|
|
317
|
+
}) : yield* interactive.start({
|
|
318
|
+
state: continuationId,
|
|
319
|
+
callbackUrl: input.callbackUrl
|
|
320
|
+
});
|
|
321
|
+
yield* storage.continuations.put(new Continuation({
|
|
322
|
+
id: continuationId,
|
|
323
|
+
ownerId: principal.ownerId,
|
|
324
|
+
actorId: principal.actorId,
|
|
325
|
+
provider: definition.id,
|
|
326
|
+
payload: Schema.encodeSync(Payload)({
|
|
327
|
+
method: input.method._tag === "OAuth" ? "oauth" : "integration",
|
|
328
|
+
codeVerifier: pkce$1?.codeVerifier ?? null,
|
|
329
|
+
callbackUrl: input.callbackUrl,
|
|
330
|
+
domain: input.domain ?? null
|
|
331
|
+
}),
|
|
332
|
+
returnTo: input.method.returnTo ?? null,
|
|
333
|
+
expiresAt: DateTime.addDuration(yield* DateTime.now, Duration.millis(policy.continuationTtlMs))
|
|
334
|
+
}));
|
|
335
|
+
return Started.Redirect({
|
|
336
|
+
authorizationUrl: redirect.authorizationUrl,
|
|
337
|
+
continuationId
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
});
|
|
342
|
+
const complete = (input) => Effect.gen(function* () {
|
|
343
|
+
const callback = yield* Effect.try({
|
|
344
|
+
try: () => new URL(input.callbackUrl),
|
|
345
|
+
catch: () => new DomainKitError({ reason: new InvalidInput({
|
|
346
|
+
message: "callbackUrl is not a URL",
|
|
347
|
+
field: "callbackUrl"
|
|
348
|
+
}) })
|
|
349
|
+
});
|
|
350
|
+
const params = Object.fromEntries(callback.searchParams);
|
|
351
|
+
const unauthenticated = (message) => fail(new Unauthenticated({ message }));
|
|
352
|
+
if (params.error !== void 0) return yield* unauthenticated(`Provider returned ${params.error}`);
|
|
353
|
+
if (params.state !== input.continuationId) return yield* unauthenticated("Callback state does not match the continuation");
|
|
354
|
+
const code = params.code;
|
|
355
|
+
if (code === void 0) return yield* unauthenticated("Callback has no code");
|
|
356
|
+
return yield* storage.withLock(`continuation:${input.continuationId}`, Effect.gen(function* () {
|
|
357
|
+
const continuation = yield* storage.continuations.get(input.continuationId);
|
|
358
|
+
const payload = yield* decode$1(Payload, continuation.payload, "continuation");
|
|
359
|
+
const definition = yield* providers.get(continuation.provider);
|
|
360
|
+
const issued = payload.method === "oauth" ? yield* Effect.gen(function* () {
|
|
361
|
+
const oauth = definition.auth.oauth;
|
|
362
|
+
if (oauth === void 0 || payload.codeVerifier === null) return yield* invalid(`${definition.name} does not offer oauth connections`, "method");
|
|
363
|
+
return yield* oauth.complete({
|
|
364
|
+
code,
|
|
365
|
+
callbackUrl: payload.callbackUrl,
|
|
366
|
+
codeVerifier: payload.codeVerifier,
|
|
367
|
+
params
|
|
368
|
+
});
|
|
369
|
+
}) : yield* Effect.gen(function* () {
|
|
370
|
+
const integration = definition.auth.integration;
|
|
371
|
+
if (integration === void 0) return yield* invalid(`${definition.name} does not offer integration connections`, "method");
|
|
372
|
+
return yield* integration.complete({
|
|
373
|
+
code,
|
|
374
|
+
callbackUrl: payload.callbackUrl,
|
|
375
|
+
params
|
|
376
|
+
});
|
|
377
|
+
});
|
|
378
|
+
const { connection, session } = yield* connectWith({
|
|
379
|
+
definition,
|
|
380
|
+
method: payload.method,
|
|
381
|
+
issued,
|
|
382
|
+
requiredCapabilities: []
|
|
383
|
+
});
|
|
384
|
+
const attached = payload.domain === null ? null : yield* attachWith({
|
|
385
|
+
definition,
|
|
386
|
+
connection,
|
|
387
|
+
session,
|
|
388
|
+
domain: payload.domain
|
|
389
|
+
});
|
|
390
|
+
yield* storage.continuations.consume(input.continuationId).pipe(Effect.ignore);
|
|
391
|
+
return started(connection, attached);
|
|
392
|
+
}));
|
|
393
|
+
});
|
|
394
|
+
const attach = (input) => Effect.gen(function* () {
|
|
395
|
+
const connection = yield* storage.connections.get(input.connectionId);
|
|
396
|
+
const authorization = yield* storage.authorizations.get(connection.authorizationId);
|
|
397
|
+
const definition = yield* providers.get(authorization.provider);
|
|
398
|
+
const session = yield* sessionFor(connection);
|
|
399
|
+
return yield* attachWith({
|
|
400
|
+
definition,
|
|
401
|
+
connection,
|
|
402
|
+
session,
|
|
403
|
+
domain: input.domain,
|
|
404
|
+
...input.target === void 0 ? {} : { target: input.target }
|
|
405
|
+
});
|
|
406
|
+
});
|
|
407
|
+
const disconnect = (connectionId) => Effect.gen(function* () {
|
|
408
|
+
yield* recover;
|
|
409
|
+
const connection = yield* storage.connections.get(connectionId);
|
|
410
|
+
const attachments = yield* storage.attachments.list(connectionId);
|
|
411
|
+
yield* Effect.forEach(attachments, (attachment) => storage.attachments.remove(attachment.id));
|
|
412
|
+
yield* storage.connections.remove(connectionId);
|
|
413
|
+
if ((yield* storage.connections.list()).some((candidate) => candidate.authorizationId === connection.authorizationId)) return;
|
|
414
|
+
const authorization = yield* storage.authorizations.get(connection.authorizationId);
|
|
415
|
+
yield* storage.authorizations.revoke(authorization.id, revokeAt(authorization));
|
|
416
|
+
});
|
|
417
|
+
const session = (attachmentId) => Effect.gen(function* () {
|
|
418
|
+
const attachment = yield* storage.attachments.get(attachmentId);
|
|
419
|
+
const connection = yield* storage.connections.get(attachment.connectionId);
|
|
420
|
+
const authorization = yield* storage.authorizations.get(connection.authorizationId);
|
|
421
|
+
const definition = yield* providers.get(authorization.provider);
|
|
422
|
+
const stored = yield* decode$1(Target, attachment.target, "target");
|
|
423
|
+
const target = {
|
|
424
|
+
...stored,
|
|
425
|
+
context: yield* decodeContext(definition, stored.context)
|
|
426
|
+
};
|
|
427
|
+
const built = yield* sessionFor(connection);
|
|
428
|
+
if (!(yield* built.listTargets()).some((candidate) => candidate.zone === target.zone)) return yield* fail(new NotFound({
|
|
429
|
+
entity: "zone",
|
|
430
|
+
id: target.zone
|
|
431
|
+
}));
|
|
432
|
+
return {
|
|
433
|
+
session: built,
|
|
434
|
+
target
|
|
435
|
+
};
|
|
436
|
+
});
|
|
437
|
+
return {
|
|
438
|
+
inspect,
|
|
439
|
+
discover,
|
|
440
|
+
start,
|
|
441
|
+
complete,
|
|
442
|
+
attach,
|
|
443
|
+
detach: (attachmentId) => storage.attachments.remove(attachmentId),
|
|
444
|
+
disconnect,
|
|
445
|
+
refresh,
|
|
446
|
+
session
|
|
447
|
+
};
|
|
448
|
+
});
|
|
449
|
+
const layer = Layer.effect(Service)(make);
|
|
450
|
+
const accessor = (pick) => (...args) => Effect.flatMap(Service, (service) => pick(service)(...args));
|
|
451
|
+
const inspect = accessor((service) => service.inspect);
|
|
452
|
+
const discover = accessor((service) => service.discover);
|
|
453
|
+
const start = accessor((service) => service.start);
|
|
454
|
+
const complete = accessor((service) => service.complete);
|
|
455
|
+
const attach = accessor((service) => service.attach);
|
|
456
|
+
const detach = accessor((service) => service.detach);
|
|
457
|
+
const disconnect = accessor((service) => service.disconnect);
|
|
458
|
+
const refresh = accessor((service) => service.refresh);
|
|
459
|
+
const session = accessor((service) => service.session);
|
|
460
|
+
//#endregion
|
|
461
|
+
export { Connect_exports, Discovery, Method, Policy, Selection, Service, Started, attach, complete, defaults, detach, disconnect, discover, inspect, layer, make, refresh, session, start };
|
|
462
|
+
|
|
463
|
+
//# sourceMappingURL=Connect.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Connect.mjs","names":["Errors.fail","Reason.InvalidInput","Storage.Service","Custody.Service","Providers.Service","Resolver.Service","Storage.Credential","Provider.decodeContext","Reason.Reconnect","Principal.Service","Reason.Forbidden","Storage.Authorization","Provider.encodeContext","DomainName.decode","Reason.NotFound","Storage.Attachment","candidates","DomainName.candidates","Provider.describeMethods","pkce","OAuth.pkce","Storage.Continuation","Errors.DomainKitError","Reason.Unauthenticated","Errors.decode"],"sources":["../src/Connect.ts"],"sourcesContent":["/**\n * Connect a provider, attach domains to it, and keep the credential alive. Interactive methods\n * (OAuth, integration) return a redirect and complete on callback; token methods connect in one\n * call. Reuse of an existing connection for a new domain is `attach`.\n */\nimport { Context, Data, DateTime, Duration, Effect, Layer, Option, Redacted, Schema } from \"effect\";\n\nimport * as Custody from \"./Custody.ts\";\nimport * as Errors from \"./internal/error.ts\";\nimport * as Reason from \"./Reason.ts\";\nimport * as DomainName from \"./DomainName.ts\";\nimport { fresh } from \"./internal/ids.ts\";\nimport * as OAuth from \"./internal/oauth.ts\";\nimport * as Principal from \"./Principal.ts\";\nimport * as Provider from \"./Provider.ts\";\nimport * as Providers from \"./Providers.ts\";\nimport * as Resolver from \"./Resolver.ts\";\nimport * as Storage from \"./Storage.ts\";\n\ntype Fx<A> = Effect.Effect<A, Errors.DomainKitError, Principal.Service>;\n\nexport type Method = Data.TaggedEnum<{\n /** Values keyed by the provider's declared `auth.token.fields`. */\n Token: { readonly values: Provider.TokenValues };\n OAuth: { readonly returnTo?: string };\n Integration: { readonly returnTo?: string };\n}>;\nconst MethodEnum = Data.taggedEnum<Method>();\n/** `token` accepts a lone string (`{ token }`) or a record; every value is wrapped in `Redacted`. */\nexport const Method = {\n ...MethodEnum,\n token: (\n values: string | Readonly<Record<string, Redacted.Redacted<string> | string | undefined>>,\n ): Method =>\n MethodEnum.Token({\n values:\n typeof values === \"string\"\n ? { token: Redacted.make(values) }\n : Object.fromEntries(\n Object.entries(values).map(([name, value]) => [\n name,\n typeof value === \"string\" ? Redacted.make(value) : value,\n ]),\n ),\n }),\n oauth: (options: { readonly returnTo?: string } = {}): Method => MethodEnum.OAuth(options),\n integration: (options: { readonly returnTo?: string } = {}): Method =>\n MethodEnum.Integration(options),\n} as const;\n\n/** Everything the UI needs about a domain's provider state. */\nexport interface Snapshot {\n readonly domain: string;\n readonly attachment: Storage.Attachment | null;\n readonly connection: Storage.Connection | null;\n readonly authorization: Pick<\n Storage.Authorization,\n \"id\" | \"provider\" | \"method\" | \"capabilities\" | \"revocation\"\n > | null;\n readonly lastReceiptId: string | null;\n /** Other connections of this owner that could serve this domain. */\n readonly reusable: ReadonlyArray<{\n readonly connection: Storage.Connection;\n readonly provider: string;\n readonly method: Storage.AuthMethod;\n }>;\n readonly providers: ReadonlyArray<{\n readonly id: string;\n readonly name: string;\n readonly methods: ReadonlyArray<Provider.MethodDescriptor>;\n }>;\n}\n\nexport type Started = Data.TaggedEnum<{\n Connected: {\n readonly connection: Storage.Connection;\n readonly attachment: Storage.Attachment | null;\n };\n Redirect: { readonly authorizationUrl: string; readonly continuationId: string };\n SelectionRequired: {\n readonly connection: Storage.Connection;\n readonly candidates: ReadonlyArray<Provider.Target>;\n };\n}>;\nexport const Started = Data.taggedEnum<Started>();\n\n/** `attach` either attached the domain or needs the caller to pick a zone. */\nexport type Selection = Data.TaggedEnum<{\n SelectionRequired: { readonly candidates: ReadonlyArray<Provider.Target> };\n}>;\nexport const Selection = Data.taggedEnum<Selection>();\n\nexport type Attached = Storage.Attachment | Selection;\n\nexport type Refreshed = \"current\" | \"refreshed\" | \"reconnect\";\n\n/** Which of the principal's connections serves a domain, from nameserver evidence and zone lists. */\nexport type Discovery = Data.TaggedEnum<{\n Resolved: { readonly connectionId: string; readonly target: Provider.Target };\n SelectionRequired: {\n readonly candidates: ReadonlyArray<{\n readonly connectionId: string;\n readonly target: Provider.Target;\n }>;\n };\n NotFound: { readonly nameservers: ReadonlyArray<string> };\n}>;\nexport const Discovery = Data.taggedEnum<Discovery>();\n\nexport interface Interface {\n readonly inspect: (domain: string) => Fx<Snapshot>;\n /**\n * Resolve the domain's authoritative nameservers, then match them against the zones each of\n * the principal's connections can reach: the closest zone wins, a decisive nameserver match\n * breaks ties, and anything else needs a selection.\n */\n readonly discover: (domain: string) => Fx<Discovery>;\n /** Connect a provider and, when `domain` is given, attach it. Token methods finish here. */\n readonly start: (input: {\n readonly provider: string;\n readonly method: Method;\n readonly domain?: string;\n readonly callbackUrl?: string;\n }) => Fx<Started>;\n /** Finish an interactive connection from the provider callback (the full callback URL). */\n readonly complete: (input: {\n readonly continuationId: string;\n readonly callbackUrl: string;\n }) => Fx<Started>;\n readonly attach: (input: {\n readonly connectionId: string;\n readonly domain: string;\n readonly target?: Provider.Target;\n }) => Fx<Attached>;\n /** Records stay in DNS; DomainKit forgets the domain. Use `Cleanup` first to remove records. */\n readonly detach: (attachmentId: string) => Fx<void>;\n /** Detach every domain and revoke the credential at the provider when the method supports it. */\n readonly disconnect: (connectionId: string) => Fx<void>;\n /** Refresh a near-expiry credential, single-flighted. Called automatically before provider use. */\n readonly refresh: (connectionId: string) => Fx<Refreshed>;\n /** Live provider session for an attachment; used by Provision, Cleanup, Verify. */\n readonly session: (\n attachmentId: string,\n ) => Fx<{ readonly session: Provider.Session; readonly target: Provider.Target }>;\n}\n\nexport class Service extends Context.Service<Service, Interface>()(\"@domainkit/Connect\") {}\n\n/** Which owner-scoped reuse is allowed. Default: any connection of the same owner may serve any of its domains. */\nexport interface PolicyShape {\n readonly allowReuse: (input: {\n readonly connection: Storage.Connection;\n readonly domain: string;\n }) => Effect.Effect<boolean, never, Principal.Service>;\n /** Refresh this long before expiry. Default 10 minutes. */\n readonly refreshBeforeMs: number;\n /** Continuation lifetime. Default 15 minutes. */\n readonly continuationTtlMs: number;\n}\nexport const defaults: PolicyShape = {\n allowReuse: () => Effect.succeed(true),\n refreshBeforeMs: 10 * 60_000,\n continuationTtlMs: 15 * 60_000,\n};\nexport class Policy extends Context.Reference<PolicyShape>(\"@domainkit/Connect/Policy\", {\n defaultValue: () => defaults,\n}) {}\n\n// ---------------------------------------------------------------------------------------------\n// Implementation\n// ---------------------------------------------------------------------------------------------\n\nconst Payload = Schema.Struct({\n method: Schema.Literals([\"oauth\", \"integration\"]),\n codeVerifier: Schema.NullOr(Schema.String),\n callbackUrl: Schema.String,\n domain: Schema.NullOr(Schema.String),\n});\n\nconst Target = Schema.Struct({\n zone: Schema.String,\n context: Schema.Unknown,\n label: Schema.String,\n});\n\nconst invalid = (message: string, field?: string) =>\n Errors.fail(new Reason.InvalidInput({ message, ...(field === undefined ? {} : { field }) }));\n\nexport const make: Effect.Effect<\n Interface,\n never,\n Storage.Service | Custody.Service | Providers.Service | Resolver.Service\n> = Effect.gen(function* () {\n const storage = yield* Storage.Service;\n const custody = yield* Custody.Service;\n const providers = yield* Providers.Service;\n const resolver = yield* Resolver.Service;\n\n const seal = (issued: Provider.IssuedCredential) =>\n Effect.gen(function* () {\n const ciphertext = yield* custody.seal(issued.secret);\n return new Storage.Credential({\n ciphertext,\n expiresAt: issued.expiresAt,\n rotatedAt: yield* DateTime.now,\n });\n });\n\n /** Plaintext credential with the context decoded from its stored envelope. */\n const open = (authorization: Storage.Authorization): Fx<Provider.Credential> =>\n Effect.gen(function* () {\n const definition = yield* providers.get(authorization.provider);\n const row = yield* storage.authorizations.credential(authorization.id);\n const secret = yield* custody.open(row.ciphertext);\n const context = yield* Provider.decodeContext(definition, authorization.context);\n return { secret, context };\n });\n\n const refresherFor = (definition: Provider.Definition, method: Storage.AuthMethod) =>\n method === \"oauth\"\n ? definition.auth.oauth?.refresh\n : method === \"integration\"\n ? definition.auth.integration?.refresh\n : undefined;\n\n const revokerFor = (definition: Provider.Definition, method: Storage.AuthMethod) =>\n method === \"oauth\"\n ? definition.auth.oauth?.revoke\n : method === \"integration\"\n ? definition.auth.integration?.revoke\n : undefined;\n\n const revokeAt = (authorization: Storage.Authorization): Fx<void> =>\n Effect.gen(function* () {\n const definition = yield* providers.get(authorization.provider);\n const revoke = revokerFor(definition, authorization.method);\n if (revoke === undefined) return;\n const credential = yield* open(authorization);\n yield* revoke(credential);\n });\n\n /** Finish revocations a crash interrupted; best effort, never blocks the caller. */\n const recover = storage.authorizations.recoverRevocations(revokeAt).pipe(Effect.ignore);\n\n const refresh: Interface[\"refresh\"] = (connectionId) =>\n Effect.gen(function* () {\n const policy = yield* Policy;\n const connection = yield* storage.connections.get(connectionId);\n const authorization = yield* storage.authorizations.get(connection.authorizationId);\n const definition = yield* providers.get(authorization.provider);\n const refresher = refresherFor(definition, authorization.method);\n const dueAt = (row: Storage.Credential, now: DateTime.Utc) =>\n row.expiresAt !== null &&\n DateTime.toEpochMillis(row.expiresAt) - DateTime.toEpochMillis(now) <=\n policy.refreshBeforeMs;\n const expired = (row: Storage.Credential, now: DateTime.Utc) =>\n row.expiresAt !== null &&\n DateTime.toEpochMillis(row.expiresAt) <= DateTime.toEpochMillis(now);\n const current = yield* storage.authorizations.credential(authorization.id);\n const now = yield* DateTime.now;\n if (!dueAt(current, now)) return \"current\" as const;\n if (refresher === undefined)\n return expired(current, now) ? (\"reconnect\" as const) : (\"current\" as const);\n return yield* storage\n .withLock(\n `refresh:${authorization.id}`,\n Effect.gen(function* () {\n // Disconnect's two-phase revocation does not take this lock: re-read the row here\n // so a revocation that started meanwhile skips the refresh instead of racing it.\n const live = yield* storage.authorizations.get(authorization.id).pipe(\n Effect.map(Option.some),\n Effect.catchIf(\n (error) => error.reason._tag === \"NotFound\",\n () => Effect.succeed(Option.none<Storage.Authorization>()),\n ),\n );\n if (Option.isNone(live) || live.value.revocation !== \"active\") {\n return \"reconnect\" as const;\n }\n const latest = yield* storage.authorizations.credential(authorization.id);\n if (!dueAt(latest, yield* DateTime.now)) return \"current\" as const;\n const secret = yield* custody.open(latest.ciphertext);\n const context = yield* Provider.decodeContext(definition, live.value.context);\n const issued = yield* refresher({ secret, context });\n // A revocation that completed while the provider was issuing leaves no row to\n // rotate; revoke the fresh credential so it does not stay live without a home.\n yield* storage.authorizations\n .rotate(authorization.id, yield* seal(issued))\n .pipe(\n Effect.tapError((error) =>\n error.reason._tag === \"NotFound\"\n ? (revokerFor(definition, authorization.method)?.(issued) ?? Effect.void).pipe(\n Effect.ignore,\n )\n : Effect.void,\n ),\n );\n return \"refreshed\" as const;\n }),\n )\n .pipe(\n Effect.catchIf(\n (error) => error.reason._tag === \"Busy\" && !expired(current, now),\n () => Effect.succeed(\"current\" as const),\n ),\n Effect.catchIf(\n (error) => error.reason._tag === \"Unauthenticated\",\n () => Effect.succeed(\"reconnect\" as const),\n ),\n );\n });\n\n const sessionFor = (connection: Storage.Connection): Fx<Provider.Session> =>\n Effect.gen(function* () {\n const authorization = yield* storage.authorizations.get(connection.authorizationId);\n const reconnect = Errors.fail(\n new Reason.Reconnect({\n provider: authorization.provider,\n connectionId: connection.id,\n }),\n );\n if (authorization.revocation !== \"active\") return yield* reconnect;\n if ((yield* refresh(connection.id)) === \"reconnect\") return yield* reconnect;\n const definition = yield* providers.get(authorization.provider);\n return definition.session(yield* open(authorization));\n });\n\n const connectWith = (input: {\n readonly definition: Provider.Definition;\n readonly method: Storage.AuthMethod;\n readonly issued: Provider.IssuedCredential;\n readonly requiredCapabilities: ReadonlyArray<Storage.Capability>;\n }): Fx<{ readonly connection: Storage.Connection; readonly session: Provider.Session }> =>\n Effect.gen(function* () {\n const principal = yield* Principal.Service;\n const session = input.definition.session(input.issued);\n const held = yield* session.capabilities();\n const missing = input.requiredCapabilities.filter((capability) => !held.includes(capability));\n if (missing.length > 0) {\n return yield* Errors.fail(\n new Reason.Forbidden({\n message: `${input.definition.name} credential lacks ${missing.join(\", \")}`,\n }),\n );\n }\n const authorization = new Storage.Authorization({\n id: yield* fresh(\"auth\"),\n ownerId: principal.ownerId,\n provider: input.definition.id,\n method: input.method,\n capabilities: held,\n context: yield* Provider.encodeContext(input.definition, input.issued.context),\n revocation: \"active\",\n createdBy: principal.actorId,\n createdAt: yield* DateTime.now,\n });\n yield* storage.authorizations.upsert({\n authorization,\n credential: yield* seal(input.issued),\n });\n const connection = yield* storage.connections.create(authorization.id);\n return { connection, session };\n });\n\n const attachWith = (input: {\n readonly definition: Provider.Definition;\n readonly connection: Storage.Connection;\n readonly session: Provider.Session;\n readonly domain: string;\n readonly target?: Provider.Target;\n }): Fx<Attached> =>\n Effect.gen(function* () {\n const domain = yield* DomainName.decode(input.domain);\n const existing = yield* storage.attachments.byDomain(domain);\n if (Option.isSome(existing)) {\n if (existing.value.connectionId === input.connection.id) return existing.value;\n return yield* invalid(`${domain} is already attached to another connection`, \"domain\");\n }\n const resolution: Provider.Resolution =\n input.target === undefined\n ? yield* input.session.resolveTarget(domain)\n : Provider.Resolution.Resolved({ target: input.target });\n switch (resolution._tag) {\n case \"SelectionRequired\":\n return Selection.SelectionRequired({ candidates: resolution.candidates });\n case \"NotFound\":\n return yield* Errors.fail(new Reason.NotFound({ entity: \"zone\", id: domain }));\n case \"Resolved\":\n return yield* storage.attachments.create({\n connectionId: input.connection.id,\n domain,\n zone: resolution.target.zone,\n target: {\n ...resolution.target,\n context: yield* Provider.encodeContext(input.definition, resolution.target.context),\n },\n });\n }\n });\n\n const started = (connection: Storage.Connection, attached: Attached | null): Started =>\n attached === null\n ? Started.Connected({ connection, attachment: null })\n : attached instanceof Storage.Attachment\n ? Started.Connected({ connection, attachment: attached })\n : Started.SelectionRequired({ connection, candidates: attached.candidates });\n\n /** Authoritative nameservers for the closest zone that answers an NS query. */\n const authoritative = (candidates: ReadonlyArray<DomainName.Model>) =>\n Effect.gen(function* () {\n for (const zone of candidates) {\n const outcomes = yield* resolver.resolve(zone, \"NS\");\n const names = outcomes.flatMap((outcome) =>\n outcome._tag === \"Answered\"\n ? outcome.answer.records.flatMap((record) =>\n record._tag === \"NS\" ? [record.nameserver] : [],\n )\n : [],\n );\n if (names.length > 0) return [...new Set(names)].sort();\n }\n return [] as ReadonlyArray<string>;\n });\n\n const discover: Interface[\"discover\"] = (input) =>\n Effect.gen(function* () {\n const domain = yield* DomainName.decode(input);\n const candidates = DomainName.candidates(domain);\n const nameservers = yield* authoritative(candidates);\n const reachable: Array<{\n readonly connectionId: string;\n readonly target: Provider.Target;\n }> = [];\n for (const connection of yield* storage.connections.list()) {\n const targets = yield* sessionFor(connection).pipe(\n Effect.flatMap((session) => session.listTargets()),\n Effect.catch(() => Effect.succeed([] as ReadonlyArray<Provider.Target>)),\n );\n for (const target of targets) {\n if (candidates.includes(target.zone as DomainName.Model)) {\n reachable.push({ connectionId: connection.id, target });\n }\n }\n }\n const normalize = (name: string) => name.toLowerCase().replace(/\\.$/, \"\");\n for (const zone of candidates) {\n const matches = reachable\n .filter(({ target }) => target.zone === zone)\n .sort((left, right) => left.connectionId.localeCompare(right.connectionId));\n if (matches.length === 0) continue;\n const [only] = matches;\n if (matches.length === 1 && only !== undefined) return Discovery.Resolved(only);\n const decisive = matches.filter(({ target }) => {\n const configured = new Set((target.nameservers ?? []).map(normalize));\n return nameservers.length > 0 && nameservers.every((name) => configured.has(name));\n });\n const [winner] = decisive;\n if (decisive.length === 1 && winner !== undefined) return Discovery.Resolved(winner);\n return Discovery.SelectionRequired({ candidates: matches });\n }\n return Discovery.NotFound({ nameservers });\n });\n\n const inspect: Interface[\"inspect\"] = (input) =>\n Effect.gen(function* () {\n const domain = yield* DomainName.decode(input);\n const policy = yield* Policy;\n const attachment = Option.getOrNull(yield* storage.attachments.byDomain(domain));\n const connection =\n attachment === null ? null : yield* storage.connections.get(attachment.connectionId);\n const authorization =\n connection === null ? null : yield* storage.authorizations.get(connection.authorizationId);\n const latest =\n attachment === null\n ? Option.none()\n : yield* storage.attempts.latest(attachment.id, \"provisioning\");\n const reusable: Array<Snapshot[\"reusable\"][number]> = [];\n for (const candidate of yield* storage.connections.list()) {\n if (candidate.id === connection?.id) continue;\n if (!(yield* policy.allowReuse({ connection: candidate, domain }))) continue;\n const candidateAuthorization = yield* storage.authorizations.get(candidate.authorizationId);\n if (candidateAuthorization.revocation !== \"active\") continue;\n reusable.push({\n connection: candidate,\n provider: candidateAuthorization.provider,\n method: candidateAuthorization.method,\n });\n }\n return {\n domain,\n attachment,\n connection,\n authorization:\n authorization === null\n ? null\n : {\n id: authorization.id,\n provider: authorization.provider,\n method: authorization.method,\n capabilities: authorization.capabilities,\n revocation: authorization.revocation,\n },\n lastReceiptId: Option.isSome(latest) ? (latest.value.receipt?.id ?? null) : null,\n reusable,\n providers: providers.list().map((definition) => ({\n id: definition.id,\n name: definition.name,\n methods: Provider.describeMethods(definition),\n })),\n };\n });\n\n const start: Interface[\"start\"] = (input) =>\n Effect.gen(function* () {\n yield* recover;\n const principal = yield* Principal.Service;\n const policy = yield* Policy;\n const definition = yield* providers.get(input.provider);\n if (input.domain !== undefined) yield* DomainName.decode(input.domain);\n switch (input.method._tag) {\n case \"Token\": {\n const token = definition.auth.token;\n if (token === undefined) {\n return yield* invalid(`${definition.name} does not accept tokens`, \"method\");\n }\n const issued = yield* token.authenticate(input.method.values);\n const { connection, session } = yield* connectWith({\n definition,\n method: \"token\",\n issued,\n requiredCapabilities: token.requiredCapabilities,\n });\n const attached =\n input.domain === undefined\n ? null\n : yield* attachWith({ definition, connection, session, domain: input.domain });\n return started(connection, attached);\n }\n case \"OAuth\":\n case \"Integration\": {\n const interactive =\n input.method._tag === \"OAuth\" ? definition.auth.oauth : definition.auth.integration;\n if (interactive === undefined) {\n return yield* invalid(\n `${definition.name} does not offer ${input.method._tag.toLowerCase()} connections`,\n \"method\",\n );\n }\n if (input.callbackUrl === undefined) {\n return yield* invalid(\"Interactive connections need a callbackUrl\", \"callbackUrl\");\n }\n const continuationId = yield* fresh(\"cont\");\n const pkce = input.method._tag === \"OAuth\" ? yield* OAuth.pkce() : null;\n const redirect =\n input.method._tag === \"OAuth\"\n ? yield* (interactive as Provider.OAuthAuth).start({\n state: continuationId,\n callbackUrl: input.callbackUrl,\n codeChallenge: pkce?.codeChallenge ?? \"\",\n })\n : yield* (interactive as Provider.IntegrationAuth).start({\n state: continuationId,\n callbackUrl: input.callbackUrl,\n });\n yield* storage.continuations.put(\n new Storage.Continuation({\n id: continuationId,\n ownerId: principal.ownerId,\n actorId: principal.actorId,\n provider: definition.id,\n payload: Schema.encodeSync(Payload)({\n method: input.method._tag === \"OAuth\" ? \"oauth\" : \"integration\",\n codeVerifier: pkce?.codeVerifier ?? null,\n callbackUrl: input.callbackUrl,\n domain: input.domain ?? null,\n }),\n returnTo: input.method.returnTo ?? null,\n expiresAt: DateTime.addDuration(\n yield* DateTime.now,\n Duration.millis(policy.continuationTtlMs),\n ),\n }),\n );\n return Started.Redirect({\n authorizationUrl: redirect.authorizationUrl,\n continuationId,\n });\n }\n }\n });\n\n const complete: Interface[\"complete\"] = (input) =>\n Effect.gen(function* () {\n // The continuation is spent only after the credential and connection are persisted, so a\n // failure before that (bad callback, provider outage, storage outage) leaves the flow\n // retryable. Concurrent callbacks for the same continuation serialize on a lock; the\n // loser sees Busy or NotFound. A provider code is single-use, so a retry after the\n // provider already redeemed it fails Unauthenticated and the customer starts over.\n const callback = yield* Effect.try({\n try: () => new URL(input.callbackUrl),\n catch: () =>\n new Errors.DomainKitError({\n reason: new Reason.InvalidInput({\n message: \"callbackUrl is not a URL\",\n field: \"callbackUrl\",\n }),\n }),\n });\n const params = Object.fromEntries(callback.searchParams);\n const unauthenticated = (message: string) =>\n Errors.fail(new Reason.Unauthenticated({ message }));\n if (params.error !== undefined) {\n return yield* unauthenticated(`Provider returned ${params.error}`);\n }\n if (params.state !== input.continuationId) {\n return yield* unauthenticated(\"Callback state does not match the continuation\");\n }\n const code = params.code;\n if (code === undefined) return yield* unauthenticated(\"Callback has no code\");\n return yield* storage.withLock(\n `continuation:${input.continuationId}`,\n Effect.gen(function* () {\n const continuation = yield* storage.continuations.get(input.continuationId);\n const payload = yield* Errors.decode(Payload, continuation.payload, \"continuation\");\n const definition = yield* providers.get(continuation.provider);\n const issued =\n payload.method === \"oauth\"\n ? yield* Effect.gen(function* () {\n const oauth = definition.auth.oauth;\n if (oauth === undefined || payload.codeVerifier === null) {\n return yield* invalid(\n `${definition.name} does not offer oauth connections`,\n \"method\",\n );\n }\n return yield* oauth.complete({\n code,\n callbackUrl: payload.callbackUrl,\n codeVerifier: payload.codeVerifier,\n params,\n });\n })\n : yield* Effect.gen(function* () {\n const integration = definition.auth.integration;\n if (integration === undefined) {\n return yield* invalid(\n `${definition.name} does not offer integration connections`,\n \"method\",\n );\n }\n return yield* integration.complete({\n code,\n callbackUrl: payload.callbackUrl,\n params,\n });\n });\n const { connection, session } = yield* connectWith({\n definition,\n method: payload.method,\n issued,\n requiredCapabilities: [],\n });\n const attached =\n payload.domain === null\n ? null\n : yield* attachWith({ definition, connection, session, domain: payload.domain });\n // The connection is durable at this point. Whatever happens to the continuation\n // now (expired, already spent, or a storage failure while spending it) no longer\n // changes the outcome: the row expires on its own and a replayed callback fails at\n // the provider, so the persisted result is returned.\n yield* storage.continuations.consume(input.continuationId).pipe(Effect.ignore);\n return started(connection, attached);\n }),\n );\n });\n\n const attach: Interface[\"attach\"] = (input) =>\n Effect.gen(function* () {\n const connection = yield* storage.connections.get(input.connectionId);\n const authorization = yield* storage.authorizations.get(connection.authorizationId);\n const definition = yield* providers.get(authorization.provider);\n const session = yield* sessionFor(connection);\n return yield* attachWith({\n definition,\n connection,\n session,\n domain: input.domain,\n ...(input.target === undefined ? {} : { target: input.target }),\n });\n });\n\n const disconnect: Interface[\"disconnect\"] = (connectionId) =>\n Effect.gen(function* () {\n yield* recover;\n const connection = yield* storage.connections.get(connectionId);\n const attachments = yield* storage.attachments.list(connectionId);\n yield* Effect.forEach(attachments, (attachment) => storage.attachments.remove(attachment.id));\n yield* storage.connections.remove(connectionId);\n const remaining = (yield* storage.connections.list()).some(\n (candidate) => candidate.authorizationId === connection.authorizationId,\n );\n if (remaining) return;\n const authorization = yield* storage.authorizations.get(connection.authorizationId);\n yield* storage.authorizations.revoke(authorization.id, revokeAt(authorization));\n });\n\n const session: Interface[\"session\"] = (attachmentId) =>\n Effect.gen(function* () {\n const attachment = yield* storage.attachments.get(attachmentId);\n const connection = yield* storage.connections.get(attachment.connectionId);\n const authorization = yield* storage.authorizations.get(connection.authorizationId);\n const definition = yield* providers.get(authorization.provider);\n const stored = yield* Errors.decode(Target, attachment.target, \"target\");\n const target: Provider.Target = {\n ...stored,\n context: yield* Provider.decodeContext(definition, stored.context),\n };\n const built = yield* sessionFor(connection);\n // The zone must still be reachable by this credential: it may have moved account, been\n // deleted, or become a kind the provider cannot host records in.\n const reachable = yield* built.listTargets();\n if (!reachable.some((candidate) => candidate.zone === target.zone)) {\n return yield* Errors.fail(new Reason.NotFound({ entity: \"zone\", id: target.zone }));\n }\n return { session: built, target };\n });\n\n return {\n inspect,\n discover,\n start,\n complete,\n attach,\n detach: (attachmentId) => storage.attachments.remove(attachmentId),\n disconnect,\n refresh,\n session,\n };\n});\n\nexport const layer: Layer.Layer<\n Service,\n never,\n Storage.Service | Custody.Service | Providers.Service | Resolver.Service\n> = Layer.effect(Service)(make);\n\nconst accessor =\n <Args extends ReadonlyArray<unknown>, A>(\n pick: (service: Interface) => (...args: Args) => Fx<A>,\n ): ((...args: Args) => Effect.Effect<A, Errors.DomainKitError, Principal.Service | Service>) =>\n (...args) =>\n Effect.flatMap(Service, (service) => pick(service)(...args));\n\nexport const inspect = accessor((service) => service.inspect);\nexport const discover = accessor((service) => service.discover);\nexport const start = accessor((service) => service.start);\nexport const complete = accessor((service) => service.complete);\nexport const attach = accessor((service) => service.attach);\nexport const detach = accessor((service) => service.detach);\nexport const disconnect = accessor((service) => service.disconnect);\nexport const refresh = accessor((service) => service.refresh);\nexport const session = accessor((service) => service.session);\n"],"mappings":"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"}
|