@kanonak-protocol/sdk 5.38.0 → 6.0.1
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/auth/CredentialProvider.d.ts +69 -49
- package/dist/auth/ExecCredentialProvider.d.ts +55 -0
- package/dist/auth/OAuthDiscovery.d.ts +38 -10
- package/dist/auth/TokenCredentialProvider.d.ts +26 -6
- package/dist/auth/challenges.d.ts +17 -0
- package/dist/auth/credentialAnswer.d.ts +34 -0
- package/dist/auth/credentialConfig.d.ts +32 -0
- package/dist/auth/credentialedFetch.d.ts +33 -22
- package/dist/auth/index.d.ts +10 -4
- package/dist/auth/scope.d.ts +30 -0
- package/dist/browser.d.ts +4 -2
- package/dist/browser.js +1 -1
- package/dist/{chunk-HKX27KBZ.js → chunk-7F2EAT5Y.js} +1 -1
- package/dist/chunk-HKQTB65D.js +2 -0
- package/dist/chunk-PNBTIGX7.js +2 -0
- package/dist/{chunk-DONOB6RA.js → chunk-SBO34ENZ.js} +1 -1
- package/dist/http/HttpError.d.ts +38 -0
- package/dist/http/kanonakFetch.d.ts +5 -2
- package/dist/index.d.ts +8 -2
- package/dist/index.js +2 -1
- package/dist/repositories/browser.js +1 -1
- package/dist/repositories/index.js +1 -1
- package/dist/server/index.js +5 -5
- package/package.json +4 -3
- package/vectors/credential-helper-vectors.json +1039 -0
- package/dist/chunk-LBVHWFFL.js +0 -2
- package/dist/chunk-OOYGILH4.js +0 -2
|
@@ -1,65 +1,85 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The credential seam between the open protocol layer and a platform's identity
|
|
3
|
-
* mechanism (see ideas/protocol-vs-platform-auth-split.md
|
|
3
|
+
* mechanism (see ideas/protocol-vs-platform-auth-split.md, and #126 for its
|
|
4
|
+
* resource-scoped shape).
|
|
4
5
|
*
|
|
5
6
|
* The protocol layer's entire knowledge of credentials is this interface: given
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* never crosses this boundary
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
* consumption transport produced by {@link createCredentialedFetch} has this
|
|
12
|
-
* shape; repositories and the server accept it so a host can inject auth. */
|
|
13
|
-
export type AuthenticatedFetchFn = (url: string, publisher: string, method?: string) => Promise<Response>;
|
|
14
|
-
/**
|
|
15
|
-
* What a provider contributes to an outgoing request — scheme-agnostic across
|
|
16
|
-
* header-based credentials (Bearer, DPoP-bound, Basic-wrapped JWT). The protocol
|
|
17
|
-
* layer merges `headers` verbatim and never inspects how they were formed.
|
|
7
|
+
* the resource being requested (and any server challenge), a provider returns
|
|
8
|
+
* headers and the scope they are good for, or declines. How the credential was
|
|
9
|
+
* minted, stored, or proven never crosses this boundary. The provider decides
|
|
10
|
+
* how wide the scope is; the transport never widens it, attaches the headers
|
|
11
|
+
* only to URLs inside it, and caches them by it until `expiresAt`.
|
|
18
12
|
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
13
|
+
* The same shapes are the exec credential helper's wire protocol
|
|
14
|
+
* ({@link ExecCredentialProvider}), so a helper and an in-process provider are
|
|
15
|
+
* interchangeable.
|
|
22
16
|
*/
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
* header), so the consumer can pre-empt re-acquisition before a 401. */
|
|
29
|
-
expiresAt?: string;
|
|
30
|
-
}
|
|
17
|
+
/** A fetch that authenticates a request. The consumption transport produced by
|
|
18
|
+
* {@link createCredentialedFetch} has this shape; repositories and the server
|
|
19
|
+
* accept it so a host can inject auth. `publisher` names the package publisher
|
|
20
|
+
* the request is for; credentials are chosen by the request URL, not by it. */
|
|
21
|
+
export type AuthenticatedFetchFn = (url: string, publisher: string, method?: string) => Promise<Response>;
|
|
31
22
|
/**
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
* provider decides how to satisfy it (bind a nonce, step up, or re-mint).
|
|
23
|
+
* One challenge from a `WWW-Authenticate` response header (RFC 9110 §11.6.1),
|
|
24
|
+
* e.g. `Bearer resource_metadata="…", error="invalid_token"`.
|
|
35
25
|
*/
|
|
36
26
|
export interface AuthChallenge {
|
|
37
|
-
/** The
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
|
|
27
|
+
/** The auth-scheme as the server spelled it; compare case-insensitively. */
|
|
28
|
+
scheme: string;
|
|
29
|
+
/** The auth-params, names lower-cased (RFC 9110: case-insensitive), values unquoted. */
|
|
30
|
+
params: Record<string, string>;
|
|
31
|
+
/** A token68 value, for a challenge that carries one instead of auth-params. */
|
|
32
|
+
token68?: string;
|
|
43
33
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
34
|
+
/** What the transport asks a provider for. */
|
|
35
|
+
export interface CredentialRequest {
|
|
36
|
+
/** The absolute https URL being requested. */
|
|
37
|
+
resource: string;
|
|
38
|
+
/** The request's HTTP method. */
|
|
39
|
+
method: string;
|
|
40
|
+
/** The server's challenges, in the order it sent them. Absent when the
|
|
41
|
+
* transport asks pre-emptively, for a resource it knows is gated. */
|
|
42
|
+
challenges?: AuthChallenge[];
|
|
53
43
|
}
|
|
44
|
+
/** A request to forget the credential a server rejected. */
|
|
45
|
+
export interface CredentialEraseRequest extends CredentialRequest {
|
|
46
|
+
/** The scope of the rejected credential. */
|
|
47
|
+
scope: string;
|
|
48
|
+
}
|
|
49
|
+
/** A credential: headers, and where they may be attached. */
|
|
50
|
+
export interface Credential {
|
|
51
|
+
/** Headers merged verbatim onto requests inside `scope` (e.g. Authorization). */
|
|
52
|
+
headers: Record<string, string>;
|
|
53
|
+
/** An https origin plus a path prefix, matched on whole path segments. It must
|
|
54
|
+
* contain the requested resource. */
|
|
55
|
+
scope: string;
|
|
56
|
+
/** When the credential stops being valid, in Unix seconds. Absent: valid for
|
|
57
|
+
* as long as this process runs. */
|
|
58
|
+
expiresAt?: number;
|
|
59
|
+
}
|
|
60
|
+
/** A provider's answer. `declined` means only "no credential for this resource";
|
|
61
|
+
* a failure is an exception, never a decline. */
|
|
62
|
+
export type CredentialAnswer = ({
|
|
63
|
+
status: 'credential';
|
|
64
|
+
} & Credential) | {
|
|
65
|
+
status: 'declined';
|
|
66
|
+
reason?: string;
|
|
67
|
+
};
|
|
54
68
|
/**
|
|
55
69
|
* The credential seam. A platform implements this; the protocol layer only calls
|
|
56
|
-
* it.
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
* apart; a binary drop-and-retry cannot.
|
|
70
|
+
* it. `get` answers for a resource; `erase` forgets a credential the server
|
|
71
|
+
* rejected (401), before the transport asks once more. A server that finds a
|
|
72
|
+
* credential valid but insufficient (403 with `error="insufficient_scope"`) gets
|
|
73
|
+
* `get` with its challenge and no `erase`: the credential still serves the rest
|
|
74
|
+
* of its scope.
|
|
62
75
|
*/
|
|
63
76
|
export interface CredentialProvider {
|
|
64
|
-
|
|
77
|
+
get(request: CredentialRequest): Promise<CredentialAnswer>;
|
|
78
|
+
erase(request: CredentialEraseRequest): Promise<void>;
|
|
79
|
+
}
|
|
80
|
+
/** A request that could not be authenticated: a provider failed, declined a
|
|
81
|
+
* resource that requires a credential, answered outside the protocol, or a
|
|
82
|
+
* server rejected the credential it answered with. Never silently anonymous. */
|
|
83
|
+
export declare class CredentialError extends Error {
|
|
84
|
+
constructor(message: string);
|
|
65
85
|
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { Writable } from 'node:stream';
|
|
2
|
+
import { type CredentialAnswer, type CredentialEraseRequest, type CredentialProvider, type CredentialRequest } from './CredentialProvider.js';
|
|
3
|
+
/** How long a helper may run before it is stopped. */
|
|
4
|
+
export declare const CREDENTIAL_HELPER_TIMEOUT_MS = 120000;
|
|
5
|
+
export interface ExecCredentialProviderOptions {
|
|
6
|
+
/** The helper's command line. It is split on whitespace and run without a
|
|
7
|
+
* shell (as Go's GOAUTH runs its commands); the operation, `get` or `erase`,
|
|
8
|
+
* is appended as the last argument. */
|
|
9
|
+
command: string;
|
|
10
|
+
/** Whether the helper may interact with the user; sent as `interactive`. */
|
|
11
|
+
interactive?: boolean;
|
|
12
|
+
/** Milliseconds a helper may run; {@link CREDENTIAL_HELPER_TIMEOUT_MS} by default. */
|
|
13
|
+
timeoutMs?: number;
|
|
14
|
+
/** Where the helper's stderr is shown as it runs; `process.stderr` by default. */
|
|
15
|
+
stderr?: Writable;
|
|
16
|
+
/** The clock, in Unix seconds. */
|
|
17
|
+
nowSeconds?: () => number;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* A {@link CredentialProvider} that is a separate program: the exec credential
|
|
21
|
+
* helper protocol (#126), the shape git, docker, cargo (RFC 3139), kubectl and
|
|
22
|
+
* Bazel credential helpers share.
|
|
23
|
+
*
|
|
24
|
+
* The provider runs `<command> get` or `<command> erase`, writes one JSON
|
|
25
|
+
* request to its stdin, and reads one JSON answer from its stdout:
|
|
26
|
+
*
|
|
27
|
+
* request {"version": 1, "operation": "get" | "erase", "resource": URL,
|
|
28
|
+
* "method": "GET", "challenges"?: [{scheme, params, token68?}],
|
|
29
|
+
* "scope"?: (erase: the rejected credential's scope), "interactive": bool}
|
|
30
|
+
* get {"version": 1, "status": "credential", "headers": {...},
|
|
31
|
+
* "scope": "https://host/prefix", "expiresAt"?: unix-seconds}
|
|
32
|
+
* or {"version": 1, "status": "declined", "reason"?: "..."}
|
|
33
|
+
* erase {"version": 1, "status": "erased"}
|
|
34
|
+
*
|
|
35
|
+
* A non-zero exit, a timeout, or an answer outside the protocol is a
|
|
36
|
+
* {@link CredentialError} carrying the helper's stderr; nothing falls back to
|
|
37
|
+
* anonymous. The helper's stderr is also shown as it runs. Requests and answers
|
|
38
|
+
* are never logged: they carry credentials.
|
|
39
|
+
*/
|
|
40
|
+
export declare class ExecCredentialProvider implements CredentialProvider {
|
|
41
|
+
private readonly argv;
|
|
42
|
+
private readonly interactive;
|
|
43
|
+
private readonly timeoutMs;
|
|
44
|
+
private readonly stderr;
|
|
45
|
+
private readonly now;
|
|
46
|
+
constructor(opts: ExecCredentialProviderOptions);
|
|
47
|
+
/** The command line as configured. */
|
|
48
|
+
get command(): string;
|
|
49
|
+
get(request: CredentialRequest): Promise<CredentialAnswer>;
|
|
50
|
+
erase(request: CredentialEraseRequest): Promise<void>;
|
|
51
|
+
private wireRequest;
|
|
52
|
+
private run;
|
|
53
|
+
}
|
|
54
|
+
/** A helper command line split the way it is run: on whitespace, no quoting. */
|
|
55
|
+
export declare function splitCommandLine(command: string): string[];
|
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OAuth 2.0 Protected Resource Metadata (RFC 9728), as validated by
|
|
3
|
+
* {@link OAuthDiscovery}: only documents whose `resource` checks out are returned.
|
|
4
|
+
*/
|
|
5
|
+
export interface ProtectedResourceMetadata {
|
|
6
|
+
/** The protected resource's identifier (`resource`). */
|
|
7
|
+
resource: string;
|
|
8
|
+
/** Issuer identifiers of the authorization servers that protect it
|
|
9
|
+
* (`authorization_servers`; empty when the document names none). */
|
|
10
|
+
authorizationServers: string[];
|
|
11
|
+
}
|
|
1
12
|
/**
|
|
2
13
|
* OAuth 2.0 Authorization Server Metadata (RFC 8414).
|
|
3
14
|
*/
|
|
@@ -44,18 +55,35 @@ export declare const DEFAULT_CONSENT_BINDING_PARAM = "key_thumbprint";
|
|
|
44
55
|
*/
|
|
45
56
|
export declare class OAuthDiscovery {
|
|
46
57
|
private readonly cache;
|
|
47
|
-
private readonly
|
|
58
|
+
private readonly wellKnownCache;
|
|
59
|
+
private readonly metadataUrlCache;
|
|
60
|
+
/**
|
|
61
|
+
* RFC 9728 §3.1: the metadata of the resource `identifier` (an https URL, e.g.
|
|
62
|
+
* an origin), at its well-known URL: `/.well-known/oauth-protected-resource`
|
|
63
|
+
* inserted between the host and the identifier's path. Validated per §3.3:
|
|
64
|
+
* the document's `resource` must be identical to `identifier` (compared after
|
|
65
|
+
* URL normalization, so `https://h` and `https://h/` are one identifier).
|
|
66
|
+
*
|
|
67
|
+
* null when the resource does not advertise metadata there: a 404 or 410, a
|
|
68
|
+
* redirect (metadata for an origin is served by that origin), or a 200 that is
|
|
69
|
+
* not a JSON document (static hosts answer unknown paths with an HTML page).
|
|
70
|
+
* Throws a `KanonakHttpError` for any other status, including 401/403 (the
|
|
71
|
+
* host gates even its metadata), and an `Error` for a JSON document that
|
|
72
|
+
* fails validation, which RFC 9728 forbids using.
|
|
73
|
+
*/
|
|
74
|
+
protectedResourceMetadata(identifier: string): Promise<ProtectedResourceMetadata | null>;
|
|
48
75
|
/**
|
|
49
|
-
* RFC 9728
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
* asks about, most of which won't be Kanonak resources).
|
|
76
|
+
* RFC 9728 §5.1: the metadata at the URL a challenge's `resource_metadata`
|
|
77
|
+
* parameter names, for the request to `requestedUrl` that drew the challenge.
|
|
78
|
+
* §3.3 requires its `resource` to match the requested URL. A resource
|
|
79
|
+
* identifier covers every URL beneath it, so this checks that `requestedUrl`
|
|
80
|
+
* lies inside `resource`: same origin, on whole path segments (the reading
|
|
81
|
+
* deployed clients use). Throws when the document is missing, is not JSON,
|
|
82
|
+
* or fails that check.
|
|
57
83
|
*/
|
|
58
|
-
|
|
84
|
+
protectedResourceMetadataAt(metadataUrl: string, requestedUrl: string): Promise<ProtectedResourceMetadata>;
|
|
85
|
+
private fetchWellKnown;
|
|
86
|
+
private fetchMetadataAt;
|
|
59
87
|
discover(host: string): Promise<OAuthServerMetadata | null>;
|
|
60
88
|
supportsOAuth(host: string): Promise<boolean>;
|
|
61
89
|
static supportsPkceS256(m: OAuthServerMetadata): boolean;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { CredentialAnswer, CredentialProvider, CredentialRequest } from './CredentialProvider.js';
|
|
2
|
+
import { OAuthDiscovery } from './OAuthDiscovery.js';
|
|
2
3
|
/**
|
|
3
4
|
* Reference / BYO-token credential provider — the open layer's minimal way to
|
|
4
5
|
* consume a gated package without any platform tooling. It reads a token from
|
|
@@ -6,11 +7,18 @@ import type { CredentialProvider, RequestCredential, ResolveOptions } from './Cr
|
|
|
6
7
|
*
|
|
7
8
|
* `Bearer` here is only the *transport* of whatever the issuer minted — its
|
|
8
9
|
* security properties are the issuer's choice, not the protocol's. A platform
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
10
|
+
* supplies a stronger provider, in process or as an exec credential helper;
|
|
11
|
+
* this one keeps the open SDK/CLI usable standalone, e.g. for an enterprise
|
|
12
|
+
* pointing at its own OAuth-issued token, or for CI.
|
|
12
13
|
*
|
|
13
|
-
*
|
|
14
|
+
* A token is presented only to a resource that names its authorization server
|
|
15
|
+
* in validated RFC 9728 metadata: from the challenge's `resource_metadata`, or
|
|
16
|
+
* else at the resource origin's well-known URL. The credential's scope is that
|
|
17
|
+
* metadata's `resource`, so the token is attached nowhere else. A resource with
|
|
18
|
+
* no such metadata is declined.
|
|
19
|
+
*
|
|
20
|
+
* Token sources, in precedence order, per authorization-server host (the first
|
|
21
|
+
* of the resource's `authorization_servers` that has one):
|
|
14
22
|
* 1. an explicit `tokens` map
|
|
15
23
|
* 2. env `KANONAK_TOKEN_<AUTHORITY>` (host upper-cased, non-alphanumeric → `_`)
|
|
16
24
|
* 3. env `KANONAK_TOKEN` (applies to any authority)
|
|
@@ -20,10 +28,22 @@ export interface TokenCredentialProviderOptions {
|
|
|
20
28
|
tokens?: Record<string, string>;
|
|
21
29
|
/** Environment to read from; defaults to `process.env` when available. */
|
|
22
30
|
env?: Record<string, string | undefined>;
|
|
31
|
+
/** RFC 9728 discovery; a fresh {@link OAuthDiscovery} by default. */
|
|
32
|
+
discovery?: Pick<OAuthDiscovery, 'protectedResourceMetadata' | 'protectedResourceMetadataAt'>;
|
|
23
33
|
}
|
|
34
|
+
/** The environment variable a BYO token is read from, and the prefix of its
|
|
35
|
+
* per-authority form. */
|
|
36
|
+
export declare const TOKEN_ENV = "KANONAK_TOKEN";
|
|
37
|
+
/** `KANONAK_TOKEN_<AUTHORITY>` for an authorization-server host. */
|
|
38
|
+
export declare function tokenEnvKeyFor(authority: string): string;
|
|
24
39
|
export declare class TokenCredentialProvider implements CredentialProvider {
|
|
25
40
|
private readonly tokens;
|
|
26
41
|
private readonly env;
|
|
42
|
+
private readonly discovery;
|
|
27
43
|
constructor(opts?: TokenCredentialProviderOptions);
|
|
28
|
-
|
|
44
|
+
get(request: CredentialRequest): Promise<CredentialAnswer>;
|
|
45
|
+
/** A configured token cannot be erased; the transport asks once more, gets the
|
|
46
|
+
* same token, and reports the server's second rejection. */
|
|
47
|
+
erase(): Promise<void>;
|
|
48
|
+
private metadataFor;
|
|
29
49
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { AuthChallenge } from './CredentialProvider.js';
|
|
2
|
+
/**
|
|
3
|
+
* Parse a `WWW-Authenticate` field value into its challenges (RFC 9110 §11.6.1):
|
|
4
|
+
*
|
|
5
|
+
* WWW-Authenticate = #challenge
|
|
6
|
+
* challenge = auth-scheme [ 1*SP ( token68 / #auth-param ) ]
|
|
7
|
+
* auth-param = token BWS "=" BWS ( token / quoted-string )
|
|
8
|
+
* token68 = 1*( ALPHA / DIGIT / "-" / "." / "_" / "~" / "+" / "/" ) *"="
|
|
9
|
+
*
|
|
10
|
+
* Several header lines arrive joined with ", " (the Fetch `Headers` contract),
|
|
11
|
+
* which is the same list syntax, so one value covers them all. Challenges keep
|
|
12
|
+
* the server's order. Parameter names are lower-cased, because RFC 9110 makes
|
|
13
|
+
* them case-insensitive; the scheme keeps the server's spelling (compare it
|
|
14
|
+
* case-insensitively). A malformed value, or a parameter repeated within one
|
|
15
|
+
* challenge, throws: a challenge we cannot read is not one we can act on.
|
|
16
|
+
*/
|
|
17
|
+
export declare function parseWwwAuthenticate(value: string): AuthChallenge[];
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { CredentialAnswer, CredentialRequest } from './CredentialProvider.js';
|
|
2
|
+
/** The exec credential helper protocol version this SDK speaks (#126). */
|
|
3
|
+
export declare const CREDENTIAL_HELPER_PROTOCOL_VERSION = 1;
|
|
4
|
+
/** Why an answer was refused. Stable: the conformance vectors pin them. */
|
|
5
|
+
export type AnswerProblem = 'malformed' | 'version' | 'status' | 'headers' | 'scope' | 'scope-excludes-resource' | 'expires-at' | 'expired' | 'reason';
|
|
6
|
+
export type AnswerCheck<T> = {
|
|
7
|
+
ok: true;
|
|
8
|
+
answer: T;
|
|
9
|
+
} | {
|
|
10
|
+
ok: false;
|
|
11
|
+
problem: AnswerProblem;
|
|
12
|
+
message: string;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Check a provider's answer to `request` against the protocol, for any provider,
|
|
16
|
+
* in-process or exec. A credential must:
|
|
17
|
+
* - carry at least one header, each a valid field name with a value free of
|
|
18
|
+
* CR, LF and NUL;
|
|
19
|
+
* - name a scope that is an https origin plus a path prefix (no userinfo, query
|
|
20
|
+
* or fragment) and that contains the requested resource;
|
|
21
|
+
* - when it has `expiresAt` (Unix seconds), not be expired at `nowSeconds`.
|
|
22
|
+
* A decline may carry a string `reason`. Fields the protocol does not define
|
|
23
|
+
* are ignored, so a newer provider can add some.
|
|
24
|
+
*/
|
|
25
|
+
export declare function checkCredentialAnswer(request: CredentialRequest, value: unknown, nowSeconds: number): AnswerCheck<CredentialAnswer>;
|
|
26
|
+
/** {@link checkCredentialAnswer} for a helper's wire answer, which also carries
|
|
27
|
+
* `"version": 1`. */
|
|
28
|
+
export declare function checkHelperAnswer(request: CredentialRequest, value: unknown, nowSeconds: number): AnswerCheck<CredentialAnswer>;
|
|
29
|
+
/** A helper's answer to `erase`: exactly `{"version": 1, "status": "erased"}`
|
|
30
|
+
* (other fields ignored). Erasing a credential the helper no longer holds is
|
|
31
|
+
* still "erased". */
|
|
32
|
+
export declare function checkHelperEraseAnswer(value: unknown): AnswerCheck<{
|
|
33
|
+
status: 'erased';
|
|
34
|
+
}>;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { CredentialProvider } from './CredentialProvider.js';
|
|
2
|
+
import type { OAuthDiscovery } from './OAuthDiscovery.js';
|
|
3
|
+
/** The environment variable naming a credential helper's command line. */
|
|
4
|
+
export declare const CREDENTIAL_HELPER_ENV = "KANONAK_CREDENTIAL_HELPER";
|
|
5
|
+
/** The user-level config file: `~/.kanonak/config.json`. */
|
|
6
|
+
export declare function userConfigPath(): string;
|
|
7
|
+
export interface CredentialProviderConfigOptions {
|
|
8
|
+
/** Environment to read; `process.env` by default. */
|
|
9
|
+
env?: Record<string, string | undefined>;
|
|
10
|
+
/** The user config file; {@link userConfigPath} by default. */
|
|
11
|
+
configPath?: string;
|
|
12
|
+
/** Whether a helper may interact with the user. */
|
|
13
|
+
interactive?: boolean;
|
|
14
|
+
/** RFC 9728 discovery shared with the transport. */
|
|
15
|
+
discovery?: Pick<OAuthDiscovery, 'protectedResourceMetadata' | 'protectedResourceMetadataAt'>;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* The credential provider the user configured, from user-level sources only.
|
|
19
|
+
* Nothing in a workspace or repository can name a helper: that would let a
|
|
20
|
+
* cloned repository choose a command the CLI runs.
|
|
21
|
+
*
|
|
22
|
+
* - A credential helper's command line, from `KANONAK_CREDENTIAL_HELPER`, or
|
|
23
|
+
* else `credentialHelper` in `~/.kanonak/config.json` (the file configures an
|
|
24
|
+
* environment without setting any variable in it). It becomes an
|
|
25
|
+
* {@link ExecCredentialProvider}.
|
|
26
|
+
* - Otherwise the {@link TokenCredentialProvider}, reading `KANONAK_TOKEN` and
|
|
27
|
+
* `KANONAK_TOKEN_<AUTHORITY>`.
|
|
28
|
+
*
|
|
29
|
+
* A helper together with any `KANONAK_TOKEN*` variable is an error that names
|
|
30
|
+
* both: two credential mechanisms with no rule between them is ambiguous.
|
|
31
|
+
*/
|
|
32
|
+
export declare function credentialProviderFromConfig(opts?: CredentialProviderConfigOptions): CredentialProvider;
|
|
@@ -1,29 +1,40 @@
|
|
|
1
|
-
import type
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
*/
|
|
6
|
-
export declare function parseAuthChallenge(res: Response): AuthChallenge | undefined;
|
|
1
|
+
import { type AuthenticatedFetchFn, type CredentialProvider } from './CredentialProvider.js';
|
|
2
|
+
import { OAuthDiscovery } from './OAuthDiscovery.js';
|
|
3
|
+
/** A cached credential is not attached within this many seconds of its `expiresAt`. */
|
|
4
|
+
export declare const CREDENTIAL_EXPIRY_SKEW_SECONDS = 30;
|
|
7
5
|
export interface CredentialedFetchDeps {
|
|
8
|
-
discovery
|
|
9
|
-
|
|
10
|
-
|
|
6
|
+
/** RFC 9728 discovery, used to learn that an origin is gated before asking it
|
|
7
|
+
* anything (see {@link createCredentialedFetch}). */
|
|
8
|
+
discovery?: Pick<OAuthDiscovery, 'protectedResourceMetadata'>;
|
|
9
|
+
/** The clock, in Unix seconds. */
|
|
10
|
+
nowSeconds?: () => number;
|
|
11
11
|
}
|
|
12
12
|
/**
|
|
13
|
-
* The protocol's consumption transport. Given a {@link CredentialProvider}
|
|
13
|
+
* The protocol's consumption transport (#126). Given a {@link CredentialProvider}:
|
|
14
14
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
15
|
+
* 1. A credential cached for a scope that contains the URL (the narrowest one,
|
|
16
|
+
* not within {@link CREDENTIAL_EXPIRY_SKEW_SECONDS} of expiring) is attached.
|
|
17
|
+
* Headers are attached only inside their scope, so a redirect to another
|
|
18
|
+
* origin, or a lock entry naming another host, never carries them.
|
|
19
|
+
* 2. With none cached, an origin known to be gated is asked pre-emptively, with
|
|
20
|
+
* no challenge. An origin is known to be gated once it has challenged in
|
|
21
|
+
* this process, or when it advertises RFC 9728 metadata (or gates even
|
|
22
|
+
* that). Discovery is what reaches a server that answers anonymous clients
|
|
23
|
+
* with 404 rather than a challenge.
|
|
24
|
+
* 3. A 401 is answered with the server's challenges: the rejected credential,
|
|
25
|
+
* if one was attached, is erased, and the provider is asked once more. A 403
|
|
26
|
+
* whose challenge says `insufficient_scope` is asked once more without an
|
|
27
|
+
* erase, since the credential still serves the rest of its scope. Any other
|
|
28
|
+
* 403 is a refusal, returned to the caller to report.
|
|
29
|
+
* 4. A second rejection, a decline when the server requires a credential, a
|
|
30
|
+
* provider failure, an answer outside the protocol, or a gated resource with
|
|
31
|
+
* no provider configured throws a {@link CredentialError}. Nothing falls back
|
|
32
|
+
* to anonymous.
|
|
24
33
|
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
34
|
+
* Redirects are never followed blindly (`redirect: 'manual'`); the caller sees
|
|
35
|
+
* the 3xx and fetches the new location through this transport again. Provider
|
|
36
|
+
* calls are serialized per origin, and each re-checks the cache first, so a
|
|
37
|
+
* closure fetched concurrently asks once per scope rather than once per URL.
|
|
38
|
+
* Without a provider every request is anonymous, and a gated one fails loudly.
|
|
28
39
|
*/
|
|
29
40
|
export declare function createCredentialedFetch(provider?: CredentialProvider, deps?: CredentialedFetchDeps): AuthenticatedFetchFn;
|
package/dist/auth/index.d.ts
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
export { normalizeHost } from './hosts.js';
|
|
2
2
|
export { OAuthDiscovery, DEFAULT_CONSENT_BINDING_PARAM } from './OAuthDiscovery.js';
|
|
3
|
-
export type { OAuthServerMetadata } from './OAuthDiscovery.js';
|
|
4
|
-
export
|
|
5
|
-
export {
|
|
3
|
+
export type { OAuthServerMetadata, ProtectedResourceMetadata } from './OAuthDiscovery.js';
|
|
4
|
+
export { CredentialError } from './CredentialProvider.js';
|
|
5
|
+
export type { AuthenticatedFetchFn, AuthChallenge, Credential, CredentialAnswer, CredentialEraseRequest, CredentialProvider, CredentialRequest, } from './CredentialProvider.js';
|
|
6
|
+
export { createCredentialedFetch, CREDENTIAL_EXPIRY_SKEW_SECONDS } from './credentialedFetch.js';
|
|
6
7
|
export type { CredentialedFetchDeps } from './credentialedFetch.js';
|
|
7
|
-
export {
|
|
8
|
+
export { parseWwwAuthenticate } from './challenges.js';
|
|
9
|
+
export { parseScope, scopeContains, narrowestScope } from './scope.js';
|
|
10
|
+
export type { ParsedScope } from './scope.js';
|
|
11
|
+
export { CREDENTIAL_HELPER_PROTOCOL_VERSION, checkCredentialAnswer, checkHelperAnswer, checkHelperEraseAnswer, } from './credentialAnswer.js';
|
|
12
|
+
export type { AnswerCheck, AnswerProblem } from './credentialAnswer.js';
|
|
13
|
+
export { TokenCredentialProvider, TOKEN_ENV, tokenEnvKeyFor } from './TokenCredentialProvider.js';
|
|
8
14
|
export type { TokenCredentialProviderOptions } from './TokenCredentialProvider.js';
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Credential scopes (#126). A scope is an `https` origin plus a path prefix,
|
|
3
|
+
* e.g. `https://registry.example.net/example.com` or `https://example.com/`. A
|
|
4
|
+
* credential's headers are attached only to URLs inside its scope, so a
|
|
5
|
+
* redirect to another origin, or a lock entry naming another host, never
|
|
6
|
+
* carries them. The prefix matches whole path segments: `/example.com` covers
|
|
7
|
+
* `/example.com` and `/example.com/core/1.0.0.kan.yml`, never
|
|
8
|
+
* `/example.com.evil/…`.
|
|
9
|
+
*/
|
|
10
|
+
export interface ParsedScope {
|
|
11
|
+
/** `https://host[:port]` */
|
|
12
|
+
origin: string;
|
|
13
|
+
/** The path prefix, normalized to end in `/`. */
|
|
14
|
+
prefix: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Parse and check a scope. Throws when it is not an absolute `https` URL, or
|
|
18
|
+
* carries credentials, a query or a fragment: none of those can bound a path
|
|
19
|
+
* prefix.
|
|
20
|
+
*/
|
|
21
|
+
export declare function parseScope(scope: string): ParsedScope;
|
|
22
|
+
/**
|
|
23
|
+
* Whether `url` lies inside `scope`: same origin, and its path is the scope's
|
|
24
|
+
* path or continues it at a segment boundary. A non-https URL is never inside.
|
|
25
|
+
*/
|
|
26
|
+
export declare function scopeContains(scope: string | ParsedScope, url: string): boolean;
|
|
27
|
+
/** Of the scopes that contain `url`, the most specific (longest prefix), or undefined. */
|
|
28
|
+
export declare function narrowestScope<T extends {
|
|
29
|
+
scope: string;
|
|
30
|
+
}>(entries: Iterable<T>, url: string): T | undefined;
|
package/dist/browser.d.ts
CHANGED
|
@@ -34,9 +34,11 @@ export type { CanonicalInput, CanonicalInputSubject, CanonicalInputStatement, Ca
|
|
|
34
34
|
export { buildOntologyModel } from './introspection/index.js';
|
|
35
35
|
export type { OntologyModel, ClassDef, PropertyDef, ClassRef, TypeRef, BuildOntologyModelOptions, } from './introspection/index.js';
|
|
36
36
|
export { normalizeHost } from './auth/hosts.js';
|
|
37
|
-
export { OAuthDiscovery, createCredentialedFetch,
|
|
38
|
-
export type { AuthenticatedFetchFn, CredentialProvider,
|
|
37
|
+
export { OAuthDiscovery, CredentialError, createCredentialedFetch, CREDENTIAL_EXPIRY_SKEW_SECONDS, parseWwwAuthenticate, parseScope, scopeContains, narrowestScope, CREDENTIAL_HELPER_PROTOCOL_VERSION, checkCredentialAnswer, checkHelperAnswer, checkHelperEraseAnswer, TokenCredentialProvider, TOKEN_ENV, tokenEnvKeyFor, } from './auth/index.js';
|
|
38
|
+
export type { AuthenticatedFetchFn, AuthChallenge, Credential, CredentialAnswer, CredentialEraseRequest, CredentialProvider, CredentialRequest, CredentialedFetchDeps, ParsedScope, AnswerCheck, AnswerProblem, ProtectedResourceMetadata, TokenCredentialProviderOptions, } from './auth/index.js';
|
|
39
39
|
export { kanonakFetch, setKanonakUserAgent, getKanonakUserAgent, KANONAK_USER_AGENT, } from './http/kanonakFetch.js';
|
|
40
|
+
export { KanonakHttpError, httpError, describeResponse, retryAfterSecondsOf } from './http/HttpError.js';
|
|
41
|
+
export type { ProblemDetails } from './http/HttpError.js';
|
|
40
42
|
export { parseLockFile, serializeLockFile, computeIntegrityAsync, INTEGRITY_PREFIX, } from './lock/LockFile.js';
|
|
41
43
|
export type { LockFile, LockEntry } from './lock/LockFile.js';
|
|
42
44
|
export { PackageBuilder, ImportBook, contentAddressedName, sanitizeName, ProducerRepository, EntitlementProducer, EntitlementDeniedError, allowAllPolicy, noopMeter, } from './producer/index.js';
|
package/dist/browser.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{a as
|
|
1
|
+
import{a as ne,b as ae,c as ie,d as We,e as ze,f as Ye,g as Xe,i as Je,j as Qe,k as Ze,l as $e,m as eo,n as oo,o as ro,p as to,q as no,r as ao,s as io}from"./chunk-PNBTIGX7.js";import{a as so}from"./chunk-ED35OFGD.js";import{a as we,b as Me,c as Fe,d as Ue,e as Be,f as Ge,g as qe,h as po,i as lo,j as co,k as mo,l as uo}from"./chunk-ADSDFJUO.js";import{a as c,b as m,c as d,d as u,e as R,f,g as y,h as k,j as x,m as P,o as E,p as C,q as S,r as g,s as I,t as K}from"./chunk-HKQTB65D.js";import{a as se,b as pe,c as le}from"./chunk-GKIX3OD6.js";import{a as ce}from"./chunk-D2X6J77P.js";import"./chunk-SC5M74NM.js";import{A as He,B as Te,C as ve,M as Le,a as me,b as de,c as ue,d as Re,g as fe,h as ye,i as ke,j as xe,l as Pe,m as Ee,n as Ce,o as Se,p as ge,q as Ie,r as Ke,s as he,t as Ae,u as Oe,v as De,w as be,x as je,y as Ne,z as Ve}from"./chunk-O6SOU5Y2.js";import"./chunk-VORVR5BZ.js";import{m as _e}from"./chunk-TYVZXABI.js";import"./chunk-QHABFCRC.js";import{a as p}from"./chunk-6VIUXP4N.js";import{a as ee,g as oe,h as re,k as te}from"./chunk-22DSABP6.js";import{a as l}from"./chunk-7JBKOJB4.js";import"./chunk-PEJALHXK.js";import{a as F,b as U,h as q,i as W,j as z,k as Y,ka as J,l as X,ma as Q}from"./chunk-NUTHBBDZ.js";import{I as Z,J as $,a as h,b as A,c as O,d as D,e as j,g as N,h as V,i as H,j as T,k as v,l as _,m as L,n as w,o as M,u as B,v as G}from"./chunk-D4QGPMRW.js";import{a as b}from"./chunk-FUUTGGJS.js";import"./chunk-F67UH4FD.js";import{c as e,d as o,e as r,f as t,g as n,h as a,i,k as s}from"./chunk-QILEJIFU.js";export{De as AmbiguousReferenceRule,v as BooleanStatement,Fe as CANONICAL_FORM_VERSION,ro as CREDENTIAL_EXPIRY_SKEW_SECONDS,Qe as CREDENTIAL_HELPER_PROTOCOL_VERSION,oe as Carrier,Te as ClassDefinitionRule,Se as ClassHierarchyCycleRule,R as CredentialError,A as DefinedKanonak,ae as EdgeType,D as EmbeddedKanonak,Pe as EmbeddedKanonakTypeRule,L as EmbeddedStatement,mo as EntitlementDeniedError,uo as EntitlementProducer,ie as GraphBuilder,K as HttpKanonakDocumentRepository,E as INTEGRITY_PREFIX,Ge as ImportBook,Ee as ImportExistenceRule,p as InMemoryKanonakDocumentRepository,f as KANONAK_USER_AGENT,h as Kanonak,pe as KanonakDocumentPositions,c as KanonakHttpError,te as KanonakObjectParser,Le as KanonakObjectValidator,l as KanonakParser,b as KanonakUri,ce as KanonakUriBuilder,J as KanonakVocabulary,w as ListStatement,ve as MarkdownLinkRule,M as MarkdownStatement,Ae as NamespaceImportCycleRule,fe as NamespacePrefixRule,ne as NodeType,T as NumberStatement,Je as OAuthDiscovery,je as ObjectPropertyValueValidationRule,ue as OntologyValidationError,me as OntologyValidationResult,Ue as PackageBuilder,po as ProducerRepository,Ne as PropertyDomainRule,ge as PropertyHierarchyCycleRule,Ve as PropertyKindRangeConsistencyRule,se as PropertyMetadata,be as PropertyRangeReferenceRule,Ie as PropertyRangeRequiredRule,ke as PropertyTypeSpecificityRule,P as PublisherConfigResolver,I as PublisherIndex,j as ReferenceKanonak,_ as ReferenceStatement,He as ReservedNameShadowRule,ye as ResourceNamingRule,F as ResourceResolver,Q as ResourceTypeClassifier,W as SHACL_PACKAGE,q as SHACL_PUBLISHER,Y as SH_NODE_SHAPE,X as SH_PROPERTY_SHAPE,z as SH_SHAPE,V as ScalarStatement,N as Statement,H as StringStatement,Ke as SubClassOfReferenceRule,he as SubPropertyOfReferenceRule,O as SubjectKanonak,xe as SubjectKanonakTypeRequiredRule,no as TOKEN_ENV,io as TokenCredentialProvider,U as TypeResolver,Oe as UnresolvedPredicateRule,Ce as UnresolvedReferenceRule,Re as ValidationContext,de as ValidationSeverity,lo as allowAllPolicy,_e as buildOntologyModel,we as canonicalForm,Me as canonicalHash,re as carrierOf,Ze as checkCredentialAnswer,$e as checkHelperAnswer,eo as checkHelperEraseAnswer,e as compareVersions,g as computeIntegrityAsync,Be as contentAddressedName,to as createCredentialedFetch,t as createVersion,d as describeResponse,so as findDerivation,r as formatVersion,k as getKanonakUserAgent,m as httpError,a as isCompatibleVersion,i as isMajorCompatible,x as kanonakFetch,Xe as narrowestScope,co as noopMeter,We as normalizeHost,C as parseLockFile,ze as parseScope,n as parseVersionString,le as parseWithPositions,oo as parseWwwAuthenticate,s as pickHighestDocument,G as referenceUris,u as retryAfterSecondsOf,qe as sanitizeName,Ye as scopeContains,S as serializeLockFile,y as setKanonakUserAgent,$ as subjectAddress,ao as tokenEnvKeyFor,B as typeReferenceUris,Z as valuesOf,o as versionsEqual,ee as walkDefinedKanonaks};
|