@slashfi/agents-sdk 0.11.2 → 0.13.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 +17 -1
- package/dist/agent-definitions/auth.d.ts.map +1 -1
- package/dist/agent-definitions/auth.js +123 -4
- package/dist/agent-definitions/auth.js.map +1 -1
- package/dist/agent-definitions/integrations.d.ts +2 -14
- package/dist/agent-definitions/integrations.d.ts.map +1 -1
- package/dist/agent-definitions/integrations.js +64 -19
- package/dist/agent-definitions/integrations.js.map +1 -1
- package/dist/agent-definitions/remote-registry.d.ts +19 -14
- package/dist/agent-definitions/remote-registry.d.ts.map +1 -1
- package/dist/agent-definitions/remote-registry.js +219 -381
- package/dist/agent-definitions/remote-registry.js.map +1 -1
- package/dist/agent-definitions/users.d.ts.map +1 -1
- package/dist/agent-definitions/users.js +29 -1
- package/dist/agent-definitions/users.js.map +1 -1
- package/dist/define.d.ts +6 -4
- package/dist/define.d.ts.map +1 -1
- package/dist/define.js +82 -3
- package/dist/define.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/jwt.js +1 -1
- package/dist/jwt.js.map +1 -1
- package/dist/key-manager.d.ts +76 -0
- package/dist/key-manager.d.ts.map +1 -0
- package/dist/key-manager.js +156 -0
- package/dist/key-manager.js.map +1 -0
- package/dist/server.d.ts +39 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +228 -52
- package/dist/server.js.map +1 -1
- package/dist/types.d.ts +53 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/agent-definitions/auth.ts +165 -6
- package/src/agent-definitions/integrations.ts +59 -28
- package/src/agent-definitions/remote-registry.ts +219 -513
- package/src/agent-definitions/users.ts +35 -1
- package/src/define.ts +98 -6
- package/src/index.ts +3 -1
- package/src/jwt.ts +1 -1
- package/src/key-manager.test.ts +273 -0
- package/src/key-manager.ts +257 -0
- package/src/server.test.ts +284 -0
- package/src/server.ts +334 -60
- package/src/types.ts +44 -1
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JWKS Key Manager
|
|
3
|
+
*
|
|
4
|
+
* Manages ES256 signing keys with automatic rotation and revocation.
|
|
5
|
+
* Store-agnostic — provide a KeyStore implementation for your DB.
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Automatic key rotation on a configurable schedule
|
|
9
|
+
* - Key lifecycle: active → deprecated → revoked → cleaned up
|
|
10
|
+
* - Multi-instance safe: checks DB before rotating (another instance may have already rotated)
|
|
11
|
+
* - Periodic background checks (configurable interval)
|
|
12
|
+
* - Exposes JWKS for /.well-known/jwks.json
|
|
13
|
+
* - Signs JWTs with the active key
|
|
14
|
+
*/
|
|
15
|
+
import { generateKeyPair, exportJWK, importJWK, SignJWT, } from "jose";
|
|
16
|
+
// ── Constants ──
|
|
17
|
+
const FIVE_MINUTES = 5 * 60 * 1000;
|
|
18
|
+
const ONE_HOUR = 60 * 60 * 1000;
|
|
19
|
+
const TWO_HOURS = 2 * ONE_HOUR;
|
|
20
|
+
const ALG = "ES256";
|
|
21
|
+
// ── Key generation ──
|
|
22
|
+
function generateKid() {
|
|
23
|
+
return `key-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
24
|
+
}
|
|
25
|
+
async function generateNewKey(keyLifetimeMs) {
|
|
26
|
+
const { privateKey, publicKey } = await generateKeyPair(ALG, { extractable: true });
|
|
27
|
+
const kid = generateKid();
|
|
28
|
+
const publicJwk = await exportJWK(publicKey);
|
|
29
|
+
publicJwk.kid = kid;
|
|
30
|
+
publicJwk.alg = ALG;
|
|
31
|
+
publicJwk.use = "sig";
|
|
32
|
+
const privateJwk = await exportJWK(privateKey);
|
|
33
|
+
privateJwk.kid = kid;
|
|
34
|
+
privateJwk.alg = ALG;
|
|
35
|
+
return {
|
|
36
|
+
kid,
|
|
37
|
+
alg: ALG,
|
|
38
|
+
status: "active",
|
|
39
|
+
publicJwk,
|
|
40
|
+
privateJwk,
|
|
41
|
+
createdAt: new Date(),
|
|
42
|
+
expiresAt: new Date(Date.now() + keyLifetimeMs),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
async function toCachedKey(stored) {
|
|
46
|
+
const privateKey = await importJWK(stored.privateJwk, ALG);
|
|
47
|
+
return { ...stored, privateKey };
|
|
48
|
+
}
|
|
49
|
+
// ── Key Manager ──
|
|
50
|
+
export async function createKeyManager(opts) {
|
|
51
|
+
const { store, issuer, checkIntervalMs = FIVE_MINUTES, rotationThresholdMs = ONE_HOUR, keyLifetimeMs = TWO_HOURS, tokenTtlSeconds = 300, enableRotation = true, } = opts;
|
|
52
|
+
let keys = [];
|
|
53
|
+
/** Generate a new key, deprecate old ones, cleanup expired, refresh cache — all in one transaction */
|
|
54
|
+
async function rotate() {
|
|
55
|
+
await store.transaction(async () => {
|
|
56
|
+
const newKey = await generateNewKey(keyLifetimeMs);
|
|
57
|
+
await store.deprecateAllActive();
|
|
58
|
+
await store.insertKey(newKey);
|
|
59
|
+
await store.cleanupExpired();
|
|
60
|
+
const updated = await store.loadKeys();
|
|
61
|
+
keys = await Promise.all(updated.map(toCachedKey));
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
/** Check if rotation is needed and rotate if so — all within a single transaction */
|
|
65
|
+
async function checkAndRotate() {
|
|
66
|
+
// Quick check against cache — no DB/store hit if key is fresh
|
|
67
|
+
const cached = keys.find((k) => k.status === "active");
|
|
68
|
+
if (cached) {
|
|
69
|
+
const age = Date.now() - cached.createdAt.getTime();
|
|
70
|
+
if (age < rotationThresholdMs)
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
// Cache says stale (or empty) — take a lock via transaction to check + rotate atomically
|
|
74
|
+
await store.transaction(async () => {
|
|
75
|
+
const stored = await store.loadKeys();
|
|
76
|
+
const active = stored.find((k) => k.status === "active");
|
|
77
|
+
if (active) {
|
|
78
|
+
const age = Date.now() - active.createdAt.getTime();
|
|
79
|
+
if (age < rotationThresholdMs) {
|
|
80
|
+
// Another instance already rotated — just update our cache
|
|
81
|
+
keys = await Promise.all(stored.map(toCachedKey));
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
// Still stale (or no active key) — rotate within this tx
|
|
86
|
+
const newKey = await generateNewKey(keyLifetimeMs);
|
|
87
|
+
await store.deprecateAllActive();
|
|
88
|
+
await store.insertKey(newKey);
|
|
89
|
+
await store.cleanupExpired();
|
|
90
|
+
// Refresh cache inside the tx for a consistent read
|
|
91
|
+
const updated = await store.loadKeys();
|
|
92
|
+
keys = await Promise.all(updated.map(toCachedKey));
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
// Initial load + ensure we have at least one key
|
|
96
|
+
// Initial load from store
|
|
97
|
+
const stored = await store.loadKeys();
|
|
98
|
+
keys = await Promise.all(stored.map(toCachedKey));
|
|
99
|
+
if (!keys.some((k) => k.status === "active")) {
|
|
100
|
+
if (enableRotation) {
|
|
101
|
+
await rotate();
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
// Read-only mode: generate a key in memory only (no store writes)
|
|
105
|
+
// This ensures signJwt works even without rotation enabled
|
|
106
|
+
const newKey = await generateNewKey(keyLifetimeMs);
|
|
107
|
+
keys.push(await toCachedKey(newKey));
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
else if (enableRotation) {
|
|
111
|
+
await checkAndRotate();
|
|
112
|
+
}
|
|
113
|
+
// Periodic background check (only if rotation enabled)
|
|
114
|
+
const interval = enableRotation
|
|
115
|
+
? setInterval(async () => {
|
|
116
|
+
try {
|
|
117
|
+
await checkAndRotate();
|
|
118
|
+
}
|
|
119
|
+
catch (err) {
|
|
120
|
+
console.error("[key-manager] Check/rotation failed:", err);
|
|
121
|
+
}
|
|
122
|
+
}, checkIntervalMs)
|
|
123
|
+
: null;
|
|
124
|
+
function getActiveKey() {
|
|
125
|
+
const active = keys.find((k) => k.status === "active");
|
|
126
|
+
if (!active)
|
|
127
|
+
throw new Error("[key-manager] No active signing key");
|
|
128
|
+
return active;
|
|
129
|
+
}
|
|
130
|
+
return {
|
|
131
|
+
getJwks() {
|
|
132
|
+
return {
|
|
133
|
+
keys: keys
|
|
134
|
+
.filter((k) => k.status !== "revoked")
|
|
135
|
+
.map((k) => k.publicJwk),
|
|
136
|
+
};
|
|
137
|
+
},
|
|
138
|
+
async signJwt(claims) {
|
|
139
|
+
const key = getActiveKey();
|
|
140
|
+
return new SignJWT({ ...claims })
|
|
141
|
+
.setProtectedHeader({ alg: ALG, kid: key.kid })
|
|
142
|
+
.setIssuer(issuer)
|
|
143
|
+
.setIssuedAt()
|
|
144
|
+
.setExpirationTime(`${tokenTtlSeconds}s`)
|
|
145
|
+
.sign(key.privateKey);
|
|
146
|
+
},
|
|
147
|
+
async rotate() {
|
|
148
|
+
await rotate();
|
|
149
|
+
},
|
|
150
|
+
stop() {
|
|
151
|
+
if (interval)
|
|
152
|
+
clearInterval(interval);
|
|
153
|
+
},
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
//# sourceMappingURL=key-manager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"key-manager.js","sourceRoot":"","sources":["../src/key-manager.ts"],"names":[],"mappings":"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"}
|
package/dist/server.d.ts
CHANGED
|
@@ -36,6 +36,34 @@ export interface TrustedIssuer {
|
|
|
36
36
|
/** Scopes granted to tokens from this issuer */
|
|
37
37
|
scopes: string[];
|
|
38
38
|
}
|
|
39
|
+
/** OAuth identity provider for /oauth/authorize + /oauth/callback flows */
|
|
40
|
+
export interface OAuthIdentityProvider {
|
|
41
|
+
/**
|
|
42
|
+
* Handle /oauth/authorize — redirect the user to an external IdP.
|
|
43
|
+
* Return a Response (typically a 302 redirect).
|
|
44
|
+
*/
|
|
45
|
+
authorize(req: Request, params: {
|
|
46
|
+
/** The verified JWT from the foreign registry */
|
|
47
|
+
token: string;
|
|
48
|
+
/** Claims from the verified JWT */
|
|
49
|
+
claims: Record<string, unknown>;
|
|
50
|
+
/** Where to redirect after completion */
|
|
51
|
+
redirectUri: string;
|
|
52
|
+
/** Base URL of this server */
|
|
53
|
+
baseUrl: string;
|
|
54
|
+
/** OAuth scope (e.g. "setup" for tenant creation flow) */
|
|
55
|
+
scope?: string;
|
|
56
|
+
}): Promise<Response>;
|
|
57
|
+
/**
|
|
58
|
+
* Handle /oauth/callback — process the IdP response.
|
|
59
|
+
* Should link the foreign identity to a local user and redirect back.
|
|
60
|
+
* Return a Response (typically a 302 redirect to redirectUri).
|
|
61
|
+
*/
|
|
62
|
+
callback(req: Request, params: {
|
|
63
|
+
/** Base URL of this server */
|
|
64
|
+
baseUrl: string;
|
|
65
|
+
}): Promise<Response>;
|
|
66
|
+
}
|
|
39
67
|
export interface AgentServerOptions {
|
|
40
68
|
/** Port to listen on (default: 3000) */
|
|
41
69
|
port?: number;
|
|
@@ -55,8 +83,14 @@ export interface AgentServerOptions {
|
|
|
55
83
|
trustedIssuers?: (TrustedIssuer | string)[];
|
|
56
84
|
/** Pre-generated signing key (if not provided, one is generated on start) */
|
|
57
85
|
signingKey?: SigningKey;
|
|
86
|
+
/** OAuth identity provider for cross-registry user linking */
|
|
87
|
+
oauthIdentityProvider?: OAuthIdentityProvider;
|
|
88
|
+
/** Key store for managed key rotation (if provided, uses createKeyManager instead of simple key gen) */
|
|
89
|
+
keyStore?: import("./key-manager.js").KeyStore;
|
|
58
90
|
}
|
|
59
91
|
export interface AgentServer {
|
|
92
|
+
/** Initialize signing keys without starting HTTP server */
|
|
93
|
+
initKeys(): Promise<void>;
|
|
60
94
|
/** Start the server */
|
|
61
95
|
start(): Promise<void>;
|
|
62
96
|
/** Stop the server */
|
|
@@ -67,6 +101,10 @@ export interface AgentServer {
|
|
|
67
101
|
url: string | null;
|
|
68
102
|
/** The agent registry this server uses */
|
|
69
103
|
registry: AgentRegistry;
|
|
104
|
+
/** Sign a JWT with the server's signing key (for outbound calls) */
|
|
105
|
+
signJwt(claims: Record<string, unknown>): Promise<string>;
|
|
106
|
+
/** Dynamically add a trusted JWT issuer at runtime */
|
|
107
|
+
addTrustedIssuer(issuerUrl: string, scopes?: string[]): void;
|
|
70
108
|
}
|
|
71
109
|
export interface AuthConfig {
|
|
72
110
|
store?: AuthStore;
|
|
@@ -74,6 +112,7 @@ export interface AuthConfig {
|
|
|
74
112
|
tokenTtl?: number;
|
|
75
113
|
}
|
|
76
114
|
export interface ResolvedAuth {
|
|
115
|
+
issuer?: string;
|
|
77
116
|
callerId: string;
|
|
78
117
|
callerType: "agent" | "user" | "system";
|
|
79
118
|
scopes: string[];
|
package/dist/server.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;AAC7D,OAAO,EACL,KAAK,WAAW,EAEjB,MAAM,gCAAgC,CAAC;AAExC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAE3C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,KAAK,EAAE,eAAe,EAAgC,MAAM,YAAY,CAAC;AAMhF,6DAA6D;AAC7D,MAAM,WAAW,aAAa;IAC5B,8DAA8D;IAC9D,MAAM,EAAE,MAAM,CAAC;IACf,gDAAgD;IAChD,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
|
package/dist/server.js
CHANGED
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
*/
|
|
27
27
|
import { processSecretParams, } from "./agent-definitions/secrets.js";
|
|
28
28
|
import { verifyJwt } from "./jwt.js";
|
|
29
|
-
import { generateSigningKey, importSigningKey, exportSigningKey, buildJwks, verifyJwtLocal, verifyJwtFromIssuer } from "./jwt.js";
|
|
29
|
+
import { generateSigningKey, importSigningKey, exportSigningKey, buildJwks, verifyJwtLocal, verifyJwtFromIssuer, signJwtES256 } from "./jwt.js";
|
|
30
30
|
// ============================================
|
|
31
31
|
// HTTP Helpers
|
|
32
32
|
// ============================================
|
|
@@ -257,7 +257,7 @@ function getToolDefinitions() {
|
|
|
257
257
|
// Create Server
|
|
258
258
|
// ============================================
|
|
259
259
|
export function createAgentServer(registry, options = {}) {
|
|
260
|
-
const { port = 3000, hostname = "localhost", basePath = "", cors = true, serverName = "agents-sdk", serverVersion = "1.0.0", secretStore, } = options;
|
|
260
|
+
const { port = 3000, hostname = "localhost", basePath = "", cors = true, serverName = "agents-sdk", serverVersion = "1.0.0", secretStore, oauthIdentityProvider, } = options;
|
|
261
261
|
// Signing keys for JWKS-based auth
|
|
262
262
|
const serverSigningKeys = [];
|
|
263
263
|
// Normalize trustedIssuers to TrustedIssuer objects
|
|
@@ -311,6 +311,8 @@ export function createAgentServer(registry, options = {}) {
|
|
|
311
311
|
req.metadata = {};
|
|
312
312
|
req.metadata.scopes = auth.scopes;
|
|
313
313
|
req.metadata.isRoot = auth.isRoot;
|
|
314
|
+
if (auth.issuer)
|
|
315
|
+
req.metadata.issuer = auth.issuer;
|
|
314
316
|
}
|
|
315
317
|
if (auth?.isRoot) {
|
|
316
318
|
req.callerType = "system";
|
|
@@ -362,62 +364,160 @@ export function createAgentServer(registry, options = {}) {
|
|
|
362
364
|
// ──────────────────────────────────────────
|
|
363
365
|
// OAuth2 token handler
|
|
364
366
|
// ──────────────────────────────────────────
|
|
367
|
+
// Resolve public-facing base URL, respecting reverse proxy headers
|
|
368
|
+
const resolveBaseUrl = (r) => {
|
|
369
|
+
const fwdProto = r.headers.get("x-forwarded-proto");
|
|
370
|
+
const fwdHost = r.headers.get("x-forwarded-host");
|
|
371
|
+
if (fwdProto && fwdHost)
|
|
372
|
+
return `${fwdProto}://${fwdHost}`;
|
|
373
|
+
return new URL(r.url).origin;
|
|
374
|
+
};
|
|
365
375
|
async function handleOAuthToken(req) {
|
|
366
376
|
if (!authConfig) {
|
|
367
377
|
return jsonResponse({ error: "auth_not_configured" }, 404);
|
|
368
378
|
}
|
|
369
379
|
const contentType = req.headers.get("Content-Type") ?? "";
|
|
370
|
-
let
|
|
371
|
-
let clientId;
|
|
372
|
-
let clientSecret;
|
|
380
|
+
let params;
|
|
373
381
|
if (contentType.includes("application/x-www-form-urlencoded")) {
|
|
374
382
|
const body = await req.text();
|
|
375
|
-
const
|
|
376
|
-
|
|
377
|
-
clientId = params.get("client_id") ?? "";
|
|
378
|
-
clientSecret = params.get("client_secret") ?? "";
|
|
383
|
+
const urlParams = new URLSearchParams(body);
|
|
384
|
+
params = Object.fromEntries(urlParams.entries());
|
|
379
385
|
}
|
|
380
386
|
else {
|
|
381
|
-
|
|
382
|
-
grantType = body.grant_type ?? "";
|
|
383
|
-
clientId = body.client_id ?? "";
|
|
384
|
-
clientSecret = body.client_secret ?? "";
|
|
385
|
-
}
|
|
386
|
-
if (grantType !== "client_credentials") {
|
|
387
|
-
return jsonResponse({
|
|
388
|
-
error: "unsupported_grant_type",
|
|
389
|
-
error_description: "Only client_credentials is supported",
|
|
390
|
-
}, 400);
|
|
391
|
-
}
|
|
392
|
-
if (!clientId || !clientSecret) {
|
|
393
|
-
return jsonResponse({
|
|
394
|
-
error: "invalid_request",
|
|
395
|
-
error_description: "Missing client_id or client_secret",
|
|
396
|
-
}, 400);
|
|
387
|
+
params = (await req.json());
|
|
397
388
|
}
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
389
|
+
const grantType = params.grant_type ?? "";
|
|
390
|
+
// ── jwt_exchange grant: verify foreign JWT, resolve local identity ──
|
|
391
|
+
if (grantType === "jwt_exchange") {
|
|
392
|
+
const assertion = params.assertion ?? "";
|
|
393
|
+
if (!assertion) {
|
|
394
|
+
return jsonResponse({ error: "invalid_request", error_description: "Missing assertion parameter" }, 400);
|
|
395
|
+
}
|
|
396
|
+
try {
|
|
397
|
+
const result = await registry.call({
|
|
398
|
+
action: "execute_tool",
|
|
399
|
+
path: "@auth",
|
|
400
|
+
tool: "exchange_token",
|
|
401
|
+
params: { token: assertion },
|
|
402
|
+
callerType: "system",
|
|
403
|
+
});
|
|
404
|
+
const exchangeResult = result?.result;
|
|
405
|
+
// If the tool call failed, forward the error
|
|
406
|
+
if (result?.success === false) {
|
|
407
|
+
return jsonResponse({ error: "server_error", error_description: result?.error ?? "Exchange tool failed", raw: JSON.stringify(result)?.slice(0, 300) }, 500);
|
|
408
|
+
}
|
|
409
|
+
// ── Reverse registration: if caller is an agent-registry, auto-store connection ──
|
|
410
|
+
try {
|
|
411
|
+
const assertionParts = assertion.split(".");
|
|
412
|
+
if (assertionParts.length === 3) {
|
|
413
|
+
const assertionPayload = JSON.parse(atob(assertionParts[1].replace(/-/g, "+").replace(/_/g, "/")));
|
|
414
|
+
if (assertionPayload.type === "agent-registry" && assertionPayload.iss && false /* disabled: causes infinite loop */) {
|
|
415
|
+
// Find or create @remote-registry agent and store the reverse connection
|
|
416
|
+
const rrAgent = registry.get("@remote-registry") ?? registry.get("/agents/@remote-registry");
|
|
417
|
+
if (rrAgent) {
|
|
418
|
+
const setupTool = rrAgent.tools?.find((t) => t.name === "setup_integration");
|
|
419
|
+
if (setupTool?.execute) {
|
|
420
|
+
try {
|
|
421
|
+
await setupTool.execute({ url: assertionPayload.iss, name: assertionPayload.name ?? "remote-registry" }, { callerId: "system", callerType: "system", tenantId: "default", agentPath: "@remote-registry" });
|
|
422
|
+
console.error(`[jwt_exchange] Reverse connection stored for ${assertionPayload.iss}`);
|
|
423
|
+
}
|
|
424
|
+
catch (setupErr) {
|
|
425
|
+
console.error(`[jwt_exchange] Reverse registration setup failed:`, setupErr);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
else {
|
|
429
|
+
console.error("[jwt_exchange] @remote-registry has no setup_integration tool — reverse registration skipped");
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
else {
|
|
433
|
+
console.error("[jwt_exchange] @remote-registry agent not found — reverse registration skipped");
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
catch (reverseErr) {
|
|
439
|
+
console.error("[jwt_exchange] Reverse registration check failed:", reverseErr);
|
|
440
|
+
}
|
|
441
|
+
if (!exchangeResult) {
|
|
442
|
+
return jsonResponse({ error: "server_error", error_description: `Exchange returned null: ${JSON.stringify(result)?.slice(0, 300)}` }, 500);
|
|
443
|
+
}
|
|
444
|
+
// User not linked yet — needs OAuth identity linking
|
|
445
|
+
if (exchangeResult.needsAuth) {
|
|
446
|
+
const baseUrl = resolveBaseUrl(req);
|
|
447
|
+
const authorizeUrl = new URL(`${baseUrl}${basePath}/oauth/authorize`);
|
|
448
|
+
authorizeUrl.searchParams.set("token", assertion);
|
|
449
|
+
if (params.redirect_uri) {
|
|
450
|
+
authorizeUrl.searchParams.set("redirect_uri", params.redirect_uri);
|
|
451
|
+
}
|
|
452
|
+
if (params.scope) {
|
|
453
|
+
authorizeUrl.searchParams.set("scope", params.scope);
|
|
454
|
+
}
|
|
455
|
+
return jsonResponse({
|
|
456
|
+
error: "identity_required",
|
|
457
|
+
error_description: "User identity not linked. Redirect to authorize_url to complete linking.",
|
|
458
|
+
authorize_url: authorizeUrl.toString(),
|
|
459
|
+
tenant_id: exchangeResult.tenantId,
|
|
460
|
+
}, 403);
|
|
461
|
+
}
|
|
462
|
+
// User found — sign a local access token
|
|
463
|
+
if (exchangeResult.userId && serverSigningKeys.length > 0) {
|
|
464
|
+
const sigKey = serverSigningKeys[0];
|
|
465
|
+
const token = await signJwtES256({
|
|
466
|
+
sub: exchangeResult.userId,
|
|
467
|
+
name: exchangeResult.userId,
|
|
468
|
+
scopes: ["*"],
|
|
469
|
+
tenantId: exchangeResult.tenantId,
|
|
470
|
+
}, sigKey.privateKey, sigKey.kid, resolveBaseUrl(req), `${authConfig.tokenTtl ?? 3600}s`);
|
|
471
|
+
return jsonResponse({
|
|
472
|
+
access_token: token,
|
|
473
|
+
token_type: "Bearer",
|
|
474
|
+
expires_in: authConfig.tokenTtl ?? 3600,
|
|
475
|
+
user_id: exchangeResult.userId,
|
|
476
|
+
tenant_id: exchangeResult.tenantId,
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
return jsonResponse(exchangeResult);
|
|
480
|
+
}
|
|
481
|
+
catch (err) {
|
|
482
|
+
console.error("[oauth] JWT exchange error:", err);
|
|
483
|
+
return jsonResponse({ error: "server_error", error_description: `JWT exchange failed: ${err instanceof Error ? err.message : String(err)}` }, 500);
|
|
409
484
|
}
|
|
410
|
-
return jsonResponse({
|
|
411
|
-
access_token: tokenResult.accessToken,
|
|
412
|
-
token_type: "Bearer",
|
|
413
|
-
expires_in: tokenResult.expiresIn ?? authConfig.tokenTtl,
|
|
414
|
-
refresh_token: tokenResult.refreshToken,
|
|
415
|
-
});
|
|
416
485
|
}
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
486
|
+
// ── client_credentials grant ──
|
|
487
|
+
if (grantType === "client_credentials") {
|
|
488
|
+
const clientId = params.client_id ?? "";
|
|
489
|
+
const clientSecret = params.client_secret ?? "";
|
|
490
|
+
if (!clientId || !clientSecret) {
|
|
491
|
+
return jsonResponse({ error: "invalid_request", error_description: "Missing client_id or client_secret" }, 400);
|
|
492
|
+
}
|
|
493
|
+
try {
|
|
494
|
+
const result = await registry.call({
|
|
495
|
+
action: "execute_tool",
|
|
496
|
+
path: "@auth",
|
|
497
|
+
tool: "token",
|
|
498
|
+
params: { clientId, clientSecret },
|
|
499
|
+
callerType: "system",
|
|
500
|
+
});
|
|
501
|
+
const tokenResult = result?.result;
|
|
502
|
+
if (!tokenResult?.accessToken) {
|
|
503
|
+
return jsonResponse({ error: "invalid_client", error_description: "Authentication failed" }, 401);
|
|
504
|
+
}
|
|
505
|
+
return jsonResponse({
|
|
506
|
+
access_token: tokenResult.accessToken,
|
|
507
|
+
token_type: "Bearer",
|
|
508
|
+
expires_in: tokenResult.expiresIn ?? authConfig.tokenTtl,
|
|
509
|
+
refresh_token: tokenResult.refreshToken,
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
catch (err) {
|
|
513
|
+
console.error("[oauth] Token error:", err);
|
|
514
|
+
return jsonResponse({ error: "server_error", error_description: "Token exchange failed" }, 500);
|
|
515
|
+
}
|
|
420
516
|
}
|
|
517
|
+
return jsonResponse({
|
|
518
|
+
error: "unsupported_grant_type",
|
|
519
|
+
error_description: "Supported grant types: client_credentials, jwt_exchange",
|
|
520
|
+
}, 400);
|
|
421
521
|
}
|
|
422
522
|
// ──────────────────────────────────────────
|
|
423
523
|
// Main fetch handler
|
|
@@ -458,11 +558,68 @@ export function createAgentServer(registry, options = {}) {
|
|
|
458
558
|
const result = await handleJsonRpc(body, effectiveAuth);
|
|
459
559
|
return cors ? addCors(jsonResponse(result)) : jsonResponse(result);
|
|
460
560
|
}
|
|
461
|
-
// ── POST /oauth/token → OAuth2
|
|
561
|
+
// ── POST /oauth/token → OAuth2 token exchange ──
|
|
462
562
|
if (path === "/oauth/token" && req.method === "POST") {
|
|
463
563
|
const res = await handleOAuthToken(req);
|
|
464
564
|
return cors ? addCors(res) : res;
|
|
465
565
|
}
|
|
566
|
+
// ── GET /oauth/authorize → Identity linking redirect (browser flow) ──
|
|
567
|
+
if (path === "/oauth/authorize" && req.method === "GET") {
|
|
568
|
+
if (!oauthIdentityProvider) {
|
|
569
|
+
const res = jsonResponse({ error: "not_configured", error_description: "No OAuth identity provider configured" }, 404);
|
|
570
|
+
return cors ? addCors(res) : res;
|
|
571
|
+
}
|
|
572
|
+
const url = new URL(req.url);
|
|
573
|
+
const token = url.searchParams.get("token") ?? "";
|
|
574
|
+
const redirectUri = url.searchParams.get("redirect_uri") ?? "";
|
|
575
|
+
if (!token) {
|
|
576
|
+
const res = jsonResponse({ error: "invalid_request", error_description: "Missing token parameter" }, 400);
|
|
577
|
+
return cors ? addCors(res) : res;
|
|
578
|
+
}
|
|
579
|
+
// Verify the JWT against trusted issuers (from store, falling back to config)
|
|
580
|
+
let claims = null;
|
|
581
|
+
const storeIssuers = authConfig?.store?.listTrustedIssuers
|
|
582
|
+
? await authConfig.store.listTrustedIssuers()
|
|
583
|
+
: [];
|
|
584
|
+
const configIssuerUrls = configTrustedIssuers.map(i => typeof i === "string" ? i : i.issuer);
|
|
585
|
+
const allIssuerUrls = [...new Set([...storeIssuers, ...configIssuerUrls])];
|
|
586
|
+
for (const issuerUrl of allIssuerUrls) {
|
|
587
|
+
try {
|
|
588
|
+
const result = await verifyJwtFromIssuer(token, issuerUrl);
|
|
589
|
+
if (result) {
|
|
590
|
+
claims = result;
|
|
591
|
+
break;
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
catch { /* try next issuer */ }
|
|
595
|
+
}
|
|
596
|
+
if (!claims) {
|
|
597
|
+
const res = jsonResponse({ error: "invalid_token", error_description: "JWT verification failed against all trusted issuers" }, 401);
|
|
598
|
+
return cors ? addCors(res) : res;
|
|
599
|
+
}
|
|
600
|
+
const baseUrl = resolveBaseUrl(req);
|
|
601
|
+
const scope = url.searchParams.get("scope") ?? undefined;
|
|
602
|
+
const res = await oauthIdentityProvider.authorize(req, {
|
|
603
|
+
token,
|
|
604
|
+
claims,
|
|
605
|
+
redirectUri,
|
|
606
|
+
baseUrl: baseUrl + basePath,
|
|
607
|
+
scope,
|
|
608
|
+
});
|
|
609
|
+
return cors ? addCors(res) : res;
|
|
610
|
+
}
|
|
611
|
+
// ── GET /oauth/callback → Identity linking callback ──
|
|
612
|
+
if (path === "/oauth/callback" && req.method === "GET") {
|
|
613
|
+
if (!oauthIdentityProvider) {
|
|
614
|
+
const res = jsonResponse({ error: "not_configured", error_description: "No OAuth identity provider configured" }, 404);
|
|
615
|
+
return cors ? addCors(res) : res;
|
|
616
|
+
}
|
|
617
|
+
const baseUrl = resolveBaseUrl(req);
|
|
618
|
+
const res = await oauthIdentityProvider.callback(req, {
|
|
619
|
+
baseUrl: baseUrl + basePath,
|
|
620
|
+
});
|
|
621
|
+
return cors ? addCors(res) : res;
|
|
622
|
+
}
|
|
466
623
|
// ── GET /health → Health check ──
|
|
467
624
|
if (path === "/health" && req.method === "GET") {
|
|
468
625
|
const res = jsonResponse({ status: "ok", agents: registry.listPaths() });
|
|
@@ -478,14 +635,15 @@ export function createAgentServer(registry, options = {}) {
|
|
|
478
635
|
}
|
|
479
636
|
// ── GET /.well-known/configuration → Server discovery ──
|
|
480
637
|
if (path === "/.well-known/configuration" && req.method === "GET") {
|
|
481
|
-
const baseUrl =
|
|
638
|
+
const baseUrl = resolveBaseUrl(req);
|
|
482
639
|
const res = jsonResponse({
|
|
483
640
|
issuer: baseUrl,
|
|
484
641
|
jwks_uri: `${baseUrl}/.well-known/jwks.json`,
|
|
485
642
|
token_endpoint: `${baseUrl}/oauth/token`,
|
|
486
643
|
agents_endpoint: `${baseUrl}/list`,
|
|
487
644
|
call_endpoint: baseUrl,
|
|
488
|
-
supported_grant_types: ["client_credentials"],
|
|
645
|
+
supported_grant_types: ["client_credentials", "jwt_exchange"],
|
|
646
|
+
authorization_endpoint: `${baseUrl}/oauth/authorize`,
|
|
489
647
|
agents: registry.listPaths(),
|
|
490
648
|
});
|
|
491
649
|
return cors ? addCors(res) : res;
|
|
@@ -548,12 +706,12 @@ export function createAgentServer(registry, options = {}) {
|
|
|
548
706
|
return {
|
|
549
707
|
url: null,
|
|
550
708
|
registry,
|
|
551
|
-
async
|
|
552
|
-
// Load or generate signing
|
|
553
|
-
if (options.signingKey) {
|
|
709
|
+
async initKeys() {
|
|
710
|
+
// Load or generate signing keys (without starting Bun.serve)
|
|
711
|
+
if (options.signingKey && serverSigningKeys.length === 0) {
|
|
554
712
|
serverSigningKeys.push(options.signingKey);
|
|
555
713
|
}
|
|
556
|
-
else if (authConfig?.store?.getSigningKeys) {
|
|
714
|
+
else if (authConfig?.store?.getSigningKeys && serverSigningKeys.length === 0) {
|
|
557
715
|
const stored = await authConfig.store.getSigningKeys() ?? [];
|
|
558
716
|
for (const exported of stored) {
|
|
559
717
|
serverSigningKeys.push(await importSigningKey(exported));
|
|
@@ -566,6 +724,9 @@ export function createAgentServer(registry, options = {}) {
|
|
|
566
724
|
await authConfig.store.storeSigningKey(await exportSigningKey(key));
|
|
567
725
|
}
|
|
568
726
|
}
|
|
727
|
+
},
|
|
728
|
+
async start() {
|
|
729
|
+
await this.initKeys();
|
|
569
730
|
serverInstance = Bun.serve({
|
|
570
731
|
port,
|
|
571
732
|
hostname,
|
|
@@ -582,6 +743,21 @@ export function createAgentServer(registry, options = {}) {
|
|
|
582
743
|
}
|
|
583
744
|
},
|
|
584
745
|
fetch,
|
|
746
|
+
async signJwt(claims) {
|
|
747
|
+
if (serverSigningKeys.length === 0) {
|
|
748
|
+
throw new Error('No signing keys available. Call start() or initKeys() first.');
|
|
749
|
+
}
|
|
750
|
+
const key = serverSigningKeys[0];
|
|
751
|
+
return signJwtES256({ sub: 'system', name: 'atlas-os', scopes: ['*'], ...claims }, key.privateKey, key.kid, options.serverName ?? 'agents-sdk', '1h');
|
|
752
|
+
},
|
|
753
|
+
addTrustedIssuer(issuerUrl, scopes) {
|
|
754
|
+
// Avoid duplicates
|
|
755
|
+
const existing = configTrustedIssuers.find(i => i.issuer === issuerUrl);
|
|
756
|
+
if (!existing) {
|
|
757
|
+
configTrustedIssuers.push({ issuer: issuerUrl, scopes: scopes ?? ['*'] });
|
|
758
|
+
console.error(`[agent-server] Added trusted issuer: ${issuerUrl}`);
|
|
759
|
+
}
|
|
760
|
+
},
|
|
585
761
|
};
|
|
586
762
|
}
|
|
587
763
|
//# sourceMappingURL=server.js.map
|