domainkit 0.12.1 → 0.12.2

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.
@@ -51,8 +51,9 @@ const server = endpointsOf({
51
51
  server: defaultIssuer
52
52
  });
53
53
  const defaultScopes = [
54
- "zone:read",
55
- "dns_records:edit",
54
+ "zone.read",
55
+ "dns.read",
56
+ "dns.write",
56
57
  "offline_access"
57
58
  ];
58
59
  const capabilities = ["dns:read", "dns:write"];
@@ -96,8 +97,14 @@ const provider = (options = {}) => {
96
97
  nameservers: zone.name_servers ?? []
97
98
  });
98
99
  const oauthClient = (oauth) => Effect.gen(function* () {
100
+ const clientId = yield* resolve(oauth.clientId, "oauth.clientId");
101
+ if (oauth.clientAuth === "none") return {
102
+ clientId,
103
+ clientSecret: null,
104
+ clientAuth: "none"
105
+ };
99
106
  return {
100
- clientId: yield* resolve(oauth.clientId, "oauth.clientId"),
107
+ clientId,
101
108
  clientSecret: yield* resolve(oauth.clientSecret, "oauth.clientSecret"),
102
109
  clientAuth: oauth.clientAuth ?? "client_secret_basic"
103
110
  };
@@ -1 +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 * Origin for consent, token exchange, and revocation, which Cloudflare serves at\n * `/oauth2/auth`, `/oauth2/token`, and `/oauth2/revoke` beneath it. Default\n * `https://dash.cloudflare.com`; a stage points it at an emulator that mounts the same three\n * paths. It stays separate from `baseUrl` because in production these are different hosts:\n * `dash.cloudflare.com` for OAuth, `api.cloudflare.com/client/v4` for the REST API.\n */\n readonly issuer?: string;\n /**\n * Where the server reaches the OAuth endpoints when that differs from where the browser does,\n * for example an API in a container reaching an emulator through `host.docker.internal`.\n * Defaults to `issuer`.\n */\n readonly serverOrigin?: string;\n /**\n * Permit `http:` for these endpoints when they are not loopback, such as an emulator reached\n * through `host.docker.internal`. Development only: the client secret, the code, and the tokens\n * cross the network in the clear, and a name resolves wherever the machine has been told to.\n * Loopback needs no flag.\n */\n readonly allowPlaintext?: boolean;\n };\n readonly fetch?: Fetch;\n readonly baseUrl?: string;\n}\n\nconst defaultIssuer = \"https://dash.cloudflare.com\";\n\n/**\n * The three OAuth endpoints Cloudflare serves under one origin. Consent is the browser's, exchange\n * and revocation are the server's, so a stage whose server reaches the same emulator by another\n * name gives two origins and keeps the paths.\n */\nconst endpointsOf = (origins: {\n readonly browser: string;\n readonly server: string;\n}): OAuth.Server => {\n const browser = origins.browser.replace(/\\/$/, \"\");\n const server = origins.server.replace(/\\/$/, \"\");\n return {\n authorization_endpoint: `${browser}/oauth2/auth`,\n issuer: browser,\n revocation_endpoint: `${server}/oauth2/revoke`,\n token_endpoint: `${server}/oauth2/token`,\n };\n};\n\nexport const server: OAuth.Server = endpointsOf({\n browser: defaultIssuer,\n server: defaultIssuer,\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 const browser = settings.issuer ?? defaultIssuer;\n const endpoints = endpointsOf({ browser, server: settings.serverOrigin ?? browser });\n const insecure =\n settings.allowPlaintext === undefined ? {} : { allowPlaintext: settings.allowPlaintext };\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: endpoints,\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: endpoints,\n client: oauthClientValue,\n code: input.code,\n state: input.params.state ?? \"\",\n callbackUrl: input.callbackUrl,\n codeVerifier: input.codeVerifier,\n fetch,\n ...insecure,\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: endpoints,\n client: yield* oauthClient(settings),\n refreshToken,\n fetch,\n ...insecure,\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: endpoints,\n client: yield* oauthClient(settings),\n token: accessToken,\n fetch,\n ...insecure,\n });\n }),\n };\n }\n\n return Provider.make<AccountContext>({\n id: \"cloudflare\",\n name: \"Cloudflare\",\n nameservers: [\"ns.cloudflare.com\"],\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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"}
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?: OAuthOptions;\n readonly fetch?: Fetch;\n readonly baseUrl?: string;\n}\n\ninterface OAuthOptionsBase {\n readonly clientId: string | Config.Config<string>;\n /** Scope ids assigned to the OAuth client. Default: `zone.read`, `dns.read`, `dns.write`, `offline_access`. */\n readonly scopes?: ReadonlyArray<string>;\n /**\n * Origin for consent, token exchange, and revocation, which Cloudflare serves at\n * `/oauth2/auth`, `/oauth2/token`, and `/oauth2/revoke` beneath it. Default\n * `https://dash.cloudflare.com`; a stage points it at an emulator that mounts the same three\n * paths. It stays separate from `baseUrl` because in production these are different hosts:\n * `dash.cloudflare.com` for OAuth, `api.cloudflare.com/client/v4` for the REST API.\n */\n readonly issuer?: string;\n /**\n * Where the server reaches the OAuth endpoints when that differs from where the browser does,\n * for example an API in a container reaching an emulator through `host.docker.internal`.\n * Defaults to `issuer`.\n */\n readonly serverOrigin?: string;\n /**\n * Permit `http:` for these endpoints when they are not loopback, such as an emulator reached\n * through `host.docker.internal`. Development only: the client secret, the code, and the tokens\n * cross the network in the clear, and a name resolves wherever the machine has been told to.\n * Loopback needs no flag.\n */\n readonly allowPlaintext?: boolean;\n}\n\n/** A public OAuth client uses PKCE and has no client secret. */\nexport interface PublicOAuthOptions extends OAuthOptionsBase {\n readonly clientAuth: \"none\";\n readonly clientSecret?: never;\n}\n\n/** A confidential OAuth client authenticates with a required client secret. */\nexport interface ConfidentialOAuthOptions extends OAuthOptionsBase {\n /** Defaults to `client_secret_basic`. */\n readonly clientAuth?: \"client_secret_basic\" | \"client_secret_post\";\n readonly clientSecret: Redacted.Redacted<string> | Config.Config<Redacted.Redacted<string>>;\n}\n\nexport type OAuthOptions = PublicOAuthOptions | ConfidentialOAuthOptions;\n\nconst defaultIssuer = \"https://dash.cloudflare.com\";\n\n/**\n * The three OAuth endpoints Cloudflare serves under one origin. Consent is the browser's, exchange\n * and revocation are the server's, so a stage whose server reaches the same emulator by another\n * name gives two origins and keeps the paths.\n */\nconst endpointsOf = (origins: {\n readonly browser: string;\n readonly server: string;\n}): OAuth.Server => {\n const browser = origins.browser.replace(/\\/$/, \"\");\n const server = origins.server.replace(/\\/$/, \"\");\n return {\n authorization_endpoint: `${browser}/oauth2/auth`,\n issuer: browser,\n revocation_endpoint: `${server}/oauth2/revoke`,\n token_endpoint: `${server}/oauth2/token`,\n };\n};\n\nexport const server: OAuth.Server = endpointsOf({\n browser: defaultIssuer,\n server: defaultIssuer,\n});\n\nconst defaultScopes = [\"zone.read\", \"dns.read\", \"dns.write\", \"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 if (oauth.clientAuth === \"none\") {\n return { clientId, clientSecret: null, clientAuth: \"none\" } satisfies OAuth.Client;\n }\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 const browser = settings.issuer ?? defaultIssuer;\n const endpoints = endpointsOf({ browser, server: settings.serverOrigin ?? browser });\n const insecure =\n settings.allowPlaintext === undefined ? {} : { allowPlaintext: settings.allowPlaintext };\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: endpoints,\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: endpoints,\n client: oauthClientValue,\n code: input.code,\n state: input.params.state ?? \"\",\n callbackUrl: input.callbackUrl,\n codeVerifier: input.codeVerifier,\n fetch,\n ...insecure,\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: endpoints,\n client: yield* oauthClient(settings),\n refreshToken,\n fetch,\n ...insecure,\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: endpoints,\n client: yield* oauthClient(settings),\n token: accessToken,\n fetch,\n ...insecure,\n });\n }),\n };\n }\n\n return Provider.make<AccountContext>({\n id: \"cloudflare\",\n name: \"Cloudflare\",\n nameservers: [\"ns.cloudflare.com\"],\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/package.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  //#region package.json
2
- var version = "0.12.1";
2
+ var version = "0.12.2";
3
3
  //#endregion
4
4
  export { version };
5
5
 
@@ -21,37 +21,48 @@ export declare const TokenFields: Schema.Struct<{
21
21
  export type AccountContext = typeof AccountContext.Type;
22
22
  export interface Options {
23
23
  /** Omit to offer tokens only. */
24
- readonly oauth?: {
25
- readonly clientId: string | Config.Config<string>;
26
- readonly clientSecret: Redacted.Redacted<string> | Config.Config<Redacted.Redacted<string>>;
27
- /** Scope ids assigned to the OAuth client. Default: `zone:read`, `dns_records:edit`, `offline_access`. */
28
- readonly scopes?: ReadonlyArray<string>;
29
- /** Default `client_secret_basic`. */
30
- readonly clientAuth?: OAuth.ClientAuth;
31
- /**
32
- * Origin for consent, token exchange, and revocation, which Cloudflare serves at
33
- * `/oauth2/auth`, `/oauth2/token`, and `/oauth2/revoke` beneath it. Default
34
- * `https://dash.cloudflare.com`; a stage points it at an emulator that mounts the same three
35
- * paths. It stays separate from `baseUrl` because in production these are different hosts:
36
- * `dash.cloudflare.com` for OAuth, `api.cloudflare.com/client/v4` for the REST API.
37
- */
38
- readonly issuer?: string;
39
- /**
40
- * Where the server reaches the OAuth endpoints when that differs from where the browser does,
41
- * for example an API in a container reaching an emulator through `host.docker.internal`.
42
- * Defaults to `issuer`.
43
- */
44
- readonly serverOrigin?: string;
45
- /**
46
- * Permit `http:` for these endpoints when they are not loopback, such as an emulator reached
47
- * through `host.docker.internal`. Development only: the client secret, the code, and the tokens
48
- * cross the network in the clear, and a name resolves wherever the machine has been told to.
49
- * Loopback needs no flag.
50
- */
51
- readonly allowPlaintext?: boolean;
52
- };
24
+ readonly oauth?: OAuthOptions;
53
25
  readonly fetch?: Fetch;
54
26
  readonly baseUrl?: string;
55
27
  }
28
+ interface OAuthOptionsBase {
29
+ readonly clientId: string | Config.Config<string>;
30
+ /** Scope ids assigned to the OAuth client. Default: `zone.read`, `dns.read`, `dns.write`, `offline_access`. */
31
+ readonly scopes?: ReadonlyArray<string>;
32
+ /**
33
+ * Origin for consent, token exchange, and revocation, which Cloudflare serves at
34
+ * `/oauth2/auth`, `/oauth2/token`, and `/oauth2/revoke` beneath it. Default
35
+ * `https://dash.cloudflare.com`; a stage points it at an emulator that mounts the same three
36
+ * paths. It stays separate from `baseUrl` because in production these are different hosts:
37
+ * `dash.cloudflare.com` for OAuth, `api.cloudflare.com/client/v4` for the REST API.
38
+ */
39
+ readonly issuer?: string;
40
+ /**
41
+ * Where the server reaches the OAuth endpoints when that differs from where the browser does,
42
+ * for example an API in a container reaching an emulator through `host.docker.internal`.
43
+ * Defaults to `issuer`.
44
+ */
45
+ readonly serverOrigin?: string;
46
+ /**
47
+ * Permit `http:` for these endpoints when they are not loopback, such as an emulator reached
48
+ * through `host.docker.internal`. Development only: the client secret, the code, and the tokens
49
+ * cross the network in the clear, and a name resolves wherever the machine has been told to.
50
+ * Loopback needs no flag.
51
+ */
52
+ readonly allowPlaintext?: boolean;
53
+ }
54
+ /** A public OAuth client uses PKCE and has no client secret. */
55
+ export interface PublicOAuthOptions extends OAuthOptionsBase {
56
+ readonly clientAuth: "none";
57
+ readonly clientSecret?: never;
58
+ }
59
+ /** A confidential OAuth client authenticates with a required client secret. */
60
+ export interface ConfidentialOAuthOptions extends OAuthOptionsBase {
61
+ /** Defaults to `client_secret_basic`. */
62
+ readonly clientAuth?: "client_secret_basic" | "client_secret_post";
63
+ readonly clientSecret: Redacted.Redacted<string> | Config.Config<Redacted.Redacted<string>>;
64
+ }
65
+ export type OAuthOptions = PublicOAuthOptions | ConfidentialOAuthOptions;
56
66
  export declare const server: OAuth.Server;
57
67
  export declare const provider: (options?: Options) => Provider.Definition<AccountContext>;
68
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "domainkit",
3
- "version": "0.12.1",
3
+ "version": "0.12.2",
4
4
  "description": "Build reviewable DNS plans for custom domains in TypeScript",
5
5
  "keywords": [
6
6
  "custom-domains",