domainkit 0.12.1 → 0.13.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/dist/package.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  //#region package.json
2
- var version = "0.12.1";
2
+ var version = "0.13.0";
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 {};
@@ -30,9 +30,27 @@ import * as Reason from "./Reason.ts";
30
30
  import * as Receipt from "./Receipt.ts";
31
31
  import * as Storage from "./Storage.ts";
32
32
  import * as Verify from "./Verify.ts";
33
+ /**
34
+ * What DomainKit already knows about the request when it asks the host who is making it. Only
35
+ * `/callback/:provider` passes one, because it is the only route that points at a flow DomainKit
36
+ * recorded earlier; every other route calls `principal` with the request alone.
37
+ */
38
+ export interface IdentityContext {
39
+ /** The route being served. */
40
+ readonly endpoint: EndpointName;
41
+ /** The flow this callback claims to finish, as DomainKit recorded it when the flow started. */
42
+ readonly continuation: Storage.ContinuationHeader;
43
+ }
33
44
  /** The only service a host must implement for the server. */
34
45
  export interface IdentityService {
35
- readonly principal: (request: HttpServerRequest.HttpServerRequest) => Effect.Effect<Principal.Interface, Errors.DomainKitError>;
46
+ /**
47
+ * `context` is present only on the callback, where a host that keeps per-request tenancy can use
48
+ * `context.continuation.ownerId` to pick the right tenant out of a session that holds several.
49
+ * Ignoring it is safe everywhere: DomainKit checks the principal against the recorded flow on
50
+ * that route regardless, so a host that answers with the wrong one is refused rather than
51
+ * trusted.
52
+ */
53
+ readonly principal: (request: HttpServerRequest.HttpServerRequest, context?: IdentityContext) => Effect.Effect<Principal.Interface, Errors.DomainKitError>;
36
54
  /**
37
55
  * Which routes this principal may reach, checked after `principal` on every request. Fail with
38
56
  * reason `Forbidden` for the 403 a UI expects. Without it every authenticated principal reaches
@@ -80,6 +80,18 @@ declare const Continuation_base: Schema.Class<Continuation, Schema.Struct<{
80
80
  /** Interactive-flow state between `Connect.start` and `Connect.complete`. */
81
81
  export declare class Continuation extends Continuation_base {
82
82
  }
83
+ declare const ContinuationHeader_base: Schema.Class<ContinuationHeader, Schema.Struct<{
84
+ readonly ownerId: Schema.String;
85
+ readonly actorId: Schema.String;
86
+ readonly provider: Schema.String;
87
+ readonly expiresAt: Schema.DateTimeUtcFromString;
88
+ }>, {}>;
89
+ /**
90
+ * Who a continuation belongs to, without the continuation itself. `payload` has no place here: it
91
+ * carries the PKCE `codeVerifier`, which only the owner-scoped read may reach.
92
+ */
93
+ export declare class ContinuationHeader extends ContinuationHeader_base {
94
+ }
83
95
  export declare const AttemptStatus: Schema.Literals<readonly ["planned", "approved", "applying", "complete", "partial", "failed", "expired", "rejected"]>;
84
96
  export type AttemptStatus = typeof AttemptStatus.Type;
85
97
  /** Who declined the plan, why, and when. Terminal: the domain needs a new plan. */
@@ -192,6 +204,16 @@ export interface Interface {
192
204
  readonly put: (continuation: Continuation) => Fx<void>;
193
205
  /** Read without spending; a late read fails `Expired`. */
194
206
  readonly get: (id: string) => Fx<Continuation>;
207
+ /**
208
+ * The flow's owner, actor, provider, and expiry, without a principal and without spending the
209
+ * row. The one read in this interface that is not tenant-scoped, because `domainkit/server`
210
+ * has to learn whose flow a provider callback finishes before it can resolve the principal to
211
+ * scope by; the callback carries a continuation id and a session cookie and nothing else.
212
+ *
213
+ * A missing row fails `NotFound` and a stale one fails `Expired`, exactly as `get` does, so an
214
+ * expired id never reaches a host. `payload` is out of reach here by construction.
215
+ */
216
+ readonly header: (id: string) => Effect.Effect<ContinuationHeader, Errors.DomainKitError>;
195
217
  /** Exactly-once: the second consume of the same id fails `NotFound`; a late one fails `Expired`. */
196
218
  readonly consume: (id: string) => Fx<Continuation>;
197
219
  };
@@ -287,6 +309,8 @@ export interface AsyncInterface {
287
309
  readonly continuations: {
288
310
  readonly put: (principal: Principal.Interface, continuation: Continuation) => Promise<void>;
289
311
  readonly get: (principal: Principal.Interface, id: string) => Promise<Continuation>;
312
+ /** Takes no principal: see `Interface.continuations.header`. Never returns `payload`. */
313
+ readonly header: (id: string) => Promise<ContinuationHeader>;
290
314
  readonly consume: (principal: Principal.Interface, id: string) => Promise<Continuation>;
291
315
  };
292
316
  readonly attempts: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "domainkit",
3
- "version": "0.12.1",
3
+ "version": "0.13.0",
4
4
  "description": "Build reviewable DNS plans for custom domains in TypeScript",
5
5
  "keywords": [
6
6
  "custom-domains",