@keycardai/eve 0.1.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.
Files changed (52) hide show
  1. package/LICENSE +9 -0
  2. package/README.md +223 -0
  3. package/dist/esm/auth.d.ts +62 -0
  4. package/dist/esm/auth.d.ts.map +1 -0
  5. package/dist/esm/auth.js +165 -0
  6. package/dist/esm/auth.js.map +1 -0
  7. package/dist/esm/config.d.ts +50 -0
  8. package/dist/esm/config.d.ts.map +1 -0
  9. package/dist/esm/config.js +83 -0
  10. package/dist/esm/config.js.map +1 -0
  11. package/dist/esm/connections.d.ts +48 -0
  12. package/dist/esm/connections.d.ts.map +1 -0
  13. package/dist/esm/connections.js +167 -0
  14. package/dist/esm/connections.js.map +1 -0
  15. package/dist/esm/errors.d.ts +78 -0
  16. package/dist/esm/errors.d.ts.map +1 -0
  17. package/dist/esm/errors.js +93 -0
  18. package/dist/esm/errors.js.map +1 -0
  19. package/dist/esm/expiry.d.ts +20 -0
  20. package/dist/esm/expiry.d.ts.map +1 -0
  21. package/dist/esm/expiry.js +49 -0
  22. package/dist/esm/expiry.js.map +1 -0
  23. package/dist/esm/index.d.ts +44 -0
  24. package/dist/esm/index.d.ts.map +1 -0
  25. package/dist/esm/index.js +37 -0
  26. package/dist/esm/index.js.map +1 -0
  27. package/dist/esm/interactive.d.ts +90 -0
  28. package/dist/esm/interactive.d.ts.map +1 -0
  29. package/dist/esm/interactive.js +190 -0
  30. package/dist/esm/interactive.js.map +1 -0
  31. package/dist/esm/package.json +1 -0
  32. package/dist/esm/requireAuth.d.ts +35 -0
  33. package/dist/esm/requireAuth.d.ts.map +1 -0
  34. package/dist/esm/requireAuth.js +26 -0
  35. package/dist/esm/requireAuth.js.map +1 -0
  36. package/dist/esm/subjectTokens.d.ts +50 -0
  37. package/dist/esm/subjectTokens.d.ts.map +1 -0
  38. package/dist/esm/subjectTokens.js +73 -0
  39. package/dist/esm/subjectTokens.js.map +1 -0
  40. package/dist/esm/testing/index.d.ts +10 -0
  41. package/dist/esm/testing/index.d.ts.map +1 -0
  42. package/dist/esm/testing/index.js +9 -0
  43. package/dist/esm/testing/index.js.map +1 -0
  44. package/dist/esm/testing/testUtils.d.ts +51 -0
  45. package/dist/esm/testing/testUtils.d.ts.map +1 -0
  46. package/dist/esm/testing/testUtils.js +90 -0
  47. package/dist/esm/testing/testUtils.js.map +1 -0
  48. package/dist/esm/zoneClient.d.ts +42 -0
  49. package/dist/esm/zoneClient.d.ts.map +1 -0
  50. package/dist/esm/zoneClient.js +45 -0
  51. package/dist/esm/zoneClient.js.map +1 -0
  52. package/package.json +53 -0
