@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
package/LICENSE ADDED
@@ -0,0 +1,9 @@
1
+ MIT LICENSE
2
+
3
+ Copyright © 2026 Keycard Labs, inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,223 @@
1
+ # @keycardai/eve
2
+
3
+ > Preview. Keycard auth for [eve](https://eve.dev) agents: a zone token verifier for a channel's auth walk, Keycard-backed connection auth, and interactive authorization over the zone's web flow.
4
+
5
+ Three adapters, each one plugging into an eve primitive instead of wrapping it:
6
+
7
+ | Adapter | eve primitive | What it does |
8
+ | --- | --- | --- |
9
+ | `keycardAuth()` | a channel's ordered `auth` array | Verifies a zone-issued bearer and projects the claims onto `SessionAuthContext`. |
10
+ | `Keycard.asSelf()`, `Keycard.onBehalfOf()`, `Keycard.impersonate()` | connection `auth` | Acquires a resource token at the tool-call boundary, for the app or for the turn's current user. |
11
+ | `Keycard.interactive()` | `defineInteractiveAuthorization` | Runs the zone's browser authorization flow and lets eve park the turn until the user consents. |
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ pnpm add @keycardai/eve
17
+ ```
18
+
19
+ `eve` is a peer dependency, pinned to `>=0.47.3 <0.48.0`. This package was
20
+ built and verified against eve `0.47.3`. eve is in public beta and ships
21
+ releases most days, and its connection and auth surfaces are still moving, so
22
+ the range deliberately stops at the next minor rather than tracking `^`. Widen
23
+ it only after re-running this package's tests against the newer eve.
24
+
25
+ eve itself declares `engines.node: ">=24"` and is ESM only. This package
26
+ imports eve for types only (`import type { ... } from "eve/connections"`), so
27
+ nothing here pulls eve into the runtime and the package builds and tests on
28
+ Node 22 as the rest of this repository's CI does. It ships an ESM build only,
29
+ because an eve app is ESM.
30
+
31
+ ## 1. Verify the caller: `keycardAuth()`
32
+
33
+ ```ts title="agent/channels/eve.ts"
34
+ import { eveChannel } from "eve/channels/eve";
35
+ import { localDev } from "eve/channels/auth";
36
+ import { keycardAuth } from "@keycardai/eve";
37
+
38
+ export default eveChannel({
39
+ auth: [
40
+ keycardAuth({
41
+ zoneUrl: process.env.KEYCARD_ZONE_URL!,
42
+ audience: "https://agent.example.com",
43
+ }),
44
+ localDev(),
45
+ ],
46
+ });
47
+ ```
48
+
49
+ Three outcomes, matching eve's ordered walk:
50
+
51
+ - No bearer, or a bearer this zone did not issue: returns `null`, so the next
52
+ entry in the array still gets a turn.
53
+ - A bearer this zone issued that does not verify, has expired, or names another
54
+ audience: throws with a `401` `Response`, which ends the walk. A broken
55
+ Keycard credential is a rejection, not an invitation to fall through to
56
+ something weaker.
57
+ - A verified bearer: returns `{ principalId, principalType, attributes, issuer,
58
+ subject }`, and retains the raw token for a later on-behalf-of exchange.
59
+
60
+ The retained subject token stays out of durable state by default: it lives in a
61
+ process-local store keyed by the principal eve projects onto a connection, so
62
+ it never reaches the model, the session record, or the event stream. Pass
63
+ `retainSubjectToken: "attributes"` when connections run in a different process
64
+ from the request that authenticated the caller, which accepts a bearer token in
65
+ eve's session attributes in exchange for surviving restarts. Pass `"none"` for
66
+ zones whose connections only ever run `asSelf` or `impersonate`.
67
+
68
+ The verifier and its JWKS keyring are built once per `keycardAuth()` call and
69
+ cache discovery and signing keys, so a request pays no discovery round trip.
70
+
71
+ ## 2. Acquire resource tokens: connection auth
72
+
73
+ ```ts title="agent/connections/calendar.ts"
74
+ import { defineMcpClientConnection } from "eve/connections";
75
+ import { Keycard } from "@keycardai/eve";
76
+
77
+ export default defineMcpClientConnection({
78
+ url: "https://calendar.example.com/mcp",
79
+ description: "The signed-in user's calendar.",
80
+ auth: Keycard.onBehalfOf({
81
+ zoneUrl: process.env.KEYCARD_ZONE_URL!,
82
+ resource: "https://calendar.example.com",
83
+ requestScopes: ["calendar.read"],
84
+ clientId: process.env.KEYCARD_CLIENT_ID!,
85
+ clientSecret: process.env.KEYCARD_CLIENT_SECRET!,
86
+ }),
87
+ });
88
+ ```
89
+
90
+ - `Keycard.onBehalfOf()` is user-scoped, so eve resolves the principal from the
91
+ active turn's `ctx.session.auth.current` and rejects with
92
+ `reason: "principal_required"` when there is no authenticated user. It
93
+ exchanges the subject token `keycardAuth()` verified for that same principal.
94
+ - `Keycard.asSelf()` is app-scoped and runs client credentials under the
95
+ agent's own identity, so it works on schedules and subagent turns. It never
96
+ performs an exchange, so nothing about a caller reaches the zone.
97
+ - `Keycard.impersonate({ userIdentifier })` uses the zone's substitute-user
98
+ exchange for a user the agent holds no token for. A fixed identifier makes
99
+ the connection app-scoped; a function receives the connection principal and
100
+ makes it user-scoped.
101
+
102
+ Nothing falls back to the agent's authority. A user-pattern connection with no
103
+ user principal, a turn whose subject token was never retained, and an expired
104
+ subject token all fail, each with its own reason: `principal_required`,
105
+ `subject_token_unavailable`, and `subject_token_expired`. The last one is the
106
+ sign-in signal, decided by a decode-only expiry check, so an already dead token
107
+ never costs an exchange round trip.
108
+
109
+ Credentials go in as either `clientId` plus `clientSecret` (shorthand for a
110
+ client-secret credential) or `applicationCredential` (any
111
+ `ApplicationCredential`, including assertion-based workload credentials, whose
112
+ `clientAssertion`, `clientAssertionType`, and `clientId` are forwarded). Setting
113
+ both is a configuration error.
114
+
115
+ Every factory builds one warm zone client and reuses it, so tool calls do not
116
+ pay per-call discovery or client construction.
117
+
118
+ ### A revoked token mid-call
119
+
120
+ `getToken` runs before a tool call, so a grant revoked while a tool is in
121
+ flight surfaces as a `401` inside `execute`. Map it to `ctx.requireAuth` so eve
122
+ evicts the rejected bearer and re-challenges instead of handing the model a
123
+ dead-token error:
124
+
125
+ ```ts
126
+ import { requireAuthOnUnauthorized } from "@keycardai/eve";
127
+
128
+ if (!res.ok) requireAuthOnUnauthorized(res, ctx, calendarAuth);
129
+ ```
130
+
131
+ ## 3. Ask the user to sign in: `Keycard.interactive()`
132
+
133
+ ```ts title="agent/connections/docs.ts"
134
+ import { defineMcpClientConnection } from "eve/connections";
135
+ import { Keycard } from "@keycardai/eve";
136
+
137
+ export default defineMcpClientConnection({
138
+ url: "https://docs.example.com/mcp",
139
+ description: "Documents the user has authorized.",
140
+ auth: Keycard.interactive({
141
+ zoneUrl: process.env.KEYCARD_ZONE_URL!,
142
+ clientId: process.env.KEYCARD_CLIENT_ID!,
143
+ resource: "https://docs.example.com",
144
+ requestScopes: ["documents.read"],
145
+ }),
146
+ });
147
+ ```
148
+
149
+ The definition implements the same three-method form as eve's
150
+ `defineInteractiveAuthorization`, over `@keycardai/oauth`'s v3 web-app flow:
151
+
152
+ - `getToken` returns a token only when this package holds one for the
153
+ principal. Otherwise it throws `ConnectionAuthorizationRequiredError`, so eve
154
+ emits `authorization.required`, runs `startAuthorization` in a durable step,
155
+ and parks the turn on a framework-owned callback.
156
+ - `startAuthorization` calls `beginAuthorization` for the connection's resource
157
+ list against eve's minted callback URL, and returns the challenge URL plus
158
+ the `state` and PKCE verifier as JSON resume state.
159
+ - `completeAuthorization` calls `completeAuthorization` with eve's callback
160
+ params and the journaled resume state, and hands eve the token.
161
+
162
+ **Resume without authorization cannot yield a credential.** `getToken` is the
163
+ only path that returns a token, and it reads a store only
164
+ `completeAuthorization` writes. A denied, forged, or failed callback writes
165
+ nothing, so a resumed turn either finds a real grant or throws `Required` again
166
+ and parks. eve's own exactly-once settlement makes that terminal instead of a
167
+ loop: it settles each parked authorization once, and a `Required` thrown after
168
+ an authorization has settled ends the tool call. User denial is reported as
169
+ `ConnectionAuthorizationFailedError` with `reason: "access_denied"` and
170
+ `retryable: false`, so eve stops re-prompting.
171
+
172
+ ## Parity with `@keycardai/langchain`
173
+
174
+ The two packages implement the same Keycard access model against different
175
+ framework primitives. What LangChain needs middleware and interrupts for, eve
176
+ already owns:
177
+
178
+ | `@keycardai/langchain` | `@keycardai/eve` |
179
+ | --- | --- |
180
+ | `keycardAccess()` middleware wrapping tool execution | connection `auth` definitions; eve calls `getToken` at the tool boundary and attaches the bearer itself |
181
+ | `Access.asSelf()`, `Access.onBehalfOf()`, `Access.impersonate()` | `Keycard.asSelf()`, `Keycard.onBehalfOf()`, `Keycard.impersonate()` |
182
+ | LangGraph `interrupt()` for sign-in and consent | eve durable parks driven by `ConnectionAuthorizationRequiredError` and the `authorization.required` event |
183
+ | middleware-managed token cache and per-run identity | eve's per-step credential cache and session principal (`ctx.session.auth.current`) |
184
+ | middleware keeping credentials out of tool arguments | eve keeping credentials out of the model's view by construction, since auth never appears in a tool's input schema |
185
+ | `subjectTokenExpired()` decode-only expiry check | the same check, exported here as well |
186
+ | fake zone client from `@keycardai/langchain/testing` | fake zone client from `@keycardai/eve/testing` |
187
+
188
+ There is no middleware to install here, and no tool wrapper. The package
189
+ supplies auth functions and auth definitions, and eve does the rest.
190
+
191
+ ## Testing offline
192
+
193
+ `@keycardai/eve/testing` provides seams that take no network:
194
+
195
+ ```ts
196
+ import { fakeZoneClient, userPrincipal, validJwt } from "@keycardai/eve/testing";
197
+ import { Keycard, memorySubjectTokenStore } from "@keycardai/eve";
198
+
199
+ const client = fakeZoneClient({
200
+ failResources: { "https://calendar.example.com": new Error("exchange refused") },
201
+ });
202
+ const subjectTokens = memorySubjectTokenStore();
203
+ subjectTokens.set("https://zone.example.com|user-1", validJwt(3600));
204
+
205
+ const auth = Keycard.onBehalfOf({
206
+ resource: "https://calendar.example.com",
207
+ client,
208
+ subjectTokens,
209
+ });
210
+ ```
211
+
212
+ `fakeZoneClient()` records every exchange, impersonation, and client
213
+ credentials call, and can fail one resource or every request. `keycardAuth()`
214
+ takes a `verify` seam in place of the JWKS-backed verifier, and
215
+ `Keycard.interactive()` takes a `flow` seam in place of the two web-flow calls.
216
+ An injected `client` or `flow` supersedes `zoneUrl`, so a test needs no zone.
217
+
218
+ ## Not included: the Keycard gateway MCP proxy
219
+
220
+ Routing third-party MCP servers through the Keycard gateway is out of scope for
221
+ this release, pending svc-sts #651, exactly as in `@keycardai/langchain`.
222
+ Third-party MCP servers reached directly through eve's native connections are
223
+ supported, and that is what the examples above do.
@@ -0,0 +1,62 @@
1
+ import { type JWTClaims, type OAuthKeyring } from "@keycardai/oauth";
2
+ import type { AuthFn } from "eve/channels/auth";
3
+ import { type SubjectTokenStore } from "./subjectTokens.js";
4
+ /**
5
+ * How the verified inbound bearer is kept for a later on-behalf-of exchange.
6
+ *
7
+ * - `"memory"` (default): a process-local store keyed by the principal eve
8
+ * projects onto a connection. Nothing is written to durable session state.
9
+ * A turn that resumes in another process finds no token and fails closed.
10
+ * - `"attributes"`: the token rides on `SessionAuthContext.attributes`, which
11
+ * eve persists with the session and copies onto the connection principal.
12
+ * Survives restarts and reaches connections in other processes, at the cost
13
+ * of a bearer token in durable state.
14
+ * - `"none"`: nothing is retained. For zones where connections only ever run
15
+ * `asSelf` or `impersonate`.
16
+ */
17
+ export type SubjectTokenRetention = "attributes" | "memory" | "none";
18
+ export interface KeycardAuthOptions {
19
+ /** Keycard zone URL (issuer). Required unless `verify` is given. */
20
+ zoneUrl?: string;
21
+ /** Audience(s) the token must carry. Omit to skip audience validation. */
22
+ audience?: string | readonly string[];
23
+ /** `principalType` for the session context. Defaults to `"user"`. */
24
+ principalType?: string;
25
+ /** Scopes the token must carry. Missing scopes reject the request. */
26
+ requiredScopes?: readonly string[];
27
+ /** JWT algorithms to accept. Defaults to the verifier's own default. */
28
+ algorithms?: readonly string[];
29
+ /** Keyring for signing keys. Defaults to a cached JWKS keyring. */
30
+ keyring?: OAuthKeyring;
31
+ /**
32
+ * Verification seam. Returns the verified claims, or throws to reject.
33
+ * Replaces the JWKS-backed verifier, so tests take no network.
34
+ */
35
+ verify?: (token: string) => Promise<JWTClaims>;
36
+ /** Extra attributes for the session context, from the verified claims. */
37
+ attributes?: (claims: JWTClaims) => Readonly<Record<string, string | readonly string[]>>;
38
+ /** Retention mode for the inbound bearer. Defaults to `"memory"`. */
39
+ retainSubjectToken?: SubjectTokenRetention;
40
+ /** Store for `"memory"` retention. Defaults to the shared store. */
41
+ subjectTokens?: SubjectTokenStore;
42
+ }
43
+ /**
44
+ * A Keycard `AuthFn` for a channel's `auth` array.
45
+ *
46
+ * Verifies a zone-issued bearer and projects its claims onto eve's
47
+ * `SessionAuthContext`. Three outcomes, matching eve's ordered walk:
48
+ *
49
+ * - No bearer, or a bearer this zone did not issue: returns `null`, so a later
50
+ * entry in the array (a shared secret, a local dev bypass) still gets a turn.
51
+ * - A bearer this zone issued that does not verify, is expired, or is for
52
+ * another audience: throws with a 401 `Response`, which stops the walk. A
53
+ * broken Keycard credential is a rejection, not an invitation to fall
54
+ * through to something weaker.
55
+ * - A verified bearer: returns the session context, and retains the token for
56
+ * `onBehalfOf`.
57
+ *
58
+ * The verifier and its keyring are built once per `keycardAuth` call and cache
59
+ * discovery and signing keys, so a request pays no discovery round trip.
60
+ */
61
+ export declare function keycardAuth(options: KeycardAuthOptions): AuthFn<Request>;
62
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,SAAS,EACd,KAAK,YAAY,EAClB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAKhD,OAAO,EAIL,KAAK,iBAAiB,EACvB,MAAM,oBAAoB,CAAC;AAc5B;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,qBAAqB,GAAG,YAAY,GAAG,QAAQ,GAAG,MAAM,CAAC;AAErE,MAAM,WAAW,kBAAkB;IACjC,oEAAoE;IACpE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAAC;IACtC,qEAAqE;IACrE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,sEAAsE;IACtE,cAAc,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,wEAAwE;IACxE,UAAU,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC/B,mEAAmE;IACnE,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB;;;OAGG;IACH,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,SAAS,CAAC,CAAC;IAC/C,0EAA0E;IAC1E,UAAU,CAAC,EAAE,CACX,MAAM,EAAE,SAAS,KACd,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAAC,CAAC,CAAC;IAC1D,qEAAqE;IACrE,kBAAkB,CAAC,EAAE,qBAAqB,CAAC;IAC3C,oEAAoE;IACpE,aAAa,CAAC,EAAE,iBAAiB,CAAC;CACnC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,kBAAkB,GAAG,MAAM,CAAC,OAAO,CAAC,CA6DxE"}
@@ -0,0 +1,165 @@
1
+ import { JWKSOAuthKeyring, JWTVerifier, } from "@keycardai/oauth";
2
+ import { RouteRejectedError } from "./errors.js";
3
+ import { decodeClaims } from "./expiry.js";
4
+ import { defaultSubjectTokenStore, principalKey, SUBJECT_TOKEN_ATTRIBUTE, } from "./subjectTokens.js";
5
+ /** Claims never copied into `SessionAuthContext.attributes`. */
6
+ const RESERVED_CLAIMS = new Set([
7
+ "aud",
8
+ "exp",
9
+ "iat",
10
+ "iss",
11
+ "jti",
12
+ "nbf",
13
+ "sub",
14
+ SUBJECT_TOKEN_ATTRIBUTE,
15
+ ]);
16
+ /**
17
+ * A Keycard `AuthFn` for a channel's `auth` array.
18
+ *
19
+ * Verifies a zone-issued bearer and projects its claims onto eve's
20
+ * `SessionAuthContext`. Three outcomes, matching eve's ordered walk:
21
+ *
22
+ * - No bearer, or a bearer this zone did not issue: returns `null`, so a later
23
+ * entry in the array (a shared secret, a local dev bypass) still gets a turn.
24
+ * - A bearer this zone issued that does not verify, is expired, or is for
25
+ * another audience: throws with a 401 `Response`, which stops the walk. A
26
+ * broken Keycard credential is a rejection, not an invitation to fall
27
+ * through to something weaker.
28
+ * - A verified bearer: returns the session context, and retains the token for
29
+ * `onBehalfOf`.
30
+ *
31
+ * The verifier and its keyring are built once per `keycardAuth` call and cache
32
+ * discovery and signing keys, so a request pays no discovery round trip.
33
+ */
34
+ export function keycardAuth(options) {
35
+ if (!options.zoneUrl && !options.verify) {
36
+ throw new Error("keycardAuth requires zoneUrl or verify");
37
+ }
38
+ // Zone tokens carry no trailing slash in `iss`; a slash on the configured
39
+ // zoneUrl would otherwise make every zone token unrecognized.
40
+ const issuer = options.zoneUrl?.replace(/\/+$/, "");
41
+ const principalType = options.principalType ?? "user";
42
+ const retention = options.retainSubjectToken ?? "memory";
43
+ const store = options.subjectTokens ?? defaultSubjectTokenStore;
44
+ const verify = options.verify ?? jwksVerifier(options, issuer);
45
+ return async (request) => {
46
+ const token = bearerToken(request);
47
+ if (token === null)
48
+ return null;
49
+ if (!recognized(token, issuer))
50
+ return null;
51
+ let claims;
52
+ try {
53
+ claims = await verify(token);
54
+ }
55
+ catch (cause) {
56
+ throw new RouteRejectedError({
57
+ message: cause instanceof Error ? cause.message : "Invalid token",
58
+ code: "invalid_token",
59
+ error: "invalid_token",
60
+ });
61
+ }
62
+ const missing = missingScopes(claims, options.requiredScopes);
63
+ if (missing.length > 0) {
64
+ throw new RouteRejectedError({
65
+ message: `Token is missing required scope(s): ${missing.join(", ")}`,
66
+ code: "insufficient_scope",
67
+ error: "insufficient_scope",
68
+ });
69
+ }
70
+ const principalId = String(claims.sub);
71
+ const attributes = {
72
+ ...claimAttributes(claims),
73
+ ...options.attributes?.(claims),
74
+ };
75
+ const context = {
76
+ attributes,
77
+ authenticator: "keycard",
78
+ principalId,
79
+ principalType,
80
+ ...(typeof claims.iss === "string" ? { issuer: claims.iss } : {}),
81
+ subject: principalId,
82
+ };
83
+ if (retention === "attributes") {
84
+ attributes[SUBJECT_TOKEN_ATTRIBUTE] = token;
85
+ }
86
+ else if (retention === "memory") {
87
+ store.set(principalKey(context), token);
88
+ }
89
+ return context;
90
+ };
91
+ }
92
+ /** The warm JWKS-backed verifier used when no `verify` seam is supplied. */
93
+ function jwksVerifier(options, issuer) {
94
+ const verifier = new JWTVerifier(options.keyring ?? new JWKSOAuthKeyring(), {
95
+ issuers: issuer,
96
+ ...(options.audience ? { audiences: options.audience } : {}),
97
+ ...(options.algorithms ? { algorithms: options.algorithms } : {}),
98
+ });
99
+ return (token) => verifier.verify(token);
100
+ }
101
+ /**
102
+ * Whether this bearer is a caller the zone issued.
103
+ *
104
+ * A decode-only issuer peek, so an unrelated credential in the same header
105
+ * (another provider's token, an opaque API key) falls through to the next
106
+ * `AuthFn` instead of rejecting the request. Nothing is trusted from this
107
+ * decode: the verifier re-checks the issuer against the same allowlist before
108
+ * any key lookup.
109
+ */
110
+ function recognized(token, issuer) {
111
+ if (issuer === undefined)
112
+ return true;
113
+ const claims = decodeClaims(token);
114
+ if (claims === null)
115
+ return false;
116
+ return claims.iss === issuer;
117
+ }
118
+ function bearerToken(request) {
119
+ const header = request.headers.get("authorization");
120
+ if (!header)
121
+ return null;
122
+ const [scheme, ...rest] = header.trim().split(/\s+/);
123
+ if (!scheme || scheme.toLowerCase() !== "bearer")
124
+ return null;
125
+ const token = rest.join("");
126
+ return token || null;
127
+ }
128
+ function missingScopes(claims, required) {
129
+ if (!required || required.length === 0)
130
+ return [];
131
+ const granted = new Set(tokenScopes(claims));
132
+ return required.filter((scope) => !granted.has(scope));
133
+ }
134
+ function tokenScopes(claims) {
135
+ const scope = claims.scope;
136
+ if (typeof scope === "string")
137
+ return scope.split(" ").filter(Boolean);
138
+ if (Array.isArray(scope)) {
139
+ return scope.filter((entry) => typeof entry === "string");
140
+ }
141
+ return [];
142
+ }
143
+ /**
144
+ * Claims projected onto the session context.
145
+ *
146
+ * String and string-list claims only, minus JWT plumbing: attributes are
147
+ * durable session state and reach connection principals, so structured claims
148
+ * are left to an explicit `attributes` mapper.
149
+ */
150
+ function claimAttributes(claims) {
151
+ const attributes = {};
152
+ for (const [name, value] of Object.entries(claims)) {
153
+ if (RESERVED_CLAIMS.has(name))
154
+ continue;
155
+ if (typeof value === "string") {
156
+ attributes[name] = value;
157
+ }
158
+ else if (Array.isArray(value) &&
159
+ value.every((entry) => typeof entry === "string")) {
160
+ attributes[name] = value;
161
+ }
162
+ }
163
+ return attributes;
164
+ }
165
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/auth.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,50 @@
1
+ import { type ApplicationCredential, type ClientCredentialsRequest } from "@keycardai/oauth";
2
+ import { type ZoneClient } from "./zoneClient.js";
3
+ import { type SubjectTokenStore } from "./subjectTokens.js";
4
+ /** Options shared by every Keycard connection auth factory. */
5
+ export interface KeycardConnectionOptions {
6
+ /** The resource URL tokens are minted for. */
7
+ resource: string;
8
+ /** Keycard zone URL (issuer). Required unless `client` is given. */
9
+ zoneUrl?: string;
10
+ /**
11
+ * How the agent authenticates to the zone: `ClientSecret` for Keycard-issued
12
+ * client credentials, or any other `ApplicationCredential`. Mutually
13
+ * exclusive with `clientId` / `clientSecret`.
14
+ */
15
+ applicationCredential?: ApplicationCredential;
16
+ /** Shorthand for `applicationCredential: new ClientSecret(clientId, clientSecret)`. */
17
+ clientId?: string;
18
+ /** Shorthand for `applicationCredential: new ClientSecret(clientId, clientSecret)`. */
19
+ clientSecret?: string;
20
+ /** Scopes requested from the zone for this connection. */
21
+ requestScopes?: string | readonly string[];
22
+ /** Pre-built zone client. Replaces `zoneUrl`, and takes no network in tests. */
23
+ client?: ZoneClient;
24
+ /** Name used in error messages and eve's authorization events. */
25
+ connectionName?: string;
26
+ /** Where the verified inbound bearer is read from. Defaults to the shared store. */
27
+ subjectTokens?: SubjectTokenStore;
28
+ }
29
+ /** One factory's resolved configuration, validated once at definition time. */
30
+ export interface ResolvedConnectionConfig {
31
+ readonly resource: string;
32
+ readonly scope?: string;
33
+ readonly connectionName: string;
34
+ readonly credential?: ApplicationCredential;
35
+ readonly subjectTokens: SubjectTokenStore;
36
+ /** The warm zone client, built on first use and reused after that. */
37
+ zoneClient(): ZoneClient;
38
+ /** Client-authentication fields an assertion credential adds to a request body. */
39
+ clientAuthFields(): Promise<Partial<ClientCredentialsRequest>>;
40
+ }
41
+ /**
42
+ * Validates and resolves factory options.
43
+ *
44
+ * Configuration mistakes throw here, when the connection module is loaded,
45
+ * rather than on the first tool call inside a turn.
46
+ */
47
+ export declare function resolveConnectionConfig(options: KeycardConnectionOptions, factory: string): ResolvedConnectionConfig;
48
+ /** Absolute expiry for eve, from the zone's relative `expires_in`. */
49
+ export declare function expiresAt(expiresIn: number | undefined): number | undefined;
50
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,qBAAqB,EAC1B,KAAK,wBAAwB,EAC9B,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EAAqB,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAC;AACrE,OAAO,EAA4B,KAAK,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAEtF,+DAA+D;AAC/D,MAAM,WAAW,wBAAwB;IACvC,8CAA8C;IAC9C,QAAQ,EAAE,MAAM,CAAC;IACjB,oEAAoE;IACpE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;OAIG;IACH,qBAAqB,CAAC,EAAE,qBAAqB,CAAC;IAC9C,uFAAuF;IACvF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,uFAAuF;IACvF,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,0DAA0D;IAC1D,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAAC;IAC3C,gFAAgF;IAChF,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,kEAAkE;IAClE,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,oFAAoF;IACpF,aAAa,CAAC,EAAE,iBAAiB,CAAC;CACnC;AAED,+EAA+E;AAC/E,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,UAAU,CAAC,EAAE,qBAAqB,CAAC;IAC5C,QAAQ,CAAC,aAAa,EAAE,iBAAiB,CAAC;IAC1C,sEAAsE;IACtE,UAAU,IAAI,UAAU,CAAC;IACzB,mFAAmF;IACnF,gBAAgB,IAAI,OAAO,CAAC,OAAO,CAAC,wBAAwB,CAAC,CAAC,CAAC;CAChE;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,wBAAwB,EACjC,OAAO,EAAE,MAAM,GACd,wBAAwB,CAiE1B;AAED,sEAAsE;AACtE,wBAAgB,SAAS,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAG3E"}
@@ -0,0 +1,83 @@
1
+ import { AuthProviderConfigurationError, ClientSecret, } from "@keycardai/oauth";
2
+ import { KeycardZoneClient } from "./zoneClient.js";
3
+ import { defaultSubjectTokenStore } from "./subjectTokens.js";
4
+ /**
5
+ * Validates and resolves factory options.
6
+ *
7
+ * Configuration mistakes throw here, when the connection module is loaded,
8
+ * rather than on the first tool call inside a turn.
9
+ */
10
+ export function resolveConnectionConfig(options, factory) {
11
+ if (!options.resource || !options.resource.trim()) {
12
+ throw new AuthProviderConfigurationError(`${factory} requires a resource URL`);
13
+ }
14
+ if (!options.zoneUrl && !options.client) {
15
+ throw new AuthProviderConfigurationError(`${factory} requires zoneUrl or client`);
16
+ }
17
+ if (options.applicationCredential && (options.clientId || options.clientSecret)) {
18
+ throw new AuthProviderConfigurationError(`${factory} accepts either applicationCredential or clientId/clientSecret, not both`);
19
+ }
20
+ if (Boolean(options.clientId) !== Boolean(options.clientSecret)) {
21
+ throw new AuthProviderConfigurationError(`${factory} requires both clientId and clientSecret when using the shorthand`);
22
+ }
23
+ let credential;
24
+ if (options.applicationCredential) {
25
+ credential = options.applicationCredential;
26
+ }
27
+ else if (options.clientId && options.clientSecret) {
28
+ credential = new ClientSecret(options.clientId, options.clientSecret);
29
+ }
30
+ let client = options.client;
31
+ const scope = joinScopes(options.requestScopes);
32
+ return {
33
+ resource: options.resource,
34
+ ...(scope ? { scope } : {}),
35
+ connectionName: options.connectionName ?? options.resource,
36
+ ...(credential ? { credential } : {}),
37
+ subjectTokens: options.subjectTokens ?? defaultSubjectTokenStore,
38
+ zoneClient() {
39
+ if (!client)
40
+ client = new KeycardZoneClient(options.zoneUrl, credential);
41
+ return client;
42
+ },
43
+ /**
44
+ * Assertion-based credentials carry no HTTP-level auth; their proof rides
45
+ * in the request body as a jwt-bearer client assertion. The credential
46
+ * protocol only exposes request preparation for token exchange, so this
47
+ * prepares one and lifts the client-auth fields for the
48
+ * client-credentials call. `ClientSecret` authenticates at the HTTP layer
49
+ * and contributes nothing here.
50
+ *
51
+ * The subject token below is a placeholder: client credentials has no
52
+ * subject, and only the client-auth fields of the prepared request are
53
+ * read.
54
+ */
55
+ async clientAuthFields() {
56
+ if (!credential)
57
+ return {};
58
+ const prepared = await credential.prepareTokenExchangeRequest("client-credentials", options.resource);
59
+ if (!prepared.clientAssertion)
60
+ return {};
61
+ const fields = {
62
+ clientAssertion: prepared.clientAssertion,
63
+ clientAssertionType: prepared.clientAssertionType,
64
+ };
65
+ if (prepared.clientId)
66
+ fields.clientId = prepared.clientId;
67
+ return fields;
68
+ },
69
+ };
70
+ }
71
+ /** Absolute expiry for eve, from the zone's relative `expires_in`. */
72
+ export function expiresAt(expiresIn) {
73
+ if (typeof expiresIn !== "number" || !Number.isFinite(expiresIn))
74
+ return undefined;
75
+ return Date.now() + expiresIn * 1000;
76
+ }
77
+ function joinScopes(scopes) {
78
+ if (scopes === undefined)
79
+ return undefined;
80
+ const value = Array.isArray(scopes) ? scopes.join(" ") : scopes;
81
+ return value || undefined;
82
+ }
83
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/config.ts"],"names":[],"mappings":"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"}