@slashfi/agents-sdk 0.16.0 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-definitions/auth.d.ts.map +1 -1
- package/dist/agent-definitions/auth.js +44 -11
- package/dist/agent-definitions/auth.js.map +1 -1
- package/dist/agent-definitions/integrations.d.ts.map +1 -1
- package/dist/agent-definitions/integrations.js +106 -45
- package/dist/agent-definitions/integrations.js.map +1 -1
- package/dist/agent-definitions/remote-registry.d.ts.map +1 -1
- package/dist/agent-definitions/remote-registry.js +174 -45
- package/dist/agent-definitions/remote-registry.js.map +1 -1
- package/dist/agent-definitions/secrets.d.ts.map +1 -1
- package/dist/agent-definitions/secrets.js +1 -4
- package/dist/agent-definitions/secrets.js.map +1 -1
- package/dist/agent-definitions/users.d.ts.map +1 -1
- package/dist/agent-definitions/users.js +14 -3
- package/dist/agent-definitions/users.js.map +1 -1
- package/dist/define-config.d.ts +125 -0
- package/dist/define-config.d.ts.map +1 -0
- package/dist/define-config.js +75 -0
- package/dist/define-config.js.map +1 -0
- package/dist/define.d.ts +11 -2
- package/dist/define.d.ts.map +1 -1
- package/dist/define.js +57 -26
- package/dist/define.js.map +1 -1
- package/dist/events.d.ts +133 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +57 -0
- package/dist/events.js.map +1 -0
- package/dist/index.d.ts +16 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +21 -3
- package/dist/index.js.map +1 -1
- package/dist/integration-interface.d.ts +3 -3
- package/dist/integration-interface.d.ts.map +1 -1
- package/dist/integration-interface.js +29 -21
- package/dist/integration-interface.js.map +1 -1
- package/dist/integrations-store.d.ts +2 -2
- package/dist/integrations-store.d.ts.map +1 -1
- package/dist/integrations-store.js +3 -3
- package/dist/integrations-store.js.map +1 -1
- package/dist/jwt.d.ts.map +1 -1
- package/dist/jwt.js +7 -5
- package/dist/jwt.js.map +1 -1
- package/dist/key-manager.d.ts.map +1 -1
- package/dist/key-manager.js +5 -3
- package/dist/key-manager.js.map +1 -1
- package/dist/oidc-signin.d.ts +32 -0
- package/dist/oidc-signin.d.ts.map +1 -0
- package/dist/oidc-signin.js +138 -0
- package/dist/oidc-signin.js.map +1 -0
- package/dist/registry-consumer.d.ts +104 -0
- package/dist/registry-consumer.d.ts.map +1 -0
- package/dist/registry-consumer.js +230 -0
- package/dist/registry-consumer.js.map +1 -0
- package/dist/registry.d.ts +5 -0
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +76 -4
- package/dist/registry.js.map +1 -1
- package/dist/secret-collection.d.ts.map +1 -1
- package/dist/secret-collection.js.map +1 -1
- package/dist/server.d.ts +3 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +222 -27
- package/dist/server.js.map +1 -1
- package/dist/test-utils/mock-oidc-server.d.ts +36 -0
- package/dist/test-utils/mock-oidc-server.d.ts.map +1 -0
- package/dist/test-utils/mock-oidc-server.js +96 -0
- package/dist/test-utils/mock-oidc-server.js.map +1 -0
- package/dist/types.d.ts +106 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +6 -1
- package/src/agent-definitions/auth.ts +106 -38
- package/src/agent-definitions/integrations.ts +201 -73
- package/src/agent-definitions/remote-registry.ts +262 -65
- package/src/agent-definitions/secrets.ts +22 -8
- package/src/agent-definitions/users.ts +16 -4
- package/src/cli.ts +293 -0
- package/src/codegen.test.ts +527 -0
- package/src/codegen.ts +1348 -0
- package/src/consumer.test.ts +536 -0
- package/src/define-config.ts +205 -0
- package/src/define.ts +134 -46
- package/src/events.ts +237 -0
- package/src/index.ts +107 -8
- package/src/integration-interface.ts +52 -28
- package/src/integrations-store.ts +9 -5
- package/src/jwt.ts +48 -19
- package/src/key-manager.test.ts +22 -13
- package/src/key-manager.ts +8 -10
- package/src/oidc-signin.ts +223 -0
- package/src/registry-consumer.ts +413 -0
- package/src/registry.ts +115 -9
- package/src/secret-collection.ts +2 -1
- package/src/server.test.ts +304 -238
- package/src/server.ts +371 -69
- package/src/test-utils/mock-oidc-server.ts +123 -0
- package/src/types.ts +172 -18
package/src/index.ts
CHANGED
|
@@ -83,6 +83,11 @@ export type {
|
|
|
83
83
|
ToolSchema,
|
|
84
84
|
ToolSelectionContext,
|
|
85
85
|
IntegrationConfig,
|
|
86
|
+
ApiKeySecurityScheme,
|
|
87
|
+
HttpSecurityScheme,
|
|
88
|
+
NoneSecurityScheme,
|
|
89
|
+
OAuth2SecurityScheme,
|
|
90
|
+
SecurityScheme,
|
|
86
91
|
IntegrationMethods,
|
|
87
92
|
IntegrationMethodResult,
|
|
88
93
|
IntegrationMethodContext,
|
|
@@ -92,15 +97,48 @@ export type {
|
|
|
92
97
|
|
|
93
98
|
// Define functions
|
|
94
99
|
export { defineAgent, defineTool } from "./define.js";
|
|
95
|
-
export type { DefineAgentOptions, DefineToolOptions } from "./define.js";
|
|
100
|
+
export type { DefineAgentOptions, DefineToolOptions, AgentWithHooks, ToolWithHooks } from "./define.js";
|
|
96
101
|
|
|
97
102
|
// Registry
|
|
98
103
|
export { createAgentRegistry } from "./registry.js";
|
|
99
|
-
export type {
|
|
104
|
+
export type {
|
|
105
|
+
AgentRegistry,
|
|
106
|
+
AgentRegistryOptions,
|
|
107
|
+
RegistryMiddleware,
|
|
108
|
+
} from "./registry.js";
|
|
109
|
+
|
|
110
|
+
// Events
|
|
111
|
+
export { createEventBus } from "./events.js";
|
|
112
|
+
export type {
|
|
113
|
+
EventBus,
|
|
114
|
+
EventType,
|
|
115
|
+
EventCallback,
|
|
116
|
+
AgentEvent,
|
|
117
|
+
ToolCallEvent,
|
|
118
|
+
ToolResultEvent,
|
|
119
|
+
ToolErrorEvent,
|
|
120
|
+
StepEvent,
|
|
121
|
+
InvokeEvent,
|
|
122
|
+
EventMap,
|
|
123
|
+
} from "./events.js";
|
|
100
124
|
|
|
101
125
|
// Server
|
|
102
|
-
export {
|
|
103
|
-
|
|
126
|
+
export {
|
|
127
|
+
createAgentServer,
|
|
128
|
+
detectAuth,
|
|
129
|
+
resolveAuth,
|
|
130
|
+
canSeeAgent,
|
|
131
|
+
} from "./server.js";
|
|
132
|
+
export type {
|
|
133
|
+
AgentServer,
|
|
134
|
+
AgentServerOptions,
|
|
135
|
+
AuthConfig,
|
|
136
|
+
OAuthIdentityProvider,
|
|
137
|
+
ResolvedAuth,
|
|
138
|
+
TrustedIssuer,
|
|
139
|
+
} from "./server.js";
|
|
140
|
+
export { createOIDCSignIn } from "./oidc-signin.js";
|
|
141
|
+
export type { OIDCProviderConfig, OIDCSignInHandler } from "./oidc-signin.js";
|
|
104
142
|
|
|
105
143
|
// Secret Collection
|
|
106
144
|
export {
|
|
@@ -147,8 +185,23 @@ export type {
|
|
|
147
185
|
export { encryptSecret, decryptSecret } from "./crypto.js";
|
|
148
186
|
|
|
149
187
|
// JWT
|
|
150
|
-
export {
|
|
151
|
-
|
|
188
|
+
export {
|
|
189
|
+
signJwt,
|
|
190
|
+
verifyJwt,
|
|
191
|
+
signJwtES256,
|
|
192
|
+
verifyJwtLocal,
|
|
193
|
+
verifyJwtFromIssuer,
|
|
194
|
+
generateSigningKey,
|
|
195
|
+
exportSigningKey,
|
|
196
|
+
importSigningKey,
|
|
197
|
+
buildJwks,
|
|
198
|
+
} from "./jwt.js";
|
|
199
|
+
export type {
|
|
200
|
+
JwtPayload,
|
|
201
|
+
AgentJwtPayload,
|
|
202
|
+
SigningKey,
|
|
203
|
+
ExportedKeyPair,
|
|
204
|
+
} from "./jwt.js";
|
|
152
205
|
|
|
153
206
|
// Postgres Secret Store
|
|
154
207
|
|
|
@@ -177,7 +230,6 @@ export type {
|
|
|
177
230
|
TokenExchangeResult,
|
|
178
231
|
} from "./agent-definitions/integrations.js";
|
|
179
232
|
|
|
180
|
-
|
|
181
233
|
// Remote Registry
|
|
182
234
|
export { createRemoteRegistryAgent } from "./agent-definitions/remote-registry.js";
|
|
183
235
|
export type { RemoteRegistryAgentOptions } from "./agent-definitions/remote-registry.js";
|
|
@@ -195,4 +247,51 @@ export type {
|
|
|
195
247
|
export * from "./integrations-store.js";
|
|
196
248
|
export * from "./integration-interface.js";
|
|
197
249
|
export type { ContextFactory } from "./registry.js";
|
|
198
|
-
export {
|
|
250
|
+
export {
|
|
251
|
+
createKeyManager,
|
|
252
|
+
type KeyManager,
|
|
253
|
+
type KeyStore,
|
|
254
|
+
type KeyManagerOptions,
|
|
255
|
+
type StoredKey,
|
|
256
|
+
type KeyStatus,
|
|
257
|
+
} from "./key-manager.js";
|
|
258
|
+
|
|
259
|
+
// Config & Consumer
|
|
260
|
+
export {
|
|
261
|
+
normalizeRef,
|
|
262
|
+
normalizeRegistry,
|
|
263
|
+
isSecretUrl,
|
|
264
|
+
isSecretUri,
|
|
265
|
+
} from "./define-config.js";
|
|
266
|
+
export type {
|
|
267
|
+
RegistryAuth,
|
|
268
|
+
RegistryEntry,
|
|
269
|
+
RefConfig,
|
|
270
|
+
RefEntry,
|
|
271
|
+
ConsumerConfig,
|
|
272
|
+
ResolvedRegistry,
|
|
273
|
+
ResolvedRef,
|
|
274
|
+
ResolvedConfig,
|
|
275
|
+
} from "./define-config.js";
|
|
276
|
+
|
|
277
|
+
export { createRegistryConsumer } from "./registry-consumer.js";
|
|
278
|
+
export type {
|
|
279
|
+
RegistryConsumer,
|
|
280
|
+
RegistryConsumerOptions,
|
|
281
|
+
RegistryConfiguration,
|
|
282
|
+
AgentListing,
|
|
283
|
+
SecretResolver,
|
|
284
|
+
} from "./registry-consumer.js";
|
|
285
|
+
|
|
286
|
+
// Codegen (exported from separate entrypoint — see ./codegen.ts)
|
|
287
|
+
// TODO: Re-enable once codegen.ts type errors are fixed
|
|
288
|
+
// export { codegen, useAgent, listAgentTools } from "./codegen.js";
|
|
289
|
+
// export type {
|
|
290
|
+
// CodegenOptions,
|
|
291
|
+
// CodegenResult,
|
|
292
|
+
// CodegenManifest,
|
|
293
|
+
// McpToolDefinition,
|
|
294
|
+
// McpServerInfo,
|
|
295
|
+
// McpTransport,
|
|
296
|
+
// ServerSource,
|
|
297
|
+
// } from "./codegen.js";
|
|
@@ -5,16 +5,16 @@
|
|
|
5
5
|
* They are all internal visibility and only callable by @integrations.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { defineTool } from
|
|
9
|
-
import type { IntegrationsStore } from
|
|
10
|
-
import type {
|
|
8
|
+
import { defineTool } from "./define.js";
|
|
9
|
+
import type { IntegrationsStore } from "./integrations-store.js";
|
|
10
|
+
import type { ToolContext, ToolDefinition } from "./types.js";
|
|
11
11
|
|
|
12
12
|
export interface IntegrationDefinition {
|
|
13
13
|
id: string;
|
|
14
14
|
agentPath: string;
|
|
15
15
|
name: string;
|
|
16
16
|
description: string;
|
|
17
|
-
type:
|
|
17
|
+
type: "oauth" | "credentials" | "config";
|
|
18
18
|
configSchema?: Record<string, unknown>;
|
|
19
19
|
}
|
|
20
20
|
|
|
@@ -22,23 +22,36 @@ export interface IntegrationInterfaceConfig {
|
|
|
22
22
|
agentPath: string;
|
|
23
23
|
store: IntegrationsStore;
|
|
24
24
|
discover: () => Promise<IntegrationDefinition[]>;
|
|
25
|
-
setup: (
|
|
26
|
-
|
|
25
|
+
setup: (
|
|
26
|
+
config: Record<string, unknown>,
|
|
27
|
+
ctx: ToolContext,
|
|
28
|
+
) => Promise<{
|
|
29
|
+
success: boolean;
|
|
30
|
+
integrationId?: string;
|
|
31
|
+
oauthUrl?: string;
|
|
32
|
+
error?: string;
|
|
33
|
+
}>;
|
|
34
|
+
connect?: (
|
|
35
|
+
integrationId: string,
|
|
36
|
+
ctx: ToolContext,
|
|
37
|
+
) => Promise<{ success: boolean; error?: string }>;
|
|
27
38
|
}
|
|
28
39
|
|
|
29
40
|
/**
|
|
30
41
|
* Create the standard _integration tools for an agent.
|
|
31
42
|
* Returns an array of ToolDefinitions to include in the agent's tools.
|
|
32
43
|
*/
|
|
33
|
-
export function createIntegrationTools(
|
|
44
|
+
export function createIntegrationTools(
|
|
45
|
+
config: IntegrationInterfaceConfig,
|
|
46
|
+
): ToolDefinition<ToolContext>[] {
|
|
34
47
|
const { agentPath, store, discover, setup, connect } = config;
|
|
35
48
|
|
|
36
49
|
const discoverTool = defineTool({
|
|
37
|
-
name:
|
|
50
|
+
name: "discover_integrations",
|
|
38
51
|
description: `Discover available integrations for ${agentPath}.`,
|
|
39
|
-
visibility:
|
|
52
|
+
visibility: "internal" as const,
|
|
40
53
|
inputSchema: {
|
|
41
|
-
type:
|
|
54
|
+
type: "object" as const,
|
|
42
55
|
properties: {},
|
|
43
56
|
},
|
|
44
57
|
execute: async () => {
|
|
@@ -48,17 +61,20 @@ export function createIntegrationTools(config: IntegrationInterfaceConfig): Tool
|
|
|
48
61
|
});
|
|
49
62
|
|
|
50
63
|
const setupTool = defineTool({
|
|
51
|
-
name:
|
|
64
|
+
name: "setup_integration",
|
|
52
65
|
description: `Set up a new integration for ${agentPath}.`,
|
|
53
|
-
visibility:
|
|
66
|
+
visibility: "internal" as const,
|
|
54
67
|
inputSchema: {
|
|
55
|
-
type:
|
|
68
|
+
type: "object" as const,
|
|
56
69
|
properties: {
|
|
57
|
-
config: { type:
|
|
70
|
+
config: { type: "object", description: "Integration configuration" },
|
|
58
71
|
},
|
|
59
|
-
required: [
|
|
72
|
+
required: ["config"],
|
|
60
73
|
},
|
|
61
|
-
execute: async (
|
|
74
|
+
execute: async (
|
|
75
|
+
input: { config: Record<string, unknown> },
|
|
76
|
+
ctx: ToolContext,
|
|
77
|
+
) => {
|
|
62
78
|
const result = await setup(input.config, ctx);
|
|
63
79
|
if (result.success && !result.oauthUrl) {
|
|
64
80
|
// Direct setup (no OAuth needed) — create integration row
|
|
@@ -74,23 +90,26 @@ export function createIntegrationTools(config: IntegrationInterfaceConfig): Tool
|
|
|
74
90
|
});
|
|
75
91
|
|
|
76
92
|
const connectTool = defineTool({
|
|
77
|
-
name:
|
|
93
|
+
name: "connect_integration",
|
|
78
94
|
description: `Test or authorize a ${agentPath} integration connection.`,
|
|
79
|
-
visibility:
|
|
95
|
+
visibility: "internal" as const,
|
|
80
96
|
inputSchema: {
|
|
81
|
-
type:
|
|
97
|
+
type: "object" as const,
|
|
82
98
|
properties: {
|
|
83
|
-
integration_id: {
|
|
99
|
+
integration_id: {
|
|
100
|
+
type: "string",
|
|
101
|
+
description: "Integration ID to connect",
|
|
102
|
+
},
|
|
84
103
|
},
|
|
85
|
-
required: [
|
|
104
|
+
required: ["integration_id"],
|
|
86
105
|
},
|
|
87
106
|
execute: async (input: { integration_id: string }, ctx: ToolContext) => {
|
|
88
107
|
if (connect) {
|
|
89
108
|
const result = await connect(input.integration_id, ctx);
|
|
90
109
|
if (result.success) {
|
|
91
|
-
await store.update(input.integration_id, { status:
|
|
110
|
+
await store.update(input.integration_id, { status: "active" });
|
|
92
111
|
} else {
|
|
93
|
-
await store.update(input.integration_id, { status:
|
|
112
|
+
await store.update(input.integration_id, { status: "error" });
|
|
94
113
|
}
|
|
95
114
|
return result;
|
|
96
115
|
}
|
|
@@ -99,13 +118,13 @@ export function createIntegrationTools(config: IntegrationInterfaceConfig): Tool
|
|
|
99
118
|
});
|
|
100
119
|
|
|
101
120
|
const listTool = defineTool({
|
|
102
|
-
name:
|
|
121
|
+
name: "list_integrations",
|
|
103
122
|
description: `List installed integrations for ${agentPath}.`,
|
|
104
|
-
visibility:
|
|
123
|
+
visibility: "internal" as const,
|
|
105
124
|
inputSchema: {
|
|
106
|
-
type:
|
|
125
|
+
type: "object" as const,
|
|
107
126
|
properties: {
|
|
108
|
-
tenant_id: { type:
|
|
127
|
+
tenant_id: { type: "string", description: "Filter by tenant" },
|
|
109
128
|
},
|
|
110
129
|
},
|
|
111
130
|
execute: async (input: { tenant_id?: string }) => {
|
|
@@ -114,5 +133,10 @@ export function createIntegrationTools(config: IntegrationInterfaceConfig): Tool
|
|
|
114
133
|
},
|
|
115
134
|
});
|
|
116
135
|
|
|
117
|
-
return [
|
|
136
|
+
return [
|
|
137
|
+
discoverTool,
|
|
138
|
+
setupTool,
|
|
139
|
+
connectTool,
|
|
140
|
+
listTool,
|
|
141
|
+
] as ToolDefinition<ToolContext>[];
|
|
118
142
|
}
|
|
@@ -9,7 +9,7 @@ export interface Integration {
|
|
|
9
9
|
id: string;
|
|
10
10
|
agentPath: string;
|
|
11
11
|
tenantId?: string;
|
|
12
|
-
status:
|
|
12
|
+
status: "active" | "disabled" | "error";
|
|
13
13
|
config: Record<string, unknown>;
|
|
14
14
|
installedBy?: string;
|
|
15
15
|
installedAt: number;
|
|
@@ -28,7 +28,10 @@ export interface IntegrationsStore {
|
|
|
28
28
|
get(id: string): Promise<Integration | null>;
|
|
29
29
|
list(tenantId?: string): Promise<Integration[]>;
|
|
30
30
|
listByAgent(agentPath: string, tenantId?: string): Promise<Integration[]>;
|
|
31
|
-
update(
|
|
31
|
+
update(
|
|
32
|
+
id: string,
|
|
33
|
+
updates: Partial<Pick<Integration, "status" | "config" | "updatedAt">>,
|
|
34
|
+
): Promise<Integration | null>;
|
|
32
35
|
delete(id: string): Promise<boolean>;
|
|
33
36
|
}
|
|
34
37
|
|
|
@@ -44,7 +47,7 @@ export function createInMemoryIntegrationsStore(): IntegrationsStore {
|
|
|
44
47
|
id,
|
|
45
48
|
agentPath: input.agentPath,
|
|
46
49
|
tenantId: input.tenantId,
|
|
47
|
-
status:
|
|
50
|
+
status: "active",
|
|
48
51
|
config: input.config,
|
|
49
52
|
installedBy: input.installedBy,
|
|
50
53
|
installedAt: now,
|
|
@@ -60,12 +63,13 @@ export function createInMemoryIntegrationsStore(): IntegrationsStore {
|
|
|
60
63
|
|
|
61
64
|
async list(tenantId?) {
|
|
62
65
|
const all = Array.from(integrations.values());
|
|
63
|
-
return tenantId ? all.filter(i => i.tenantId === tenantId) : all;
|
|
66
|
+
return tenantId ? all.filter((i) => i.tenantId === tenantId) : all;
|
|
64
67
|
},
|
|
65
68
|
|
|
66
69
|
async listByAgent(agentPath, tenantId?) {
|
|
67
70
|
return Array.from(integrations.values()).filter(
|
|
68
|
-
i =>
|
|
71
|
+
(i) =>
|
|
72
|
+
i.agentPath === agentPath && (!tenantId || i.tenantId === tenantId),
|
|
69
73
|
);
|
|
70
74
|
},
|
|
71
75
|
|
package/src/jwt.ts
CHANGED
|
@@ -9,15 +9,14 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import {
|
|
12
|
+
type JWK,
|
|
13
|
+
type JWTPayload,
|
|
12
14
|
SignJWT,
|
|
13
|
-
|
|
14
|
-
generateKeyPair,
|
|
15
|
+
createRemoteJWKSet,
|
|
15
16
|
exportJWK,
|
|
17
|
+
generateKeyPair,
|
|
16
18
|
importJWK,
|
|
17
|
-
|
|
18
|
-
type JWTPayload,
|
|
19
|
-
|
|
20
|
-
type JWK,
|
|
19
|
+
jwtVerify,
|
|
21
20
|
} from "jose";
|
|
22
21
|
|
|
23
22
|
// ============================================
|
|
@@ -80,7 +79,9 @@ export interface ExportedKeyPair {
|
|
|
80
79
|
* Generate a new ES256 signing key pair.
|
|
81
80
|
*/
|
|
82
81
|
export async function generateSigningKey(kid?: string): Promise<SigningKey> {
|
|
83
|
-
const { privateKey, publicKey } = await generateKeyPair("ES256", {
|
|
82
|
+
const { privateKey, publicKey } = await generateKeyPair("ES256", {
|
|
83
|
+
extractable: true,
|
|
84
|
+
});
|
|
84
85
|
return {
|
|
85
86
|
kid: kid ?? `key-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
86
87
|
privateKey,
|
|
@@ -94,7 +95,9 @@ export async function generateSigningKey(kid?: string): Promise<SigningKey> {
|
|
|
94
95
|
/**
|
|
95
96
|
* Export a signing key to JWK format (for storage).
|
|
96
97
|
*/
|
|
97
|
-
export async function exportSigningKey(
|
|
98
|
+
export async function exportSigningKey(
|
|
99
|
+
key: SigningKey,
|
|
100
|
+
): Promise<ExportedKeyPair> {
|
|
98
101
|
const privateKeyJwk = await exportJWK(key.privateKey);
|
|
99
102
|
const publicKeyJwk = await exportJWK(key.publicKey);
|
|
100
103
|
return {
|
|
@@ -110,9 +113,17 @@ export async function exportSigningKey(key: SigningKey): Promise<ExportedKeyPair
|
|
|
110
113
|
/**
|
|
111
114
|
* Import a signing key from stored JWK format.
|
|
112
115
|
*/
|
|
113
|
-
export async function importSigningKey(
|
|
114
|
-
|
|
115
|
-
|
|
116
|
+
export async function importSigningKey(
|
|
117
|
+
exported: ExportedKeyPair,
|
|
118
|
+
): Promise<SigningKey> {
|
|
119
|
+
const privateKey = (await importJWK(
|
|
120
|
+
exported.privateKeyJwk,
|
|
121
|
+
exported.alg,
|
|
122
|
+
)) as CryptoKey;
|
|
123
|
+
const publicKey = (await importJWK(
|
|
124
|
+
exported.publicKeyJwk,
|
|
125
|
+
exported.alg,
|
|
126
|
+
)) as CryptoKey;
|
|
116
127
|
return {
|
|
117
128
|
kid: exported.kid,
|
|
118
129
|
privateKey,
|
|
@@ -148,7 +159,10 @@ export async function buildJwks(keys: SigningKey[]): Promise<{ keys: JWK[] }> {
|
|
|
148
159
|
* Sign a JWT with ES256 using the server's private key.
|
|
149
160
|
*/
|
|
150
161
|
export async function signJwtES256(
|
|
151
|
-
payload: Omit<AgentJwtPayload, "iat" | "exp"> & {
|
|
162
|
+
payload: Omit<AgentJwtPayload, "iat" | "exp"> & {
|
|
163
|
+
iat?: number;
|
|
164
|
+
exp?: number;
|
|
165
|
+
},
|
|
152
166
|
privateKey: CryptoKey,
|
|
153
167
|
kid: string,
|
|
154
168
|
issuer?: string,
|
|
@@ -202,7 +216,7 @@ export async function verifyJwtFromIssuer(
|
|
|
202
216
|
issuerUrl: string,
|
|
203
217
|
): Promise<AgentJwtPayload | null> {
|
|
204
218
|
try {
|
|
205
|
-
const jwksUrl = issuerUrl.replace(/\/$/, "")
|
|
219
|
+
const jwksUrl = `${issuerUrl.replace(/\/$/, "")}/.well-known/jwks.json`;
|
|
206
220
|
let jwks = jwksCache.get(jwksUrl);
|
|
207
221
|
if (!jwks) {
|
|
208
222
|
jwks = createRemoteJWKSet(new URL(jwksUrl));
|
|
@@ -234,19 +248,34 @@ function base64UrlDecode(str: string): Uint8Array {
|
|
|
234
248
|
|
|
235
249
|
async function hmacSign(data: string, secret: string): Promise<Uint8Array> {
|
|
236
250
|
const key = await crypto.subtle.importKey(
|
|
237
|
-
"raw",
|
|
238
|
-
|
|
251
|
+
"raw",
|
|
252
|
+
encoder.encode(secret),
|
|
253
|
+
{ name: "HMAC", hash: "SHA-256" },
|
|
254
|
+
false,
|
|
255
|
+
["sign"],
|
|
239
256
|
);
|
|
240
257
|
const sig = await crypto.subtle.sign("HMAC", key, encoder.encode(data));
|
|
241
258
|
return new Uint8Array(sig);
|
|
242
259
|
}
|
|
243
260
|
|
|
244
|
-
async function hmacVerify(
|
|
261
|
+
async function hmacVerify(
|
|
262
|
+
data: string,
|
|
263
|
+
signature: Uint8Array,
|
|
264
|
+
secret: string,
|
|
265
|
+
): Promise<boolean> {
|
|
245
266
|
const key = await crypto.subtle.importKey(
|
|
246
|
-
"raw",
|
|
247
|
-
|
|
267
|
+
"raw",
|
|
268
|
+
encoder.encode(secret),
|
|
269
|
+
{ name: "HMAC", hash: "SHA-256" },
|
|
270
|
+
false,
|
|
271
|
+
["verify"],
|
|
272
|
+
);
|
|
273
|
+
return crypto.subtle.verify(
|
|
274
|
+
"HMAC",
|
|
275
|
+
key,
|
|
276
|
+
signature.buffer as ArrayBuffer,
|
|
277
|
+
encoder.encode(data),
|
|
248
278
|
);
|
|
249
|
-
return crypto.subtle.verify("HMAC", key, signature.buffer as ArrayBuffer, encoder.encode(data));
|
|
250
279
|
}
|
|
251
280
|
|
|
252
281
|
/** @deprecated Use AgentJwtPayload instead */
|
package/src/key-manager.test.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { afterEach, describe, expect, test } from "bun:test";
|
|
2
|
+
import { createLocalJWKSet, jwtVerify } from "jose";
|
|
3
|
+
import {
|
|
4
|
+
type KeyManager,
|
|
5
|
+
type KeyStore,
|
|
6
|
+
type StoredKey,
|
|
7
|
+
createKeyManager,
|
|
8
|
+
} from "./key-manager";
|
|
4
9
|
|
|
5
10
|
// In-memory KeyStore for testing (no DB needed)
|
|
6
11
|
function createMemoryKeyStore(): KeyStore & { keys: StoredKey[] } {
|
|
@@ -76,9 +81,7 @@ describe("KeyManager", () => {
|
|
|
76
81
|
const parts = token.split(".");
|
|
77
82
|
expect(parts).toHaveLength(3);
|
|
78
83
|
|
|
79
|
-
const payload = JSON.parse(
|
|
80
|
-
Buffer.from(parts[1], "base64url").toString()
|
|
81
|
-
);
|
|
84
|
+
const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString());
|
|
82
85
|
expect(payload.sub).toBe("test-service");
|
|
83
86
|
expect(payload.iss).toBe("http://test:3000");
|
|
84
87
|
expect(payload.exp - payload.iat).toBe(300); // default 5 min TTL
|
|
@@ -110,7 +113,7 @@ describe("KeyManager", () => {
|
|
|
110
113
|
|
|
111
114
|
const token = await km.signJwt({ sub: "short-lived" });
|
|
112
115
|
const payload = JSON.parse(
|
|
113
|
-
Buffer.from(token.split(".")[1], "base64url").toString()
|
|
116
|
+
Buffer.from(token.split(".")[1], "base64url").toString(),
|
|
114
117
|
);
|
|
115
118
|
expect(payload.exp - payload.iat).toBe(60);
|
|
116
119
|
});
|
|
@@ -126,9 +129,11 @@ describe("KeyManager", () => {
|
|
|
126
129
|
checkIntervalMs: 60_000,
|
|
127
130
|
});
|
|
128
131
|
|
|
129
|
-
const initialKid = km
|
|
130
|
-
|
|
131
|
-
|
|
132
|
+
const initialKid = km
|
|
133
|
+
.getJwks()
|
|
134
|
+
.keys.find(
|
|
135
|
+
(k) => store.keys.find((sk) => sk.kid === k.kid)?.status === "active",
|
|
136
|
+
)?.kid;
|
|
132
137
|
|
|
133
138
|
// Force rotation
|
|
134
139
|
await km.rotate();
|
|
@@ -227,7 +232,7 @@ describe("KeyManager", () => {
|
|
|
227
232
|
expect(failed).toBe(true);
|
|
228
233
|
});
|
|
229
234
|
|
|
230
|
-
|
|
235
|
+
// ---- Transaction tests ----
|
|
231
236
|
|
|
232
237
|
test("transaction: rotate is atomic (rollback on failure)", async () => {
|
|
233
238
|
const store = createMemoryKeyStore();
|
|
@@ -241,10 +246,14 @@ describe("KeyManager", () => {
|
|
|
241
246
|
|
|
242
247
|
// Monkey-patch insertKey to fail mid-transaction
|
|
243
248
|
const origInsert = store.insertKey;
|
|
244
|
-
store.insertKey = async () => {
|
|
249
|
+
store.insertKey = async () => {
|
|
250
|
+
throw new Error("simulated failure");
|
|
251
|
+
};
|
|
245
252
|
|
|
246
253
|
// Rotation should fail, but state should be rolled back
|
|
247
|
-
try {
|
|
254
|
+
try {
|
|
255
|
+
await km.rotate();
|
|
256
|
+
} catch {}
|
|
248
257
|
|
|
249
258
|
// Original key should still be active (not deprecated)
|
|
250
259
|
const active = store.keys.filter((k) => k.status === "active");
|
package/src/key-manager.ts
CHANGED
|
@@ -13,13 +13,7 @@
|
|
|
13
13
|
* - Signs JWTs with the active key
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
import {
|
|
17
|
-
generateKeyPair,
|
|
18
|
-
exportJWK,
|
|
19
|
-
importJWK,
|
|
20
|
-
SignJWT,
|
|
21
|
-
type JWK,
|
|
22
|
-
} from "jose";
|
|
16
|
+
import { type JWK, SignJWT, exportJWK, generateKeyPair, importJWK } from "jose";
|
|
23
17
|
|
|
24
18
|
// ── Types ──
|
|
25
19
|
|
|
@@ -103,7 +97,9 @@ function generateKid(): string {
|
|
|
103
97
|
}
|
|
104
98
|
|
|
105
99
|
async function generateNewKey(keyLifetimeMs: number): Promise<StoredKey> {
|
|
106
|
-
const { privateKey, publicKey } = await generateKeyPair(ALG, {
|
|
100
|
+
const { privateKey, publicKey } = await generateKeyPair(ALG, {
|
|
101
|
+
extractable: true,
|
|
102
|
+
});
|
|
107
103
|
const kid = generateKid();
|
|
108
104
|
|
|
109
105
|
const publicJwk = await exportJWK(publicKey);
|
|
@@ -127,13 +123,15 @@ async function generateNewKey(keyLifetimeMs: number): Promise<StoredKey> {
|
|
|
127
123
|
}
|
|
128
124
|
|
|
129
125
|
async function toCachedKey(stored: StoredKey): Promise<CachedKey> {
|
|
130
|
-
const privateKey = await importJWK(stored.privateJwk, ALG) as CryptoKey;
|
|
126
|
+
const privateKey = (await importJWK(stored.privateJwk, ALG)) as CryptoKey;
|
|
131
127
|
return { ...stored, privateKey };
|
|
132
128
|
}
|
|
133
129
|
|
|
134
130
|
// ── Key Manager ──
|
|
135
131
|
|
|
136
|
-
export async function createKeyManager(
|
|
132
|
+
export async function createKeyManager(
|
|
133
|
+
opts: KeyManagerOptions,
|
|
134
|
+
): Promise<KeyManager> {
|
|
137
135
|
const {
|
|
138
136
|
store,
|
|
139
137
|
issuer,
|