@@ -0,0 +1,90 @@
1
+ import { type AuthorizationRedirect, type BeginAuthorizationOptions, type CompleteAuthorizationOptions, type TokenResponse } from "@keycardai/oauth";
2
+ import type { InteractiveAuthorizationDefinition, JsonValue } from "eve/connections";
3
+ /**
4
+ * The state journaled between eve's begin and callback steps.
5
+ *
6
+ * Every field is JSON, so the value satisfies eve's `JsonValue` bound and
7
+ * survives the durable step boundary the park crosses.
8
+ */
9
+ export interface KeycardResumeState {
10
+ readonly state: string;
11
+ readonly codeVerifier: string;
12
+ readonly resources: readonly string[];
13
+ readonly callbackUrl: string;
14
+ readonly [key: string]: JsonValue;
15
+ }
16
+ /** A token this package minted for one principal, plus its advisory expiry. */
17
+ export interface AuthorizedToken {
18
+ readonly token: string;
19
+ readonly expiresAt?: number;
20
+ readonly providerSubject?: string;
21
+ }
22
+ /**
23
+ * Where completed authorizations live between turns.
24
+ *
25
+ * eve caches a resolved bearer for the duration of a step, and reruns
26
+ * `getToken` after that. This store is what makes a second step reuse the
27
+ * grant a user already completed instead of parking the turn again. The
28
+ * default is process-local, so a fresh process re-parks rather than
29
+ * resurrecting a credential from durable state.
30
+ */
31
+ export interface AuthorizedTokenStore {
32
+ get(key: string): AuthorizedToken | undefined;
33
+ set(key: string, token: AuthorizedToken): void;
34
+ delete(key: string): void;
35
+ }
36
+ /** The two web-flow calls this adapter makes, as one seam for tests. */
37
+ export interface WebAppFlow {
38
+ begin(options: BeginAuthorizationOptions): Promise<AuthorizationRedirect>;
39
+ complete(options: CompleteAuthorizationOptions): Promise<TokenResponse>;
40
+ }
41
+ export interface KeycardInteractiveOptions {
42
+ /** The resource URL the authorization is scoped to. */
43
+ resource: string;
44
+ /** Keycard zone URL (issuer). Required unless `flow` is given. */
45
+ zoneUrl?: string;
46
+ /** OAuth client the browser flow runs as. Required unless `flow` is given. */
47
+ clientId?: string;
48
+ /** Client secret, for a confidential client. Public clients omit it. */
49
+ clientSecret?: string;
50
+ /** Scopes requested in the authorization request. */
51
+ requestScopes?: string | readonly string[];
52
+ /** Extra resources authorized alongside `resource`. */
53
+ additionalResources?: readonly string[];
54
+ /** Name used in error messages and eve's authorization events. */
55
+ connectionName?: string;
56
+ /** Store for completed authorizations. Defaults to a process-local store. */
57
+ tokens?: AuthorizedTokenStore;
58
+ /** Web-flow seam. Replaces the zone calls, so tests take no network. */
59
+ flow?: WebAppFlow;
60
+ }
61
+ /**
62
+ * Interactive connection auth backed by the zone's web authorization flow.
63
+ *
64
+ * The three callbacks eve drives:
65
+ *
66
+ * - `getToken` returns a token only when this package holds one for the
67
+ * principal. Otherwise it throws `ConnectionAuthorizationRequiredError`, and
68
+ * eve emits `authorization.required`, runs `startAuthorization` in a durable
69
+ * step, and parks the turn on its own callback webhook.
70
+ * - `startAuthorization` begins the authorization-code flow with PKCE for the
71
+ * connection's resources, against eve's minted callback URL, and returns the
72
+ * challenge URL plus the `state` and verifier as resume state.
73
+ * - `completeAuthorization` redeems the callback against the journaled resume
74
+ * state and hands eve the token.
75
+ *
76
+ * Resuming without a completed authorization cannot yield a credential.
77
+ * `getToken` is the only path that returns a token and it reads the store,
78
+ * which only `completeAuthorization` writes; a denied, mismatched, or failed
79
+ * callback writes nothing. So a resumed turn either finds a real grant or
80
+ * throws `Required` again and parks. eve's own settlement makes that terminal
81
+ * rather than an endless loop: it settles each parked authorization once, and
82
+ * a `Required` thrown after a completed authorization ends the tool call.
83
+ *
84
+ * Discovery is memoized per definition, so the first authorization pays the
85
+ * metadata round trip and later ones do not.
86
+ */
87
+ export declare function interactive(options: KeycardInteractiveOptions): InteractiveAuthorizationDefinition<KeycardResumeState>;
88
+ /** A process-local store that drops entries at their advisory expiry. */
89
+ export declare function memoryAuthorizedTokenStore(): AuthorizedTokenStore;
90
+ //# sourceMappingURL=interactive.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interactive.d.ts","sourceRoot":"","sources":["../../src/interactive.ts"],"names":[],"mappings":"AAAA,OAAO,EAOL,KAAK,qBAAqB,EAC1B,KAAK,yBAAyB,EAC9B,KAAK,4BAA4B,EAEjC,KAAK,aAAa,EACnB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,KAAK,EAEV,kCAAkC,EAClC,SAAS,EAEV,MAAM,iBAAiB,CAAC;AAUzB;;;;;GAKG;AACH,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;IACtC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,EAAE,GAAG,EAAE,MAAM,GAAG,SAAS,CAAC;CACnC;AAED,+EAA+E;AAC/E,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;CACnC;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,oBAAoB;IACnC,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,eAAe,GAAG,SAAS,CAAC;IAC9C,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,eAAe,GAAG,IAAI,CAAC;IAC/C,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED,wEAAwE;AACxE,MAAM,WAAW,UAAU;IACzB,KAAK,CAAC,OAAO,EAAE,yBAAyB,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAAC;IAC1E,QAAQ,CAAC,OAAO,EAAE,4BAA4B,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC;CACzE;AAED,MAAM,WAAW,yBAAyB;IACxC,uDAAuD;IACvD,QAAQ,EAAE,MAAM,CAAC;IACjB,kEAAkE;IAClE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,8EAA8E;IAC9E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,wEAAwE;IACxE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,qDAAqD;IACrD,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAAC;IAC3C,uDAAuD;IACvD,mBAAmB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACxC,kEAAkE;IAClE,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,6EAA6E;IAC7E,MAAM,CAAC,EAAE,oBAAoB,CAAC;IAC9B,wEAAwE;IACxE,IAAI,CAAC,EAAE,UAAU,CAAC;CACnB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,WAAW,CACzB,OAAO,EAAE,yBAAyB,GACjC,kCAAkC,CAAC,kBAAkB,CAAC,CAiGxD;AAED,yEAAyE;AACzE,wBAAgB,0BAA0B,IAAI,oBAAoB,CAmBjE"}
@@ -0,0 +1,190 @@
1
+ import { AuthProviderConfigurationError, AuthorizationDeniedError, beginAuthorization, completeAuthorization, fetchAuthorizationServerMetadata, StateMismatchError, } from "@keycardai/oauth";
2
+ import { expiresAt } from "./config.js";
3
+ import { AuthorizationFailedError, AuthorizationRequiredError, FailureReason, } from "./errors.js";
4
+ import { principalKey } from "./subjectTokens.js";
5
+ /**
6
+ * Interactive connection auth backed by the zone's web authorization flow.
7
+ *
8
+ * The three callbacks eve drives:
9
+ *
10
+ * - `getToken` returns a token only when this package holds one for the
11
+ * principal. Otherwise it throws `ConnectionAuthorizationRequiredError`, and
12
+ * eve emits `authorization.required`, runs `startAuthorization` in a durable
13
+ * step, and parks the turn on its own callback webhook.
14
+ * - `startAuthorization` begins the authorization-code flow with PKCE for the
15
+ * connection's resources, against eve's minted callback URL, and returns the
16
+ * challenge URL plus the `state` and verifier as resume state.
17
+ * - `completeAuthorization` redeems the callback against the journaled resume
18
+ * state and hands eve the token.
19
+ *
20
+ * Resuming without a completed authorization cannot yield a credential.
21
+ * `getToken` is the only path that returns a token and it reads the store,
22
+ * which only `completeAuthorization` writes; a denied, mismatched, or failed
23
+ * callback writes nothing. So a resumed turn either finds a real grant or
24
+ * throws `Required` again and parks. eve's own settlement makes that terminal
25
+ * rather than an endless loop: it settles each parked authorization once, and
26
+ * a `Required` thrown after a completed authorization ends the tool call.
27
+ *
28
+ * Discovery is memoized per definition, so the first authorization pays the
29
+ * metadata round trip and later ones do not.
30
+ */
31
+ export function interactive(options) {
32
+ if (!options.resource || !options.resource.trim()) {
33
+ throw new AuthProviderConfigurationError("interactive requires a resource URL");
34
+ }
35
+ if (!options.flow && (!options.zoneUrl || !options.clientId)) {
36
+ throw new AuthProviderConfigurationError("interactive requires zoneUrl and clientId, or an injected flow");
37
+ }
38
+ const connectionName = options.connectionName ?? options.resource;
39
+ const tokens = options.tokens ?? memoryAuthorizedTokenStore();
40
+ const flow = options.flow ?? zoneWebAppFlow(options.zoneUrl);
41
+ const clientId = options.clientId ?? "";
42
+ const resources = [options.resource, ...(options.additionalResources ?? [])];
43
+ const scopes = splitScopes(options.requestScopes);
44
+ return {
45
+ principalType: "user",
46
+ displayName: connectionName,
47
+ async getToken({ principal }) {
48
+ const key = storeKey(principal, connectionName);
49
+ const held = tokens.get(key);
50
+ if (!held) {
51
+ throw new AuthorizationRequiredError(connectionName);
52
+ }
53
+ return toTokenResult(held);
54
+ },
55
+ async startAuthorization({ principal, callbackUrl }) {
56
+ requireUser(principal, connectionName);
57
+ const redirect = await flow.begin({
58
+ clientId,
59
+ redirectUri: callbackUrl,
60
+ resources,
61
+ ...(scopes.length > 0 ? { scopes } : {}),
62
+ });
63
+ return {
64
+ challenge: { url: redirect.url, displayName: connectionName },
65
+ resume: {
66
+ state: redirect.state,
67
+ codeVerifier: redirect.codeVerifier,
68
+ resources: redirect.resources,
69
+ callbackUrl,
70
+ },
71
+ };
72
+ },
73
+ async completeAuthorization({ principal, callbackUrl, resume, callback }) {
74
+ requireUser(principal, connectionName);
75
+ if (!resume) {
76
+ throw new AuthorizationFailedError(connectionName, {
77
+ message: "The authorization callback arrived without the state journaled at the " +
78
+ "begin step, so it cannot be redeemed.",
79
+ reason: FailureReason.INVALID_CALLBACK,
80
+ retryable: false,
81
+ });
82
+ }
83
+ let response;
84
+ try {
85
+ response = await flow.complete({
86
+ callbackParams: { ...callback.params },
87
+ state: resume.state,
88
+ codeVerifier: resume.codeVerifier,
89
+ clientId,
90
+ redirectUri: resume.callbackUrl || callbackUrl,
91
+ ...(options.clientSecret ? { clientSecret: options.clientSecret } : {}),
92
+ });
93
+ }
94
+ catch (cause) {
95
+ throw new AuthorizationFailedError(connectionName, {
96
+ message: cause instanceof Error ? cause.message : "Authorization failed",
97
+ reason: cause instanceof AuthorizationDeniedError
98
+ ? FailureReason.ACCESS_DENIED
99
+ : cause instanceof StateMismatchError
100
+ ? FailureReason.INVALID_CALLBACK
101
+ : FailureReason.ACQUISITION_FAILED,
102
+ retryable: false,
103
+ });
104
+ }
105
+ const expiry = expiresAt(response.expiresIn);
106
+ const token = {
107
+ token: response.accessToken,
108
+ ...(expiry !== undefined ? { expiresAt: expiry } : {}),
109
+ };
110
+ tokens.set(storeKey(principal, connectionName), token);
111
+ return toTokenResult(token);
112
+ },
113
+ /**
114
+ * eve calls this after a rejected bearer, so the next `getToken` parks the
115
+ * turn for a fresh grant instead of handing back the same dead token.
116
+ */
117
+ evict({ principal }) {
118
+ tokens.delete(storeKey(principal, connectionName));
119
+ },
120
+ };
121
+ }
122
+ /** A process-local store that drops entries at their advisory expiry. */
123
+ export function memoryAuthorizedTokenStore() {
124
+ const tokens = new Map();
125
+ return {
126
+ get(key) {
127
+ const held = tokens.get(key);
128
+ if (!held)
129
+ return undefined;
130
+ if (held.expiresAt !== undefined && held.expiresAt <= Date.now()) {
131
+ tokens.delete(key);
132
+ return undefined;
133
+ }
134
+ return held;
135
+ },
136
+ set(key, token) {
137
+ tokens.set(key, token);
138
+ },
139
+ delete(key) {
140
+ tokens.delete(key);
141
+ },
142
+ };
143
+ }
144
+ /** The default flow: the zone's web-app endpoints, with discovery memoized. */
145
+ function zoneWebAppFlow(issuer) {
146
+ let metadata;
147
+ const discover = () => {
148
+ if (!metadata)
149
+ metadata = fetchAuthorizationServerMetadata(issuer);
150
+ return metadata;
151
+ };
152
+ return {
153
+ async begin(options) {
154
+ return beginAuthorization(issuer, { ...options, metadata: await discover() });
155
+ },
156
+ async complete(options) {
157
+ return completeAuthorization(issuer, { ...options, metadata: await discover() });
158
+ },
159
+ };
160
+ }
161
+ function storeKey(principal, connectionName) {
162
+ if (principal.type !== "user")
163
+ return `${connectionName}|app`;
164
+ return `${connectionName}|${principalKey(principal)}`;
165
+ }
166
+ function toTokenResult(token) {
167
+ return {
168
+ token: token.token,
169
+ ...(token.expiresAt !== undefined ? { expiresAt: token.expiresAt } : {}),
170
+ ...(token.providerSubject !== undefined
171
+ ? { providerSubject: token.providerSubject }
172
+ : {}),
173
+ };
174
+ }
175
+ function requireUser(principal, connectionName) {
176
+ if (principal.type === "user")
177
+ return;
178
+ throw new AuthorizationFailedError(connectionName, {
179
+ message: "Interactive authorization needs a user principal, and the current turn has " +
180
+ "no authenticated user.",
181
+ reason: FailureReason.PRINCIPAL_REQUIRED,
182
+ retryable: false,
183
+ });
184
+ }
185
+ function splitScopes(scopes) {
186
+ if (scopes === undefined)
187
+ return [];
188
+ return Array.isArray(scopes) ? [...scopes] : scopes.split(" ").filter(Boolean);
189
+ }
190
+ //# sourceMappingURL=interactive.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interactive.js","sourceRoot":"","sources":["../../src/interactive.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,8BAA8B,EAC9B,wBAAwB,EACxB,kBAAkB,EAClB,qBAAqB,EACrB,gCAAgC,EAChC,kBAAkB,GAMnB,MAAM,kBAAkB,CAAC;AAQ1B,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EACL,wBAAwB,EACxB,0BAA0B,EAC1B,aAAa,GACd,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAiElD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,UAAU,WAAW,CACzB,OAAkC;IAElC,IAAI,CAAC,OAAO,CAAC,QAAQ,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,CAAC;QAClD,MAAM,IAAI,8BAA8B,CAAC,qCAAqC,CAAC,CAAC;IAClF,CAAC;IACD,IAAI,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC,CAAC,OAAO,CAAC,OAAO,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC7D,MAAM,IAAI,8BAA8B,CACtC,gEAAgE,CACjE,CAAC;IACJ,CAAC;IAED,MAAM,cAAc,GAAG,OAAO,CAAC,cAAc,IAAI,OAAO,CAAC,QAAQ,CAAC;IAClE,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,0BAA0B,EAAE,CAAC;IAC9D,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,cAAc,CAAC,OAAO,CAAC,OAAQ,CAAC,CAAC;IAC9D,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,EAAE,CAAC;IACxC,MAAM,SAAS,GAAG,CAAC,OAAO,CAAC,QAAQ,EAAE,GAAG,CAAC,OAAO,CAAC,mBAAmB,IAAI,EAAE,CAAC,CAAC,CAAC;IAC7E,MAAM,MAAM,GAAG,WAAW,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC;IAElD,OAAO;QACL,aAAa,EAAE,MAAM;QACrB,WAAW,EAAE,cAAc;QAC3B,KAAK,CAAC,QAAQ,CAAC,EAAE,SAAS,EAAE;YAC1B,MAAM,GAAG,GAAG,QAAQ,CAAC,SAAS,EAAE,cAAc,CAAC,CAAC;YAChD,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YAC7B,IAAI,CAAC,IAAI,EAAE,CAAC;gBACV,MAAM,IAAI,0BAA0B,CAAC,cAAc,CAAC,CAAC;YACvD,CAAC;YACD,OAAO,aAAa,CAAC,IAAI,CAAC,CAAC;QAC7B,CAAC;QACD,KAAK,CAAC,kBAAkB,CAAC,EAAE,SAAS,EAAE,WAAW,EAAE;YACjD,WAAW,CAAC,SAAS,EAAE,cAAc,CAAC,CAAC;YACvC,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC;gBAChC,QAAQ;gBACR,WAAW,EAAE,WAAW;gBACxB,SAAS;gBACT,GAAG,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aACzC,CAAC,CAAC;YACH,OAAO;gBACL,SAAS,EAAE,EAAE,GAAG,EAAE,QAAQ,CAAC,GAAG,EAAE,WAAW,EAAE,cAAc,EAAE;gBAC7D,MAAM,EAAE;oBACN,KAAK,EAAE,QAAQ,CAAC,KAAK;oBACrB,YAAY,EAAE,QAAQ,CAAC,YAAY;oBACnC,SAAS,EAAE,QAAQ,CAAC,SAAS;oBAC7B,WAAW;iBACZ;aACF,CAAC;QACJ,CAAC;QACD,KAAK,CAAC,qBAAqB,CAAC,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE;YACtE,WAAW,CAAC,SAAS,EAAE,cAAc,CAAC,CAAC;YACvC,IAAI,CAAC,MAAM,EAAE,CAAC;gBACZ,MAAM,IAAI,wBAAwB,CAAC,cAAc,EAAE;oBACjD,OAAO,EACL,wEAAwE;wBACxE,uCAAuC;oBACzC,MAAM,EAAE,aAAa,CAAC,gBAAgB;oBACtC,SAAS,EAAE,KAAK;iBACjB,CAAC,CAAC;YACL,CAAC;YAED,IAAI,QAAuB,CAAC;YAC5B,IAAI,CAAC;gBACH,QAAQ,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC;oBAC7B,cAAc,EAAE,EAAE,GAAG,QAAQ,CAAC,MAAM,EAAE;oBACtC,KAAK,EAAE,MAAM,CAAC,KAAK;oBACnB,YAAY,EAAE,MAAM,CAAC,YAAY;oBACjC,QAAQ;oBACR,WAAW,EAAE,MAAM,CAAC,WAAW,IAAI,WAAW;oBAC9C,GAAG,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,OAAO,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;iBACxE,CAAC,CAAC;YACL,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,MAAM,IAAI,wBAAwB,CAAC,cAAc,EAAE;oBACjD,OAAO,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,sBAAsB;oBACxE,MAAM,EACJ,KAAK,YAAY,wBAAwB;wBACvC,CAAC,CAAC,aAAa,CAAC,aAAa;wBAC7B,CAAC,CAAC,KAAK,YAAY,kBAAkB;4BACnC,CAAC,CAAC,aAAa,CAAC,gBAAgB;4BAChC,CAAC,CAAC,aAAa,CAAC,kBAAkB;oBACxC,SAAS,EAAE,KAAK;iBACjB,CAAC,CAAC;YACL,CAAC;YAED,MAAM,MAAM,GAAG,SAAS,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;YAC7C,MAAM,KAAK,GAAoB;gBAC7B,KAAK,EAAE,QAAQ,CAAC,WAAW;gBAC3B,GAAG,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aACvD,CAAC;YACF,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,SAAS,EAAE,cAAc,CAAC,EAAE,KAAK,CAAC,CAAC;YACvD,OAAO,aAAa,CAAC,KAAK,CAAC,CAAC;QAC9B,CAAC;QACD;;;WAGG;QACH,KAAK,CAAC,EAAE,SAAS,EAAE;YACjB,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,EAAE,cAAc,CAAC,CAAC,CAAC;QACrD,CAAC;KACF,CAAC;AACJ,CAAC;AAED,yEAAyE;AACzE,MAAM,UAAU,0BAA0B;IACxC,MAAM,MAAM,GAAG,IAAI,GAAG,EAA2B,CAAC;IAClD,OAAO;QACL,GAAG,CAAC,GAAG;YACL,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YAC7B,IAAI,CAAC,IAAI;gBAAE,OAAO,SAAS,CAAC;YAC5B,IAAI,IAAI,CAAC,SAAS,KAAK,SAAS,IAAI,IAAI,CAAC,SAAS,IAAI,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;gBACjE,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;gBACnB,OAAO,SAAS,CAAC;YACnB,CAAC;YACD,OAAO,IAAI,CAAC;QACd,CAAC;QACD,GAAG,CAAC,GAAG,EAAE,KAAK;YACZ,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QACzB,CAAC;QACD,MAAM,CAAC,GAAG;YACR,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACrB,CAAC;KACF,CAAC;AACJ,CAAC;AAED,+EAA+E;AAC/E,SAAS,cAAc,CAAC,MAAc;IACpC,IAAI,QAA+D,CAAC;IACpE,MAAM,QAAQ,GAAG,GAA8C,EAAE;QAC/D,IAAI,CAAC,QAAQ;YAAE,QAAQ,GAAG,gCAAgC,CAAC,MAAM,CAAC,CAAC;QACnE,OAAO,QAAQ,CAAC;IAClB,CAAC,CAAC;IACF,OAAO;QACL,KAAK,CAAC,KAAK,CAAC,OAAO;YACjB,OAAO,kBAAkB,CAAC,MAAM,EAAE,EAAE,GAAG,OAAO,EAAE,QAAQ,EAAE,MAAM,QAAQ,EAAE,EAAE,CAAC,CAAC;QAChF,CAAC;QACD,KAAK,CAAC,QAAQ,CAAC,OAAO;YACpB,OAAO,qBAAqB,CAAC,MAAM,EAAE,EAAE,GAAG,OAAO,EAAE,QAAQ,EAAE,MAAM,QAAQ,EAAE,EAAE,CAAC,CAAC;QACnF,CAAC;KACF,CAAC;AACJ,CAAC;AAED,SAAS,QAAQ,CAAC,SAA8B,EAAE,cAAsB;IACtE,IAAI,SAAS,CAAC,IAAI,KAAK,MAAM;QAAE,OAAO,GAAG,cAAc,MAAM,CAAC;IAC9D,OAAO,GAAG,cAAc,IAAI,YAAY,CAAC,SAAS,CAAC,EAAE,CAAC;AACxD,CAAC;AAED,SAAS,aAAa,CAAC,KAAsB;IAC3C,OAAO;QACL,KAAK,EAAE,KAAK,CAAC,KAAK;QAClB,GAAG,CAAC,KAAK,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,KAAK,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACxE,GAAG,CAAC,KAAK,CAAC,eAAe,KAAK,SAAS;YACrC,CAAC,CAAC,EAAE,eAAe,EAAE,KAAK,CAAC,eAAe,EAAE;YAC5C,CAAC,CAAC,EAAE,CAAC;KACR,CAAC;AACJ,CAAC;AAED,SAAS,WAAW,CAAC,SAA8B,EAAE,cAAsB;IACzE,IAAI,SAAS,CAAC,IAAI,KAAK,MAAM;QAAE,OAAO;IACtC,MAAM,IAAI,wBAAwB,CAAC,cAAc,EAAE;QACjD,OAAO,EACL,6EAA6E;YAC7E,wBAAwB;QAC1B,MAAM,EAAE,aAAa,CAAC,kBAAkB;QACxC,SAAS,EAAE,KAAK;KACjB,CAAC,CAAC;AACL,CAAC;AAED,SAAS,WAAW,CAAC,MAA8C;IACjE,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,EAAE,CAAC;IACpC,OAAO,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAAE,MAAiB,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;AAC7F,CAAC"}
@@ -0,0 +1 @@
1
+ {"type": "module"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * The part of eve's tool context this helper needs.
3
+ *
4
+ * Declared structurally rather than imported so nothing here loads eve at
5
+ * runtime. `ctx.requireAuth` never returns: it aborts the tool call and hands
6
+ * the connection's authorization strategy back to the runtime.
7
+ */
8
+ export interface RequireAuthContext<Provider> {
9
+ requireAuth(provider: Provider, options?: {
10
+ readonly reason?: string;
11
+ }): never;
12
+ }
13
+ /**
14
+ * Maps a provider's rejection of the current credential onto
15
+ * `ctx.requireAuth`.
16
+ *
17
+ * A token that verified when it was minted can still be refused downstream
18
+ * after a revocation or a scope change. eve treats `requireAuth` as the signal
19
+ * to drop its cached bearer, call the strategy's `evict`, and re-run
20
+ * authorization, so an authored tool wrapping a `fetch` only needs to forward
21
+ * the status:
22
+ *
23
+ * ```ts
24
+ * const response = await fetch(url, { headers });
25
+ * requireAuthOnUnauthorized(response, ctx, auth);
26
+ * ```
27
+ *
28
+ * Returns for every other status, so the tool keeps handling its own errors.
29
+ */
30
+ export declare function requireAuthOnUnauthorized<Provider>(response: {
31
+ readonly status: number;
32
+ }, ctx: RequireAuthContext<Provider>, provider: Provider, options?: {
33
+ readonly reason?: string;
34
+ }): void;
35
+ //# sourceMappingURL=requireAuth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"requireAuth.d.ts","sourceRoot":"","sources":["../../src/requireAuth.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,WAAW,kBAAkB,CAAC,QAAQ;IAC1C,WAAW,CAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,KAAK,CAAC;CAChF;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,yBAAyB,CAAC,QAAQ,EAChD,QAAQ,EAAE;IAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,EACrC,GAAG,EAAE,kBAAkB,CAAC,QAAQ,CAAC,EACjC,QAAQ,EAAE,QAAQ,EAClB,OAAO,CAAC,EAAE;IAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GACrC,IAAI,CAON"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Maps a provider's rejection of the current credential onto
3
+ * `ctx.requireAuth`.
4
+ *
5
+ * A token that verified when it was minted can still be refused downstream
6
+ * after a revocation or a scope change. eve treats `requireAuth` as the signal
7
+ * to drop its cached bearer, call the strategy's `evict`, and re-run
8
+ * authorization, so an authored tool wrapping a `fetch` only needs to forward
9
+ * the status:
10
+ *
11
+ * ```ts
12
+ * const response = await fetch(url, { headers });
13
+ * requireAuthOnUnauthorized(response, ctx, auth);
14
+ * ```
15
+ *
16
+ * Returns for every other status, so the tool keeps handling its own errors.
17
+ */
18
+ export function requireAuthOnUnauthorized(response, ctx, provider, options) {
19
+ if (response.status !== 401 && response.status !== 403)
20
+ return;
21
+ ctx.requireAuth(provider, {
22
+ reason: options?.reason ??
23
+ `The provider rejected the connection credential with ${response.status}.`,
24
+ });
25
+ }
26
+ //# sourceMappingURL=requireAuth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"requireAuth.js","sourceRoot":"","sources":["../../src/requireAuth.ts"],"names":[],"mappings":"AAWA;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,yBAAyB,CACvC,QAAqC,EACrC,GAAiC,EACjC,QAAkB,EAClB,OAAsC;IAEtC,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG;QAAE,OAAO;IAC/D,GAAG,CAAC,WAAW,CAAC,QAAQ,EAAE;QACxB,MAAM,EACJ,OAAO,EAAE,MAAM;YACf,wDAAwD,QAAQ,CAAC,MAAM,GAAG;KAC7E,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,50 @@
1
+ import type { ConnectionPrincipal } from "eve/connections";
2
+ import type { SessionAuthContext } from "eve/context";
3
+ /**
4
+ * The attribute name carrying the inbound bearer when retention is
5
+ * `"attributes"`.
6
+ *
7
+ * eve copies `SessionAuthContext.attributes` onto the connection principal, so
8
+ * an attribute is the only channel eve itself offers between the auth walk and
9
+ * a connection's `getToken`. It is also durable session state, which is why
10
+ * this is opt-in rather than the default.
11
+ */
12
+ export declare const SUBJECT_TOKEN_ATTRIBUTE = "keycard_subject_token";
13
+ /**
14
+ * Where the verified inbound bearer lives between the auth walk and the
15
+ * exchange in `onBehalfOf`.
16
+ *
17
+ * Two implementations ship: {@link memorySubjectTokenStore} (the default,
18
+ * process-local, nothing at rest) and the `"attributes"` retention mode, which
19
+ * puts the token on the eve session instead. Both keep the token out of model
20
+ * input: neither is a tool argument, a connection schema field, or a message.
21
+ */
22
+ export interface SubjectTokenStore {
23
+ get(key: string): string | undefined;
24
+ set(key: string, token: string): void;
25
+ delete(key: string): void;
26
+ }
27
+ /**
28
+ * A process-local store that drops entries once their JWT `exp` has passed.
29
+ *
30
+ * Nothing here survives a restart, so a turn that resumes in a fresh process
31
+ * finds no subject token and fails closed with
32
+ * `subject_token_unavailable` rather than reaching for the agent's authority.
33
+ */
34
+ export declare function memorySubjectTokenStore(): SubjectTokenStore;
35
+ /** The default store, shared by `keycardAuth` and the connection factories. */
36
+ export declare const defaultSubjectTokenStore: SubjectTokenStore;
37
+ /**
38
+ * The store key for one principal.
39
+ *
40
+ * Derived from the fields eve projects from the session's current auth onto
41
+ * the connection principal, so the auth walk and `getToken` compute the same
42
+ * key without sharing any other state.
43
+ */
44
+ export declare function principalKey(identity: Pick<SessionAuthContext, "authenticator" | "issuer" | "principalId"> | {
45
+ readonly id: string;
46
+ readonly issuer?: string;
47
+ }): string;
48
+ /** The retained subject token for a connection principal, if any. */
49
+ export declare function readSubjectToken(principal: ConnectionPrincipal, store: SubjectTokenStore): string | undefined;
50
+ //# sourceMappingURL=subjectTokens.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"subjectTokens.d.ts","sourceRoot":"","sources":["../../src/subjectTokens.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAC3D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAItD;;;;;;;;GAQG;AACH,eAAO,MAAM,uBAAuB,0BAA0B,CAAC;AAE/D;;;;;;;;GAQG;AACH,MAAM,WAAW,iBAAiB;IAChC,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;IACrC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACtC,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,IAAI,iBAAiB,CAqB3D;AAED,+EAA+E;AAC/E,eAAO,MAAM,wBAAwB,EAAE,iBAA6C,CAAC;AAErF;;;;;;GAMG;AACH,wBAAgB,YAAY,CAC1B,QAAQ,EACJ,IAAI,CAAC,kBAAkB,EAAE,eAAe,GAAG,QAAQ,GAAG,aAAa,CAAC,GACpE;IAAE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GACpD,MAAM,CAKR;AAED,qEAAqE;AACrE,wBAAgB,gBAAgB,CAC9B,SAAS,EAAE,mBAAmB,EAC9B,KAAK,EAAE,iBAAiB,GACvB,MAAM,GAAG,SAAS,CAKpB"}
@@ -0,0 +1,73 @@
1
+ import { decodeClaims } from "./expiry.js";
2
+ /**
3
+ * The attribute name carrying the inbound bearer when retention is
4
+ * `"attributes"`.
5
+ *
6
+ * eve copies `SessionAuthContext.attributes` onto the connection principal, so
7
+ * an attribute is the only channel eve itself offers between the auth walk and
8
+ * a connection's `getToken`. It is also durable session state, which is why
9
+ * this is opt-in rather than the default.
10
+ */
11
+ export const SUBJECT_TOKEN_ATTRIBUTE = "keycard_subject_token";
12
+ /**
13
+ * A process-local store that drops entries once their JWT `exp` has passed.
14
+ *
15
+ * Nothing here survives a restart, so a turn that resumes in a fresh process
16
+ * finds no subject token and fails closed with
17
+ * `subject_token_unavailable` rather than reaching for the agent's authority.
18
+ */
19
+ export function memorySubjectTokenStore() {
20
+ const tokens = new Map();
21
+ return {
22
+ get(key) {
23
+ return tokens.get(key);
24
+ },
25
+ /**
26
+ * Expiry is not hidden from readers: `onBehalfOf` distinguishes an expired
27
+ * subject token from a missing one, and only the first routes to sign-in.
28
+ * Pruning here keeps the map from growing without bound.
29
+ */
30
+ set(key, token) {
31
+ for (const [existing, value] of tokens) {
32
+ if (tokenExpired(value))
33
+ tokens.delete(existing);
34
+ }
35
+ tokens.set(key, token);
36
+ },
37
+ delete(key) {
38
+ tokens.delete(key);
39
+ },
40
+ };
41
+ }
42
+ /** The default store, shared by `keycardAuth` and the connection factories. */
43
+ export const defaultSubjectTokenStore = memorySubjectTokenStore();
44
+ /**
45
+ * The store key for one principal.
46
+ *
47
+ * Derived from the fields eve projects from the session's current auth onto
48
+ * the connection principal, so the auth walk and `getToken` compute the same
49
+ * key without sharing any other state.
50
+ */
51
+ export function principalKey(identity) {
52
+ if ("id" in identity) {
53
+ return `${identity.issuer ?? ""}|${identity.id}`;
54
+ }
55
+ return `${identity.issuer ?? identity.authenticator}|${identity.principalId}`;
56
+ }
57
+ /** The retained subject token for a connection principal, if any. */
58
+ export function readSubjectToken(principal, store) {
59
+ if (principal.type !== "user")
60
+ return undefined;
61
+ const attribute = principal.attributes?.[SUBJECT_TOKEN_ATTRIBUTE];
62
+ if (typeof attribute === "string" && attribute)
63
+ return attribute;
64
+ return store.get(principalKey(principal));
65
+ }
66
+ function tokenExpired(token) {
67
+ const claims = decodeClaims(token);
68
+ if (claims === null)
69
+ return false;
70
+ const exp = claims.exp;
71
+ return typeof exp === "number" && Number.isFinite(exp) && exp <= Date.now() / 1000;
72
+ }
73
+ //# sourceMappingURL=subjectTokens.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"subjectTokens.js","sourceRoot":"","sources":["../../src/subjectTokens.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,uBAAuB,CAAC;AAiB/D;;;;;;GAMG;AACH,MAAM,UAAU,uBAAuB;IACrC,MAAM,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAC;IACzC,OAAO;QACL,GAAG,CAAC,GAAG;YACL,OAAO,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACzB,CAAC;QACD;;;;WAIG;QACH,GAAG,CAAC,GAAG,EAAE,KAAK;YACZ,KAAK,MAAM,CAAC,QAAQ,EAAE,KAAK,CAAC,IAAI,MAAM,EAAE,CAAC;gBACvC,IAAI,YAAY,CAAC,KAAK,CAAC;oBAAE,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;YACnD,CAAC;YACD,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QACzB,CAAC;QACD,MAAM,CAAC,GAAG;YACR,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACrB,CAAC;KACF,CAAC;AACJ,CAAC;AAED,+EAA+E;AAC/E,MAAM,CAAC,MAAM,wBAAwB,GAAsB,uBAAuB,EAAE,CAAC;AAErF;;;;;;GAMG;AACH,MAAM,UAAU,YAAY,CAC1B,QAEqD;IAErD,IAAI,IAAI,IAAI,QAAQ,EAAE,CAAC;QACrB,OAAO,GAAG,QAAQ,CAAC,MAAM,IAAI,EAAE,IAAI,QAAQ,CAAC,EAAE,EAAE,CAAC;IACnD,CAAC;IACD,OAAO,GAAG,QAAQ,CAAC,MAAM,IAAI,QAAQ,CAAC,aAAa,IAAI,QAAQ,CAAC,WAAW,EAAE,CAAC;AAChF,CAAC;AAED,qEAAqE;AACrE,MAAM,UAAU,gBAAgB,CAC9B,SAA8B,EAC9B,KAAwB;IAExB,IAAI,SAAS,CAAC,IAAI,KAAK,MAAM;QAAE,OAAO,SAAS,CAAC;IAChD,MAAM,SAAS,GAAG,SAAS,CAAC,UAAU,EAAE,CAAC,uBAAuB,CAAC,CAAC;IAClE,IAAI,OAAO,SAAS,KAAK,QAAQ,IAAI,SAAS;QAAE,OAAO,SAAS,CAAC;IACjE,OAAO,KAAK,CAAC,GAAG,CAAC,YAAY,CAAC,SAAS,CAAC,CAAC,CAAC;AAC5C,CAAC;AAED,SAAS,YAAY,CAAC,KAAa;IACjC,MAAM,MAAM,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC;IACnC,IAAI,MAAM,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IAClC,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC;IACvB,OAAO,OAAO,GAAG,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,GAAG,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC;AACrF,CAAC"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Offline test helpers.
3
+ *
4
+ * Nothing here touches the network: a fake zone client records the requests a
5
+ * factory made, and the JWT builders produce decode-only fixtures. Import from
6
+ * `@keycardai/eve/testing`.
7
+ */
8
+ export { appPrincipal, bearerRequest, connectionContext, expiredJwt, fakeVerifier, fakeZoneClient, sessionAuthContext, unsignedJwt, userPrincipal, validJwt, } from "./testUtils.js";
9
+ export type { FakeZoneClient, FakeZoneClientOptions, ZoneCalls } from "./testUtils.js";
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/testing/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EACL,YAAY,EACZ,aAAa,EACb,iBAAiB,EACjB,UAAU,EACV,YAAY,EACZ,cAAc,EACd,kBAAkB,EAClB,WAAW,EACX,aAAa,EACb,QAAQ,GACT,MAAM,gBAAgB,CAAC;AACxB,YAAY,EAAE,cAAc,EAAE,qBAAqB,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Offline test helpers.
3
+ *
4
+ * Nothing here touches the network: a fake zone client records the requests a
5
+ * factory made, and the JWT builders produce decode-only fixtures. Import from
6
+ * `@keycardai/eve/testing`.
7
+ */
8
+ export { appPrincipal, bearerRequest, connectionContext, expiredJwt, fakeVerifier, fakeZoneClient, sessionAuthContext, unsignedJwt, userPrincipal, validJwt, } from "./testUtils.js";
9
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/testing/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EACL,YAAY,EACZ,aAAa,EACb,iBAAiB,EACjB,UAAU,EACV,YAAY,EACZ,cAAc,EACd,kBAAkB,EAClB,WAAW,EACX,aAAa,EACb,QAAQ,GACT,MAAM,gBAAgB,CAAC"}
@@ -0,0 +1,51 @@
1
+ import type { ClientCredentialsRequest, ImpersonateRequest, JWTClaims, TokenExchangeRequest } from "@keycardai/oauth";
2
+ import type { ConnectionPrincipal } from "eve/connections";
3
+ import type { SessionAuthContext } from "eve/context";
4
+ import type { ZoneClient } from "../zoneClient.js";
5
+ /** Every call a fake zone client saw, in order. */
6
+ export interface ZoneCalls {
7
+ readonly exchanges: TokenExchangeRequest[];
8
+ readonly impersonations: ImpersonateRequest[];
9
+ readonly clientCredentials: ClientCredentialsRequest[];
10
+ }
11
+ export interface FakeZoneClientOptions {
12
+ /** Fails only these resources, leaving the rest to succeed. */
13
+ failResources?: Readonly<Record<string, Error>>;
14
+ /** Fails every call, whatever the resource. */
15
+ fail?: Error;
16
+ /** Advisory lifetime on issued tokens, in seconds. */
17
+ expiresIn?: number;
18
+ }
19
+ /** A fake zone client with recorded calls and no network. */
20
+ export interface FakeZoneClient extends ZoneClient {
21
+ readonly calls: ZoneCalls;
22
+ }
23
+ /**
24
+ * A zone client that mints deterministic tokens and records what it was asked
25
+ * for.
26
+ *
27
+ * Covers the three cases the contract requires from a testing seam: successful
28
+ * acquisition, failure of one resource, and failure of every call.
29
+ */
30
+ export declare function fakeZoneClient(options?: FakeZoneClientOptions): FakeZoneClient;
31
+ /** An unsigned JWT with the given claims, for decode-only paths. */
32
+ export declare function unsignedJwt(claims: Record<string, unknown>): string;
33
+ /** A JWT that expired `secondsAgo` seconds ago. */
34
+ export declare function expiredJwt(secondsAgo?: number): string;
35
+ /** A JWT that is valid for another `secondsAhead` seconds. */
36
+ export declare function validJwt(secondsAhead?: number, claims?: Record<string, unknown>): string;
37
+ /** A request carrying a bearer token. */
38
+ export declare function bearerRequest(token: string, url?: string): Request;
39
+ /** A verification seam that accepts one token and rejects everything else. */
40
+ export declare function fakeVerifier(accepted: string, claims: JWTClaims): (token: string) => Promise<JWTClaims>;
41
+ /** A user connection principal. */
42
+ export declare function userPrincipal(id?: string, issuer?: string, attributes?: Readonly<Record<string, string | readonly string[]>>): ConnectionPrincipal;
43
+ /** The app connection principal. */
44
+ export declare function appPrincipal(): ConnectionPrincipal;
45
+ /** A session auth context, as `keycardAuth` would return it. */
46
+ export declare function sessionAuthContext(overrides?: Partial<SessionAuthContext>): SessionAuthContext;
47
+ /** The connection metadata eve passes to every authorization callback. */
48
+ export declare function connectionContext(url?: string): {
49
+ url: string;
50
+ };
51
+ //# sourceMappingURL=testUtils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"testUtils.d.ts","sourceRoot":"","sources":["../../../src/testing/testUtils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,wBAAwB,EACxB,kBAAkB,EAClB,SAAS,EACT,oBAAoB,EAErB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAC3D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAEtD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEnD,mDAAmD;AACnD,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,SAAS,EAAE,oBAAoB,EAAE,CAAC;IAC3C,QAAQ,CAAC,cAAc,EAAE,kBAAkB,EAAE,CAAC;IAC9C,QAAQ,CAAC,iBAAiB,EAAE,wBAAwB,EAAE,CAAC;CACxD;AAED,MAAM,WAAW,qBAAqB;IACpC,+DAA+D;IAC/D,aAAa,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC;IAChD,+CAA+C;IAC/C,IAAI,CAAC,EAAE,KAAK,CAAC;IACb,sDAAsD;IACtD,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,6DAA6D;AAC7D,MAAM,WAAW,cAAe,SAAQ,UAAU;IAChD,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC;CAC3B;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,OAAO,GAAE,qBAA0B,GAAG,cAAc,CA6BlF;AAED,oEAAoE;AACpE,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAInE;AAED,mDAAmD;AACnD,wBAAgB,UAAU,CAAC,UAAU,SAAK,GAAG,MAAM,CAElD;AAED,8DAA8D;AAC9D,wBAAgB,QAAQ,CAAC,YAAY,SAAO,EAAE,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GAAG,MAAM,CAM1F;AAED,yCAAyC;AACzC,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,SAAmC,GAAG,OAAO,CAE5F;AAED,8EAA8E;AAC9E,wBAAgB,YAAY,CAC1B,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,SAAS,GAChB,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,SAAS,CAAC,CAKvC;AAED,mCAAmC;AACnC,wBAAgB,aAAa,CAC3B,EAAE,SAAW,EACb,MAAM,SAA6B,EACnC,UAAU,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAAC,CAAC,GAChE,mBAAmB,CAErB;AAED,oCAAoC;AACpC,wBAAgB,YAAY,IAAI,mBAAmB,CAElD;AAED,gEAAgE;AAChE,wBAAgB,kBAAkB,CAChC,SAAS,GAAE,OAAO,CAAC,kBAAkB,CAAM,GAC1C,kBAAkB,CASpB;AAED,0EAA0E;AAC1E,wBAAgB,iBAAiB,CAAC,GAAG,SAAgC,GAAG;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE,CAEtF"}