@economic/agents 1.3.0 → 1.4.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/README.md +53 -31
- package/dist/index.d.mts +28 -15
- package/dist/index.mjs +128 -103
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -270,6 +270,58 @@ execute: async (args, { experimental_context }) => {
|
|
|
270
270
|
|
|
271
271
|
---
|
|
272
272
|
|
|
273
|
+
### JWT Authentication
|
|
274
|
+
|
|
275
|
+
Authenticate WebSocket connections by implementing `getJwtAuthConfig` on your agent. When defined, JWT verification runs in `onConnect` — failed auth closes the connection, successful auth stores claims in `session`.
|
|
276
|
+
|
|
277
|
+
```typescript
|
|
278
|
+
import type { JWTPayload } from "jose";
|
|
279
|
+
import { ChatAgentHarness, type AgentToolContext } from "@economic/agents";
|
|
280
|
+
|
|
281
|
+
interface Session {
|
|
282
|
+
clientId: string;
|
|
283
|
+
userGuid: string;
|
|
284
|
+
agreementNumber: number;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
export class MyAgent extends ChatAgentHarness<Env, RequestBody> {
|
|
288
|
+
getJwtAuthConfig(request: Request) {
|
|
289
|
+
const origin = request.headers.get("Origin") ?? "";
|
|
290
|
+
const isStaging = origin.includes("staging");
|
|
291
|
+
|
|
292
|
+
return {
|
|
293
|
+
allowedIssuers: isStaging
|
|
294
|
+
? [/^https:\/\/auth\.staging\.example\.com$/]
|
|
295
|
+
: ["https://auth.example.com"],
|
|
296
|
+
audience: "my-api",
|
|
297
|
+
requiredScopes: ["read"],
|
|
298
|
+
getClaims: (payload: JWTPayload): Session => ({
|
|
299
|
+
clientId: payload.client_id as string,
|
|
300
|
+
userGuid: payload.user_guid as string,
|
|
301
|
+
agreementNumber: payload.agreement_number as number,
|
|
302
|
+
}),
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Session is available in tool context
|
|
307
|
+
getModel(ctx: AgentToolContext<RequestBody>) {
|
|
308
|
+
console.log(ctx.session); // { clientId, userGuid, agreementNumber }
|
|
309
|
+
return openai("gpt-4o");
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
- `allowedIssuers` — array of strings or RegExp patterns for trusted issuers
|
|
315
|
+
- `audience` — expected `aud` claim
|
|
316
|
+
- `requiredScopes` — optional array of required OAuth scopes
|
|
317
|
+
- `getClaims(payload)` — extract claims from verified JWT payload
|
|
318
|
+
|
|
319
|
+
Claims are available as `ctx.session` in `getModel`, `getSystemPrompt`, `getTools`, `getSkills`, and tool `execute` functions.
|
|
320
|
+
|
|
321
|
+
If `getJwtAuthConfig` is not implemented, no authentication is performed and `ctx.session` is `undefined`.
|
|
322
|
+
|
|
323
|
+
---
|
|
324
|
+
|
|
273
325
|
### Source URLs from Tools
|
|
274
326
|
|
|
275
327
|
Any tool can surface source URLs into the message stream by including a `sources` array in its return value. Detected automatically by `buildLLMParams` — no additional wiring needed.
|
|
@@ -426,29 +478,6 @@ const conversations = await agent.call("getConversations");
|
|
|
426
478
|
|
|
427
479
|
---
|
|
428
480
|
|
|
429
|
-
## Hono
|
|
430
|
-
|
|
431
|
-
Hono tooling is exported on `@economic/agents/hono`.
|
|
432
|
-
|
|
433
|
-
### JWT Auth Middleware
|
|
434
|
-
|
|
435
|
-
Bearer JWT verification middleware for Hono, imported from `@economic/agents/hono`. Verifies tokens via JWKS derived from the token's `iss` claim.
|
|
436
|
-
|
|
437
|
-
```typescript
|
|
438
|
-
import { jwtAuth } from "@economic/agents/hono";
|
|
439
|
-
|
|
440
|
-
app.use(
|
|
441
|
-
"/api/*",
|
|
442
|
-
jwtAuth({
|
|
443
|
-
allowedIssuers: ["https://login.example.com"],
|
|
444
|
-
audience: "my-api",
|
|
445
|
-
requiredScopes: ["read", "write"],
|
|
446
|
-
}),
|
|
447
|
-
);
|
|
448
|
-
```
|
|
449
|
-
|
|
450
|
-
---
|
|
451
|
-
|
|
452
481
|
## API Reference
|
|
453
482
|
|
|
454
483
|
### `@economic/agents`
|
|
@@ -460,7 +489,7 @@ app.use(
|
|
|
460
489
|
| `ChatAgentHarness` | Opinionated chat harness with getModel/getSystemPrompt/getTools/getSkills |
|
|
461
490
|
| `buildLLMParams` | Standalone function to build streamText/generateText params |
|
|
462
491
|
| `Skill` | Type: named group of tools with optional guidance |
|
|
463
|
-
| `AgentToolContext` | Type: request body merged with `logEvent` for tool context
|
|
492
|
+
| `AgentToolContext` | Type: request body merged with `session` and `logEvent` for tool context |
|
|
464
493
|
| `OnChatMessageOptions` | Type: options passed to `onChatMessage` |
|
|
465
494
|
| `BuildLLMParamsConfig` | Type: config for standalone `buildLLMParams` |
|
|
466
495
|
|
|
@@ -474,13 +503,6 @@ React hooks are in a separate package. See [`@economic/agents-react`](../react/R
|
|
|
474
503
|
| `UseAIChatAgentOptions` | Type: options for `useAIChatAgent` (`agent`, `host`, `chatId`, optional `basePath`, `toolContext`, `connectionParams`, …) |
|
|
475
504
|
| `AgentConnectionStatus` | Type: `"connecting" \| "connected" \| "disconnected" \| "unauthorized"` |
|
|
476
505
|
|
|
477
|
-
### `@economic/agents/hono`
|
|
478
|
-
|
|
479
|
-
| Export | Description |
|
|
480
|
-
| --------------- | ------------------------------------------- |
|
|
481
|
-
| `jwtAuth` | Hono middleware for Bearer JWT verification |
|
|
482
|
-
| `JwtAuthConfig` | Type: config for `jwtAuth` |
|
|
483
|
-
|
|
484
506
|
### CLI
|
|
485
507
|
|
|
486
508
|
| Command | Description |
|
package/dist/index.d.mts
CHANGED
|
@@ -65,6 +65,21 @@ type BuildLLMParamsConfig = Omit<LLMParams, "prompt"> & {
|
|
|
65
65
|
*/
|
|
66
66
|
declare function buildLLMParams(config: BuildLLMParamsConfig): LLMParams;
|
|
67
67
|
//#endregion
|
|
68
|
+
//#region src/server/features/auth/index.d.ts
|
|
69
|
+
interface JwtAuthConfig<TClaims extends Record<string, unknown> = Record<string, unknown>> {
|
|
70
|
+
/** Issuers whose tokens are accepted (exact string or RegExp). */
|
|
71
|
+
allowedIssuers: readonly (string | RegExp)[];
|
|
72
|
+
/** Expected `aud` claim. */
|
|
73
|
+
audience: string;
|
|
74
|
+
/** Required OAuth scopes; token `scope` must include all (empty = no scope check). */
|
|
75
|
+
requiredScopes?: readonly string[];
|
|
76
|
+
/**
|
|
77
|
+
* Extract the claims you need from the verified JWT payload.
|
|
78
|
+
* These will be stored and made available in the agent's tool context.
|
|
79
|
+
*/
|
|
80
|
+
getClaims: (payload: JWTPayload) => TClaims;
|
|
81
|
+
}
|
|
82
|
+
//#endregion
|
|
68
83
|
//#region src/server/agents/Agent.d.ts
|
|
69
84
|
/**
|
|
70
85
|
* Base agent for Cloudflare Agents SDK Durable Objects with lazy skill loading,
|
|
@@ -77,6 +92,19 @@ declare function buildLLMParams(config: BuildLLMParamsConfig): LLMParams;
|
|
|
77
92
|
* extend {@link ChatAgent} instead.
|
|
78
93
|
*/
|
|
79
94
|
declare abstract class Agent<Env extends Cloudflare.Env = Cloudflare.Env> extends Agent$1<Env & AgentEnv> {
|
|
95
|
+
/**
|
|
96
|
+
* Verified session claims from JWT authentication.
|
|
97
|
+
* Set in `onConnect` when `getJwtAuthConfig` is implemented.
|
|
98
|
+
*/
|
|
99
|
+
private session?;
|
|
100
|
+
/**
|
|
101
|
+
* Override to enable JWT authentication on WebSocket connections.
|
|
102
|
+
* Return the auth config based on the incoming request, or undefined to skip auth.
|
|
103
|
+
*
|
|
104
|
+
* @param request - The WebSocket upgrade request
|
|
105
|
+
* @returns JWT auth config or undefined to skip authentication
|
|
106
|
+
*/
|
|
107
|
+
protected getJwtAuthConfig?(request: Request): JwtAuthConfig<Record<string, unknown>> | undefined;
|
|
80
108
|
/**
|
|
81
109
|
* Returns the user ID from the durable object name.
|
|
82
110
|
*/
|
|
@@ -102,21 +130,6 @@ declare abstract class Agent<Env extends Cloudflare.Env = Cloudflare.Env> extend
|
|
|
102
130
|
}): Promise<LLMParams>;
|
|
103
131
|
}
|
|
104
132
|
//#endregion
|
|
105
|
-
//#region src/server/features/auth/index.d.ts
|
|
106
|
-
interface JwtAuthConfig<TClaims extends Record<string, unknown> = Record<string, unknown>> {
|
|
107
|
-
/** Issuers whose tokens are accepted (exact string or RegExp). */
|
|
108
|
-
allowedIssuers: readonly (string | RegExp)[];
|
|
109
|
-
/** Expected `aud` claim. */
|
|
110
|
-
audience: string;
|
|
111
|
-
/** Required OAuth scopes; token `scope` must include all (empty = no scope check). */
|
|
112
|
-
requiredScopes?: readonly string[];
|
|
113
|
-
/**
|
|
114
|
-
* Extract the claims you need from the verified JWT payload.
|
|
115
|
-
* These will be stored and made available in the agent's tool context.
|
|
116
|
-
*/
|
|
117
|
-
getClaims: (payload: JWTPayload) => TClaims;
|
|
118
|
-
}
|
|
119
|
-
//#endregion
|
|
120
133
|
//#region src/server/agents/ChatAgent.d.ts
|
|
121
134
|
/**
|
|
122
135
|
* Chat agent for Cloudflare Agents SDK: lazy skill loading, audit logging,
|
package/dist/index.mjs
CHANGED
|
@@ -189,7 +189,7 @@ function buildSystemPromptWithSkills(basePrompt, availableSkillList, loadedGuida
|
|
|
189
189
|
## Tools
|
|
190
190
|
|
|
191
191
|
### Loading More Tools:
|
|
192
|
-
Use \`activate_skill\` to load these skills
|
|
192
|
+
Use \`activate_skill\` to load these skills. NEVER ask the user for permission to load a skill - just load it silently when relevant. The user should not be aware of skills or tools; activate them autonomously based on the request:
|
|
193
193
|
|
|
194
194
|
${availableSkillList}`;
|
|
195
195
|
if (loadedGuidance) prompt += `
|
|
@@ -369,6 +369,106 @@ function buildTurnLogPayload(event) {
|
|
|
369
369
|
};
|
|
370
370
|
}
|
|
371
371
|
//#endregion
|
|
372
|
+
//#region src/server/features/auth/index.ts
|
|
373
|
+
const jwksByIssuer = /* @__PURE__ */ new Map();
|
|
374
|
+
function getJwksForIssuer(issuer) {
|
|
375
|
+
const normalized = issuer.replace(/\/$/, "");
|
|
376
|
+
let jwks = jwksByIssuer.get(normalized);
|
|
377
|
+
if (!jwks) {
|
|
378
|
+
jwks = createRemoteJWKSet(new URL(`${normalized}/.well-known/openid-configuration/jwks`));
|
|
379
|
+
jwksByIssuer.set(normalized, jwks);
|
|
380
|
+
}
|
|
381
|
+
return jwks;
|
|
382
|
+
}
|
|
383
|
+
function isIssuerAllowed(iss, allowed) {
|
|
384
|
+
return allowed.some((rule) => typeof rule === "string" ? rule === iss : rule.test(iss));
|
|
385
|
+
}
|
|
386
|
+
function extractTokenFromRequest(request) {
|
|
387
|
+
const authorization = request.headers.get("Authorization");
|
|
388
|
+
if (authorization) {
|
|
389
|
+
const match = authorization.match(/^Bearer\s+(.+)$/i);
|
|
390
|
+
if (match?.[1]) return match[1].trim();
|
|
391
|
+
}
|
|
392
|
+
const wsProtocol = request.headers.get("Sec-WebSocket-Protocol");
|
|
393
|
+
if (wsProtocol) {
|
|
394
|
+
const parts = wsProtocol.split(",").map((p) => p.trim());
|
|
395
|
+
const idx = parts.indexOf("bearer");
|
|
396
|
+
if (idx !== -1 && parts[idx + 1]) return parts[idx + 1];
|
|
397
|
+
}
|
|
398
|
+
return null;
|
|
399
|
+
}
|
|
400
|
+
function hasRequiredScopes(tokenScope, required) {
|
|
401
|
+
if (required.length === 0) return true;
|
|
402
|
+
if (tokenScope === void 0) return false;
|
|
403
|
+
const tokens = Array.isArray(tokenScope) ? tokenScope : tokenScope.split(" ").map((s) => s.trim()).filter(Boolean);
|
|
404
|
+
const granted = new Set(tokens);
|
|
405
|
+
return required.every((scope) => granted.has(scope));
|
|
406
|
+
}
|
|
407
|
+
/**
|
|
408
|
+
* Verify a JWT from a request and extract claims.
|
|
409
|
+
*
|
|
410
|
+
* Extracts the token from `Authorization: Bearer` header first,
|
|
411
|
+
* then falls back to `Sec-WebSocket-Protocol: bearer, <token>` for WebSocket connections.
|
|
412
|
+
*
|
|
413
|
+
* Expected auth failures (missing/expired token, insufficient scope) return a failure result.
|
|
414
|
+
* Unexpected errors (network issues, malformed requests, untrusted issuers) throw and should
|
|
415
|
+
* be caught and logged by the caller.
|
|
416
|
+
*
|
|
417
|
+
* @param request - The incoming request (from ConnectionContext)
|
|
418
|
+
* @param config - JWT verification configuration
|
|
419
|
+
* @returns Result object with either verified claims or expected auth failure
|
|
420
|
+
* @throws Error for unexpected failures that should be logged
|
|
421
|
+
*/
|
|
422
|
+
async function verifyJwt(request, config) {
|
|
423
|
+
const token = extractTokenFromRequest(request);
|
|
424
|
+
if (!token) return {
|
|
425
|
+
success: false,
|
|
426
|
+
status: 401,
|
|
427
|
+
message: "Unauthorized: Missing authentication token"
|
|
428
|
+
};
|
|
429
|
+
let unverifiedPayload;
|
|
430
|
+
try {
|
|
431
|
+
unverifiedPayload = decodeJwt(token);
|
|
432
|
+
} catch {
|
|
433
|
+
throw new Error("Invalid token format");
|
|
434
|
+
}
|
|
435
|
+
const iss = typeof unverifiedPayload.iss === "string" ? unverifiedPayload.iss : void 0;
|
|
436
|
+
if (!iss) throw new Error("Missing issuer claim in token");
|
|
437
|
+
if (!isIssuerAllowed(iss, config.allowedIssuers)) throw new Error(`Untrusted issuer: ${iss}`);
|
|
438
|
+
const jwks = getJwksForIssuer(iss);
|
|
439
|
+
let payload;
|
|
440
|
+
try {
|
|
441
|
+
payload = (await jwtVerify(token, jwks, {
|
|
442
|
+
issuer: iss,
|
|
443
|
+
audience: config.audience
|
|
444
|
+
})).payload;
|
|
445
|
+
} catch (error) {
|
|
446
|
+
if (error instanceof errors.JWTExpired) return {
|
|
447
|
+
success: false,
|
|
448
|
+
status: 401,
|
|
449
|
+
message: "Unauthorized: Token expired"
|
|
450
|
+
};
|
|
451
|
+
if (error instanceof errors.JWTClaimValidationFailed) return {
|
|
452
|
+
success: false,
|
|
453
|
+
status: 401,
|
|
454
|
+
message: "Unauthorized: Token claim validation failed"
|
|
455
|
+
};
|
|
456
|
+
if (error instanceof errors.JWSSignatureVerificationFailed || error instanceof errors.JWKSNoMatchingKey) throw new Error("Invalid token signature");
|
|
457
|
+
throw error;
|
|
458
|
+
}
|
|
459
|
+
const requiredScopes = config.requiredScopes ?? [];
|
|
460
|
+
const scopeClaim = payload.scope;
|
|
461
|
+
if (!hasRequiredScopes(scopeClaim, requiredScopes)) return {
|
|
462
|
+
success: false,
|
|
463
|
+
status: 403,
|
|
464
|
+
message: "Forbidden: Insufficient scope"
|
|
465
|
+
};
|
|
466
|
+
return {
|
|
467
|
+
success: true,
|
|
468
|
+
claims: config.getClaims(payload)
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
//#endregion
|
|
372
472
|
//#region src/server/agents/Agent.ts
|
|
373
473
|
/**
|
|
374
474
|
* Base agent for Cloudflare Agents SDK Durable Objects with lazy skill loading,
|
|
@@ -381,6 +481,11 @@ function buildTurnLogPayload(event) {
|
|
|
381
481
|
* extend {@link ChatAgent} instead.
|
|
382
482
|
*/
|
|
383
483
|
var Agent = class extends Agent$1 {
|
|
484
|
+
/**
|
|
485
|
+
* Verified session claims from JWT authentication.
|
|
486
|
+
* Set in `onConnect` when `getJwtAuthConfig` is implemented.
|
|
487
|
+
*/
|
|
488
|
+
session;
|
|
384
489
|
/**
|
|
385
490
|
* Returns the user ID from the durable object name.
|
|
386
491
|
*/
|
|
@@ -389,15 +494,33 @@ var Agent = class extends Agent$1 {
|
|
|
389
494
|
}
|
|
390
495
|
async onConnect(connection, ctx) {
|
|
391
496
|
if (!this.env.AGENT_DB) {
|
|
392
|
-
console.error("[
|
|
497
|
+
console.error("[ChatAgent] Connection rejected: no AGENT_DB bound");
|
|
393
498
|
connection.close(3e3, "Could not connect to agent, database not found");
|
|
394
499
|
return;
|
|
395
500
|
}
|
|
396
501
|
if (!this.getUserId()) {
|
|
397
|
-
console.error("[
|
|
502
|
+
console.error("[ChatAgent] Connection rejected: name must be in the format userId:uniqueChatId");
|
|
398
503
|
connection.close(3e3, "Could not connect to agent, name is not in correct format");
|
|
399
504
|
return;
|
|
400
505
|
}
|
|
506
|
+
if (this.getJwtAuthConfig) {
|
|
507
|
+
const config = this.getJwtAuthConfig(ctx.request);
|
|
508
|
+
if (config) {
|
|
509
|
+
let result;
|
|
510
|
+
try {
|
|
511
|
+
result = await verifyJwt(ctx.request, config);
|
|
512
|
+
} catch (error) {
|
|
513
|
+
console.error("[ChatAgent] JWT verification error", error);
|
|
514
|
+
connection.close(4001, "Unauthorized");
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
if (!result.success) {
|
|
518
|
+
connection.close(result.status === 401 ? 4001 : 4003, result.message);
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
this.session = result.claims;
|
|
522
|
+
}
|
|
523
|
+
}
|
|
401
524
|
return super.onConnect(connection, ctx);
|
|
402
525
|
}
|
|
403
526
|
/**
|
|
@@ -424,7 +547,9 @@ var Agent = class extends Agent$1 {
|
|
|
424
547
|
async buildLLMParams(config) {
|
|
425
548
|
const activeSkills = await getStoredSkills(this.sql.bind(this));
|
|
426
549
|
const experimental_context = {
|
|
550
|
+
...config.experimental_context,
|
|
427
551
|
...config.options?.body,
|
|
552
|
+
session: this.session,
|
|
428
553
|
logEvent: this.logEvent.bind(this)
|
|
429
554
|
};
|
|
430
555
|
const onFinish = async (event) => {
|
|
@@ -653,106 +778,6 @@ function getDeleteConversationScheduleIds(schedules) {
|
|
|
653
778
|
return schedules.filter((schedule) => schedule.callback === DELETE_CONVERSATION_CALLBACK).map((schedule) => schedule.id);
|
|
654
779
|
}
|
|
655
780
|
//#endregion
|
|
656
|
-
//#region src/server/features/auth/index.ts
|
|
657
|
-
const jwksByIssuer = /* @__PURE__ */ new Map();
|
|
658
|
-
function getJwksForIssuer(issuer) {
|
|
659
|
-
const normalized = issuer.replace(/\/$/, "");
|
|
660
|
-
let jwks = jwksByIssuer.get(normalized);
|
|
661
|
-
if (!jwks) {
|
|
662
|
-
jwks = createRemoteJWKSet(new URL(`${normalized}/.well-known/openid-configuration/jwks`));
|
|
663
|
-
jwksByIssuer.set(normalized, jwks);
|
|
664
|
-
}
|
|
665
|
-
return jwks;
|
|
666
|
-
}
|
|
667
|
-
function isIssuerAllowed(iss, allowed) {
|
|
668
|
-
return allowed.some((rule) => typeof rule === "string" ? rule === iss : rule.test(iss));
|
|
669
|
-
}
|
|
670
|
-
function extractTokenFromRequest(request) {
|
|
671
|
-
const authorization = request.headers.get("Authorization");
|
|
672
|
-
if (authorization) {
|
|
673
|
-
const match = authorization.match(/^Bearer\s+(.+)$/i);
|
|
674
|
-
if (match?.[1]) return match[1].trim();
|
|
675
|
-
}
|
|
676
|
-
const wsProtocol = request.headers.get("Sec-WebSocket-Protocol");
|
|
677
|
-
if (wsProtocol) {
|
|
678
|
-
const parts = wsProtocol.split(",").map((p) => p.trim());
|
|
679
|
-
const idx = parts.indexOf("bearer");
|
|
680
|
-
if (idx !== -1 && parts[idx + 1]) return parts[idx + 1];
|
|
681
|
-
}
|
|
682
|
-
return null;
|
|
683
|
-
}
|
|
684
|
-
function hasRequiredScopes(tokenScope, required) {
|
|
685
|
-
if (required.length === 0) return true;
|
|
686
|
-
if (tokenScope === void 0) return false;
|
|
687
|
-
const tokens = Array.isArray(tokenScope) ? tokenScope : tokenScope.split(" ").map((s) => s.trim()).filter(Boolean);
|
|
688
|
-
const granted = new Set(tokens);
|
|
689
|
-
return required.every((scope) => granted.has(scope));
|
|
690
|
-
}
|
|
691
|
-
/**
|
|
692
|
-
* Verify a JWT from a request and extract claims.
|
|
693
|
-
*
|
|
694
|
-
* Extracts the token from `Authorization: Bearer` header first,
|
|
695
|
-
* then falls back to `Sec-WebSocket-Protocol: bearer, <token>` for WebSocket connections.
|
|
696
|
-
*
|
|
697
|
-
* Expected auth failures (missing/expired token, insufficient scope) return a failure result.
|
|
698
|
-
* Unexpected errors (network issues, malformed requests, untrusted issuers) throw and should
|
|
699
|
-
* be caught and logged by the caller.
|
|
700
|
-
*
|
|
701
|
-
* @param request - The incoming request (from ConnectionContext)
|
|
702
|
-
* @param config - JWT verification configuration
|
|
703
|
-
* @returns Result object with either verified claims or expected auth failure
|
|
704
|
-
* @throws Error for unexpected failures that should be logged
|
|
705
|
-
*/
|
|
706
|
-
async function verifyJwt(request, config) {
|
|
707
|
-
const token = extractTokenFromRequest(request);
|
|
708
|
-
if (!token) return {
|
|
709
|
-
success: false,
|
|
710
|
-
status: 401,
|
|
711
|
-
message: "Unauthorized: Missing authentication token"
|
|
712
|
-
};
|
|
713
|
-
let unverifiedPayload;
|
|
714
|
-
try {
|
|
715
|
-
unverifiedPayload = decodeJwt(token);
|
|
716
|
-
} catch {
|
|
717
|
-
throw new Error("Invalid token format");
|
|
718
|
-
}
|
|
719
|
-
const iss = typeof unverifiedPayload.iss === "string" ? unverifiedPayload.iss : void 0;
|
|
720
|
-
if (!iss) throw new Error("Missing issuer claim in token");
|
|
721
|
-
if (!isIssuerAllowed(iss, config.allowedIssuers)) throw new Error(`Untrusted issuer: ${iss}`);
|
|
722
|
-
const jwks = getJwksForIssuer(iss);
|
|
723
|
-
let payload;
|
|
724
|
-
try {
|
|
725
|
-
payload = (await jwtVerify(token, jwks, {
|
|
726
|
-
issuer: iss,
|
|
727
|
-
audience: config.audience
|
|
728
|
-
})).payload;
|
|
729
|
-
} catch (error) {
|
|
730
|
-
if (error instanceof errors.JWTExpired) return {
|
|
731
|
-
success: false,
|
|
732
|
-
status: 401,
|
|
733
|
-
message: "Unauthorized: Token expired"
|
|
734
|
-
};
|
|
735
|
-
if (error instanceof errors.JWTClaimValidationFailed) return {
|
|
736
|
-
success: false,
|
|
737
|
-
status: 401,
|
|
738
|
-
message: "Unauthorized: Token claim validation failed"
|
|
739
|
-
};
|
|
740
|
-
if (error instanceof errors.JWSSignatureVerificationFailed || error instanceof errors.JWKSNoMatchingKey) throw new Error("Invalid token signature");
|
|
741
|
-
throw error;
|
|
742
|
-
}
|
|
743
|
-
const requiredScopes = config.requiredScopes ?? [];
|
|
744
|
-
const scopeClaim = payload.scope;
|
|
745
|
-
if (!hasRequiredScopes(scopeClaim, requiredScopes)) return {
|
|
746
|
-
success: false,
|
|
747
|
-
status: 403,
|
|
748
|
-
message: "Forbidden: Insufficient scope"
|
|
749
|
-
};
|
|
750
|
-
return {
|
|
751
|
-
success: true,
|
|
752
|
-
claims: config.getClaims(payload)
|
|
753
|
-
};
|
|
754
|
-
}
|
|
755
|
-
//#endregion
|
|
756
781
|
//#region src/server/agents/ChatAgent.ts
|
|
757
782
|
/**
|
|
758
783
|
* Chat agent for Cloudflare Agents SDK: lazy skill loading, audit logging,
|