@shelbyb/daywalker-sdk 0.6.0 → 0.8.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/client.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { type ResolvedConfig } from "./config.js";
2
- import { type DaywalkerConfig, type TokenSet, type GetTokensOptions, type PublicJwk, type RetryOptions, type SelfCheckResult, type TokenPayload, type ValidateResult } from "./types.js";
2
+ import { type DaywalkerConfig, type ValidateTokenOptions, type TokenSet, type GetTokensOptions, type License, type LicenseOptions, type ConfigBundle, type PublicJwk, type RetryOptions, type SelfCheckResult, type TokenPayload, type ValidateResult } from "./types.js";
3
3
  /** Validation results kept in memory; the least recently used are evicted past this. */
4
4
  export declare const MAX_VALIDATION_CACHE_ENTRIES = 1000;
5
5
  /** Longest 429 `Retry-After` waited out by default (`retry.maxRetryAfterMs`); longer ones return the 429. */
@@ -17,21 +17,45 @@ export type DaywalkerFetchInit = Omit<RequestInit, "body"> & {
17
17
  /** Sent as `Idempotency-Key` (unless already set) and lets non-idempotent requests retry network errors. */
18
18
  idempotencyKey?: string;
19
19
  };
20
+ /**
21
+ * Longest a successful validation is cached (5 minutes), whatever `cacheTtlMs` says, so a revoked token
22
+ * can't stay accepted for long.
23
+ */
24
+ export declare const MAX_CACHE_TTL_MS = 300000;
25
+ /** Longest incoming token accepted (characters); longer input is refused without calling Daywalker. */
26
+ export declare const MAX_TOKEN_LENGTH = 8192;
27
+ /** How long a fetched JWKS (`jwks: "auto"`) is used before it is refetched. */
28
+ export declare const JWKS_CACHE_TTL_MS = 600000;
29
+ /**
30
+ * Least time between two JWKS fetches, whatever triggers them (an unknown `kid`, an expired set, a failed
31
+ * fetch), so a flood of tokens naming made-up `kid`s costs at most one request to Daywalker per interval.
32
+ */
33
+ export declare const JWKS_REFETCH_COOLDOWN_MS = 30000;
34
+ /** What signs this client's requests, and where it came from. */
35
+ type SignerSource = "signer" | "signingKey" | "ephemeral" | "keyStore";
20
36
  export declare class Daywalker {
21
- private readonly _config;
22
- private readonly _rawConfig;
23
- private _signingKeyPromise?;
24
- private _previousSigningKeyPromise?;
25
- private readonly _cache;
26
- private readonly _inflight;
37
+ #private;
27
38
  constructor(config?: DaywalkerConfig);
28
- get config(): ResolvedConfig;
39
+ /** The resolved configuration, frozen, with the API key and private keys shown as "[configured]". */
40
+ get config(): Readonly<ResolvedConfig>;
29
41
  toJSON(): Record<string, unknown>;
30
- private getLoadedSigningKey;
31
- private _dpopKey;
32
- /** The client's DPoP key, generated on first use. Issued tokens are bound to it for this client's life. */
33
- private getDpopKey;
34
- private getLoadedPreviousSigningKey;
42
+ /**
43
+ * Shuts the client down: revokes its ephemeral signing key (which also ends the OAuth sessions it started)
44
+ * and forgets its tokens. Afterwards every call that needs Daywalker fails with CLIENT_CLOSED. Safe to call
45
+ * twice. Keys from `signingKey`, `keyStore` or `signer` are long-lived and are left alone.
46
+ */
47
+ close(): Promise<{
48
+ revoked: string[];
49
+ failed: string[];
50
+ }>;
51
+ /**
52
+ * Resolves the signing key now instead of on the first request: loads it, or provisions and registers it
53
+ * (see `keyStore`). Call it at startup to fail fast. Returns where the key came from and its id.
54
+ */
55
+ provisionSigningKey(): Promise<{
56
+ source: SignerSource;
57
+ keyId: string;
58
+ } | null>;
35
59
  /**
36
60
  * Signs and executes a request to Daywalker Auth (same origin as `authUrl` only).
37
61
  * - Redirects are never followed: a 3xx throws a DaywalkerError (code UNEXPECTED_REDIRECT), so the API key,
@@ -41,58 +65,49 @@ export declare class Daywalker {
41
65
  * - 429s are retried with exponential backoff, honoring `Retry-After` up to `retry.maxRetryAfterMs`. Network
42
66
  * errors and timeouts are retried only for GET / HEAD / OPTIONS, with an `idempotencyKey`, or when
43
67
  * `retry.retryNetworkErrors` is true. Options merge as defaults < `config.retry` < `init.retry`.
44
- * - An explicit `Authorization` header is sent as given; otherwise `Bearer <apiKey>` is added. The signature
45
- * covers method, path, query, authority and Content-Digest, never Authorization.
68
+ * - An explicit `Authorization` header is sent as given; otherwise `DPoP <access token>` from the OAuth
69
+ * client credentials flow (`accessToken()`) is added, with a DPoP proof bound to it. The API key is never
70
+ * sent here: it only authenticates the client credentials grant. An answer of 401 ACCESS_TOKEN_EXPIRED
71
+ * refreshes the token and retries once. The signature covers method, path, query, authority and
72
+ * Content-Digest, never Authorization.
46
73
  */
47
74
  fetch(path: string, init?: DaywalkerFetchInit): Promise<Response>;
48
- private _cacheGet;
49
- private _cacheSet;
50
75
  /**
51
- * Validates an incoming bearer token with memory caching and discriminated union return.
52
- * Supports zero-egress offline verification for JWT token leases when Daywalker's public key is configured.
53
- * A missing token is always rejected (401); it never falls back to this service's own API key —
54
- * use `validateServiceKey()` for that.
55
- * Online validation fails closed: only a 2xx JSON answer with `valid: true` and a sane shape is accepted.
56
- * Outages (network errors, timeouts, 5xx, malformed answers) return status 503 with error NETWORK_ERROR or
57
- * SERVICE_UNAVAILABLE and are never cached. Each call gets its own copy of the (cached) result.
76
+ * Validates an incoming token with memory caching and a discriminated union result.
77
+ * - Access tokens (JWTs) are verified offline when Daywalker's public key is configured. A DPoP-bound
78
+ * token (`cnf.jkt`) is only accepted with a valid DPoP proof (`options.dpop`; the adapters pass it).
79
+ * - Other tokens are validated online with `POST /api/v1/tokens/validate`, authenticated with this
80
+ * service's own DPoP-bound access token. The answer must name the expected service.
81
+ * - A missing, oversized or malformed token is rejected (401) without calling Daywalker. A token is never
82
+ * replaced by this service's own credentials: use `validateServiceCredentials()` for that.
83
+ * - Online validation fails closed: only a 2xx JSON answer with `valid: true` and a sane shape is accepted.
84
+ * Outages (network errors, timeouts, 5xx, malformed answers) return status 503 with error NETWORK_ERROR or
85
+ * SERVICE_UNAVAILABLE and are never cached. Each call gets its own copy of the (cached) result.
58
86
  */
59
- validateToken(tokenOrOptions?: string | {
87
+ validateToken(tokenOrOptions?: string | (ValidateTokenOptions & {
60
88
  token?: string;
61
- serviceSlug?: string;
62
- forceRefresh?: boolean;
63
- offlineOnly?: boolean;
64
- }, options?: {
65
- serviceSlug?: string;
66
- forceRefresh?: boolean;
67
- offlineOnly?: boolean;
68
- }): Promise<ValidateResult>;
69
- private _validateOnline;
89
+ }), options?: ValidateTokenOptions): Promise<ValidateResult>;
70
90
  /**
71
- * Service health check: validates this service's own configured API key (DAYWALKER_API_KEY).
72
- * Never use this to authenticate an incoming request.
91
+ * Service health check: runs this service's OAuth client credentials flow (`accessToken()`) and checks
92
+ * that the granted services include `serviceSlug` (default: the configured one; with none configured, at
93
+ * least one service must be granted). Never use this to authenticate an incoming request. `forceRefresh`
94
+ * starts a new client credentials grant.
73
95
  */
74
- validateServiceKey(options?: {
96
+ validateServiceCredentials(options?: {
75
97
  serviceSlug?: string;
76
98
  forceRefresh?: boolean;
77
99
  }): Promise<ValidateResult>;
78
100
  /**
79
101
  * Throws DaywalkerAuthError or DaywalkerRateLimitError if token is invalid or rate limited.
80
102
  */
81
- requireValidToken(tokenOrOptions?: string | {
103
+ requireValidToken(tokenOrOptions?: string | (ValidateTokenOptions & {
82
104
  token?: string;
83
- serviceSlug?: string;
84
- offlineOnly?: boolean;
85
- }, options?: {
86
- serviceSlug?: string;
87
- offlineOnly?: boolean;
88
- }): Promise<TokenPayload>;
89
- private _tokens;
90
- private _tokenFlight;
91
- private _tokenRequest;
105
+ }), options?: ValidateTokenOptions): Promise<TokenPayload>;
92
106
  /**
93
107
  * Client credentials grant (RFC 6749 §4.4): the signed request (API key + registered Ed25519 key) is
94
- * exchanged for a short-lived access token JWT listing every service the credentials cover, and a
95
- * rotating refresh token. No service slug is sent. The tokens are remembered for `accessToken()`.
108
+ * exchanged for a short-lived access token JWT and a rotating refresh token. By default the grant is
109
+ * narrowed to the configured service slug(s) (least privilege); with none configured it covers every
110
+ * service the credentials do. The tokens are remembered for `accessToken()`. The result is frozen.
96
111
  */
97
112
  getTokens(options?: GetTokensOptions): Promise<TokenSet>;
98
113
  /**
@@ -107,6 +122,20 @@ export declare class Daywalker {
107
122
  * refresh token is never presented twice.
108
123
  */
109
124
  accessToken(): Promise<string>;
125
+ /**
126
+ * The service's license key from Daywalker (issued with the client credentials grant, valid for one hour,
127
+ * cached in memory until a minute before it expires). A revoked or suspended service gets no license: this
128
+ * throws the DaywalkerAuthError Daywalker answered with (`SERVICE_REVOKED`, `SERVICE_SUSPENDED`,
129
+ * `DOMAIN_REVOKED`, ...). `LICENSE_UNAVAILABLE` (403) means Daywalker granted the service without a license.
130
+ * Never log the key.
131
+ */
132
+ getLicense(options?: LicenseOptions): Promise<License>;
133
+ /**
134
+ * Fetches the config bundle (`POST /api/v1/services/:slug/bundle`) and opens it with the license key
135
+ * (AES-256-GCM). The bundle can only be read by a client that got the license from Daywalker. At a window
136
+ * rollover the key is refreshed once and the bundle opened again. Cached until shortly before it expires.
137
+ */
138
+ fetchBundle(options?: LicenseOptions): Promise<ConfigBundle>;
110
139
  /**
111
140
  * Fetches Daywalker's public JWKS (GET /.well-known/jwks.json) for offline lease verification.
112
141
  * Uses the configured `fetch`, sends no credentials, never follows redirects and times out after `timeoutMs`.
@@ -122,7 +151,8 @@ export declare class Daywalker {
122
151
  keys: PublicJwk[];
123
152
  }>;
124
153
  /**
125
- * Check configuration and verify signature readiness.
154
+ * Check configuration and verify signature readiness. Never provisions or registers a key: with a
155
+ * `keyStore` it only reads what is stored and reports what first use would need.
126
156
  */
127
157
  selfCheck(): Promise<SelfCheckResult>;
128
158
  }
@@ -131,3 +161,4 @@ export declare class Daywalker {
131
161
  */
132
162
  export declare function createDaywalker(config?: DaywalkerConfig): Daywalker;
133
163
  export declare const daywalker: Daywalker & (() => Daywalker);
164
+ export {};