@absolutejs/auth 0.54.8 → 0.55.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 (44) hide show
  1. package/README.md +19 -5
  2. package/dist/agents/config.d.ts +16 -0
  3. package/dist/agents/idJag.d.ts +49 -0
  4. package/dist/agents/inMemoryStores.d.ts +2 -1
  5. package/dist/agents/index.d.ts +5 -2
  6. package/dist/agents/index.js +1391 -66
  7. package/dist/agents/index.js.map +12 -8
  8. package/dist/agents/postgresStores.d.ts +280 -1
  9. package/dist/agents/registration.d.ts +162 -0
  10. package/dist/agents/registrationClient.d.ts +50 -0
  11. package/dist/agents/routes.d.ts +112 -1
  12. package/dist/agents/types.d.ts +59 -0
  13. package/dist/apikeys/routes.d.ts +2 -2
  14. package/dist/cli/migrate.js +57 -8
  15. package/dist/cli/migrate.js.map +4 -4
  16. package/dist/credentials/login.d.ts +4 -4
  17. package/dist/credentials/routes.d.ts +4 -4
  18. package/dist/index.d.ts +185 -2
  19. package/dist/index.js +1388 -102
  20. package/dist/index.js.map +15 -12
  21. package/dist/manifest.js +12 -6
  22. package/dist/manifest.js.map +4 -4
  23. package/dist/manifest.json +2 -2
  24. package/dist/mfa/routes.d.ts +2 -2
  25. package/dist/mfa/sms.d.ts +2 -2
  26. package/dist/oidc/clientAuth.d.ts +6 -2
  27. package/dist/oidc/config.d.ts +19 -5
  28. package/dist/oidc/keys.d.ts +7 -3
  29. package/dist/oidc/logout.d.ts +2 -2
  30. package/dist/oidc/routes.d.ts +13 -11
  31. package/dist/organizations/routes.d.ts +1 -1
  32. package/dist/portal/routes.d.ts +3 -3
  33. package/dist/roles/routes.d.ts +1 -1
  34. package/dist/routes/refresh.d.ts +1 -1
  35. package/dist/routes/revoke.d.ts +1 -1
  36. package/dist/routes/sessions.d.ts +3 -3
  37. package/dist/sso/discoveryRoute.d.ts +1 -1
  38. package/dist/sso/oidcRoutes.d.ts +2 -2
  39. package/dist/sso/samlRoutes.d.ts +4 -4
  40. package/docs/AGENT-AUTH.md +54 -0
  41. package/docs/MIGRATE-FROM-LUCIA.md +235 -0
  42. package/docs/OAUTH-PROVIDER-QUIRKS.md +150 -0
  43. package/docs/UI-COMPONENTS.md +226 -0
  44. package/package.json +7 -3
@@ -55,6 +55,132 @@ var init_constants = __esm(() => {
55
55
  COOKIE_DURATION = SECONDS_IN_A_MINUTE * COOKIE_MINUTES;
56
56
  });
57
57
 
58
+ // src/crypto.ts
59
+ var exports_crypto = {};
60
+ __export(exports_crypto, {
61
+ verifyTotp: () => verifyTotp,
62
+ verifyPassword: () => verifyPassword,
63
+ hashToken: () => hashToken,
64
+ hashPassword: () => hashPassword,
65
+ generateTotpSecret: () => generateTotpSecret,
66
+ generateTotp: () => generateTotp,
67
+ generateSecureToken: () => generateSecureToken,
68
+ generateEncryptionKey: () => generateEncryptionKey,
69
+ encryptSecret: () => encryptSecret,
70
+ decryptSecret: () => decryptSecret,
71
+ createTotpKeyUri: () => createTotpKeyUri,
72
+ constantTimeEqual: () => constantTimeEqual,
73
+ base32Encode: () => base32Encode,
74
+ base32Decode: () => base32Decode
75
+ });
76
+ var DEFAULT_TOKEN_BYTES = 32, AES_KEY_BYTES = 32, AES_IV_BYTES = 12, HOTP_COUNTER_BYTES = 8, TOTP_SECRET_BYTES = 20, TOTP_DIGITS = 6, TOTP_PERIOD_SECONDS = 30, DEFAULT_TOTP_WINDOW = 1, DECIMAL_RADIX = 10, LAST_NIBBLE_MASK = 15, SIGN_BIT_MASK = 2147483647, BASE32_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567", BASE32_GROUP_BITS = 5, BASE32_MASK = 31, BYTE_BITS = 8, textEncoder, textDecoder, base64UrlEncode = (bytes) => Buffer.from(bytes).toString("base64url"), base64UrlDecode = (encoded) => new Uint8Array(Buffer.from(encoded, "base64url")), sha256 = async (input) => {
77
+ const digest = await crypto.subtle.digest("SHA-256", textEncoder.encode(input));
78
+ return new Uint8Array(digest);
79
+ }, hmacSha1 = async (key, message) => {
80
+ const cryptoKey = await crypto.subtle.importKey("raw", key, { hash: "SHA-1", name: "HMAC" }, false, ["sign"]);
81
+ const signature = await crypto.subtle.sign("HMAC", cryptoKey, message);
82
+ return new Uint8Array(signature);
83
+ }, counterToBytes = (counter) => {
84
+ const bytes = new Uint8Array(HOTP_COUNTER_BYTES);
85
+ new DataView(bytes.buffer).setBigUint64(0, BigInt(counter), false);
86
+ return bytes;
87
+ }, generateHotp = async (secret, counter, digits = TOTP_DIGITS) => {
88
+ const hmac = await hmacSha1(secret, counterToBytes(counter));
89
+ const view = new DataView(hmac.buffer, hmac.byteOffset, hmac.byteLength);
90
+ const offset = view.getUint8(hmac.byteLength - 1) & LAST_NIBBLE_MASK;
91
+ const truncated = view.getUint32(offset, false) & SIGN_BIT_MASK;
92
+ const otp = truncated % DECIMAL_RADIX ** digits;
93
+ return otp.toString().padStart(digits, "0");
94
+ }, importAesKey = (keyMaterial) => crypto.subtle.importKey("raw", base64UrlDecode(keyMaterial), { name: "AES-GCM" }, false, ["decrypt", "encrypt"]), base32Decode = (encoded) => {
95
+ const normalized = encoded.toUpperCase().replace(/[^A-Z2-7]/gu, "");
96
+ const bits = [...normalized].map((char) => BASE32_ALPHABET.indexOf(char).toString(2).padStart(BASE32_GROUP_BITS, "0")).join("");
97
+ const byteChunks = bits.match(/.{8}/gu) ?? [];
98
+ return new Uint8Array(byteChunks.map((chunk) => parseInt(chunk, 2)));
99
+ }, base32Encode = (bytes) => {
100
+ const bits = Array.from(bytes, (byte) => byte.toString(2).padStart(BYTE_BITS, "0")).join("");
101
+ const groups = bits.match(/.{1,5}/gu) ?? [];
102
+ return groups.map((group) => BASE32_ALPHABET[parseInt(group.padEnd(BASE32_GROUP_BITS, "0"), 2) & BASE32_MASK] ?? "").join("");
103
+ }, constantTimeEqual = async (left, right) => {
104
+ const [leftDigest, rightDigest] = await Promise.all([
105
+ sha256(left),
106
+ sha256(right)
107
+ ]);
108
+ const leftView = new DataView(leftDigest.buffer, leftDigest.byteOffset, leftDigest.byteLength);
109
+ const rightView = new DataView(rightDigest.buffer, rightDigest.byteOffset, rightDigest.byteLength);
110
+ let mismatch = 0;
111
+ for (let index = 0;index < leftDigest.byteLength; index += 1) {
112
+ mismatch |= leftView.getUint8(index) ^ rightView.getUint8(index);
113
+ }
114
+ return mismatch === 0;
115
+ }, createTotpKeyUri = ({
116
+ accountName,
117
+ digits = TOTP_DIGITS,
118
+ issuer,
119
+ period = TOTP_PERIOD_SECONDS,
120
+ secret
121
+ }) => {
122
+ const params = new URLSearchParams({
123
+ algorithm: "SHA1",
124
+ digits: `${digits}`,
125
+ issuer,
126
+ period: `${period}`,
127
+ secret
128
+ });
129
+ const label = encodeURIComponent(`${issuer}:${accountName}`);
130
+ return `otpauth://totp/${label}?${params.toString()}`;
131
+ }, decryptSecret = async (ciphertext, keyMaterial) => {
132
+ const key = await importAesKey(keyMaterial);
133
+ const combined = base64UrlDecode(ciphertext);
134
+ const nonce = combined.subarray(0, AES_IV_BYTES);
135
+ const data = combined.subarray(AES_IV_BYTES);
136
+ const plaintext = await crypto.subtle.decrypt({ iv: nonce, name: "AES-GCM" }, key, data);
137
+ return textDecoder.decode(plaintext);
138
+ }, encryptSecret = async (plaintext, keyMaterial) => {
139
+ const key = await importAesKey(keyMaterial);
140
+ const nonce = new Uint8Array(AES_IV_BYTES);
141
+ crypto.getRandomValues(nonce);
142
+ const ciphertext = await crypto.subtle.encrypt({ iv: nonce, name: "AES-GCM" }, key, textEncoder.encode(plaintext));
143
+ const combined = new Uint8Array(nonce.byteLength + ciphertext.byteLength);
144
+ combined.set(nonce, 0);
145
+ combined.set(new Uint8Array(ciphertext), nonce.byteLength);
146
+ return base64UrlEncode(combined);
147
+ }, generateEncryptionKey = () => generateSecureToken(AES_KEY_BYTES), generateSecureToken = (byteLength = DEFAULT_TOKEN_BYTES) => {
148
+ const bytes = new Uint8Array(byteLength);
149
+ crypto.getRandomValues(bytes);
150
+ return base64UrlEncode(bytes);
151
+ }, generateTotp = async ({
152
+ digits = TOTP_DIGITS,
153
+ now = Date.now(),
154
+ period = TOTP_PERIOD_SECONDS,
155
+ secret
156
+ }) => {
157
+ const counter = Math.floor(now / MILLISECONDS_IN_A_SECOND / period);
158
+ return generateHotp(base32Decode(secret), counter, digits);
159
+ }, generateTotpSecret = (byteLength = TOTP_SECRET_BYTES) => {
160
+ const bytes = new Uint8Array(byteLength);
161
+ crypto.getRandomValues(bytes);
162
+ return base32Encode(bytes);
163
+ }, hashPassword = (password) => Bun.password.hash(password, { algorithm: "argon2id" }), hashToken = async (token) => base64UrlEncode(await sha256(token)), verifyPassword = (password, hash) => Bun.password.verify(password, hash), verifyTotp = async ({
164
+ digits = TOTP_DIGITS,
165
+ now = Date.now(),
166
+ period = TOTP_PERIOD_SECONDS,
167
+ secret,
168
+ token,
169
+ window = DEFAULT_TOTP_WINDOW
170
+ }) => {
171
+ const secretBytes = base32Decode(secret);
172
+ const counter = Math.floor(now / MILLISECONDS_IN_A_SECOND / period);
173
+ const drifts = Array.from({ length: window * 2 + 1 }, (_, offset) => counter - window + offset);
174
+ const candidates = await Promise.all(drifts.map((value) => generateHotp(secretBytes, value, digits)));
175
+ const matches = await Promise.all(candidates.map((candidate) => constantTimeEqual(candidate, token)));
176
+ return matches.includes(true);
177
+ };
178
+ var init_crypto = __esm(() => {
179
+ init_constants();
180
+ textEncoder = new TextEncoder;
181
+ textDecoder = new TextDecoder;
182
+ });
183
+
58
184
  // src/agents/config.ts
59
185
  var DEFAULT_AGENT_RESOURCE_METADATA_ROUTE = "/.well-known/oauth-protected-resource";
60
186
  var agentProtectedResourceMetadata = (config) => ({
@@ -65,8 +191,952 @@ var agentProtectedResourceMetadata = (config) => ({
65
191
  resource: config.resource,
66
192
  scopes_supported: config.scopes
67
193
  });
194
+ // src/agents/registration.ts
195
+ init_constants();
196
+ init_crypto();
197
+
198
+ // src/oidc/keys.ts
199
+ var ENCODER = new TextEncoder;
200
+ var ES256 = { hash: "SHA-256", name: "ECDSA" };
201
+ var KEY_PARAMS = { name: "ECDSA", namedCurve: "P-256" };
202
+ var toBase64Url = (bytes) => Buffer.from(bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes)).toString("base64url");
203
+ var fromBase64Url = (value) => new Uint8Array(Buffer.from(value, "base64url"));
204
+ var encodeSegment = (value) => Buffer.from(JSON.stringify(value)).toString("base64url");
205
+ var decodeSegment = (segment) => {
206
+ try {
207
+ const value = JSON.parse(Buffer.from(segment, "base64url").toString("utf8"));
208
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
209
+ return;
210
+ }
211
+ return Object.fromEntries(Object.entries(value));
212
+ } catch {
213
+ return;
214
+ }
215
+ };
216
+ var generateSigningKey = async () => {
217
+ const pair = await crypto.subtle.generateKey(KEY_PARAMS, true, [
218
+ "sign",
219
+ "verify"
220
+ ]);
221
+ const privateJwk = await crypto.subtle.exportKey("jwk", pair.privateKey);
222
+ const publicJwk = await crypto.subtle.exportKey("jwk", pair.publicKey);
223
+ return { kid: await jwkThumbprint(publicJwk), privateJwk, publicJwk };
224
+ };
225
+ var jwkThumbprint = async (jwk) => {
226
+ const canonical = JSON.stringify({
227
+ crv: jwk.crv,
228
+ kty: jwk.kty,
229
+ x: jwk.x,
230
+ y: jwk.y
231
+ });
232
+ return toBase64Url(await crypto.subtle.digest("SHA-256", ENCODER.encode(canonical)));
233
+ };
234
+ var signJwt = async (payload, signing, typ = "JWT") => {
235
+ const key = await crypto.subtle.importKey("jwk", signing.privateJwk, KEY_PARAMS, false, ["sign"]);
236
+ const input = `${encodeSegment({ alg: "ES256", kid: signing.kid, typ })}.${encodeSegment(payload)}`;
237
+ const signature = await crypto.subtle.sign(ES256, key, ENCODER.encode(input));
238
+ return `${input}.${toBase64Url(signature)}`;
239
+ };
240
+ var toPublicJwk = (key) => ({
241
+ alg: "ES256",
242
+ crv: key.publicJwk.crv,
243
+ kid: key.kid,
244
+ kty: key.publicJwk.kty,
245
+ use: "sig",
246
+ x: key.publicJwk.x,
247
+ y: key.publicJwk.y
248
+ });
249
+ var verifyJwt = async (token, publicJwk) => {
250
+ const [headerSegment, payloadSegment, signatureSegment] = token.split(".");
251
+ if (headerSegment === undefined || payloadSegment === undefined || signatureSegment === undefined) {
252
+ return;
253
+ }
254
+ const key = await crypto.subtle.importKey("jwk", publicJwk, KEY_PARAMS, false, ["verify"]);
255
+ const valid = await crypto.subtle.verify(ES256, key, fromBase64Url(signatureSegment), ENCODER.encode(`${headerSegment}.${payloadSegment}`));
256
+ if (!valid)
257
+ return;
258
+ const header = decodeSegment(headerSegment);
259
+ const payload = decodeSegment(payloadSegment);
260
+ if (header === undefined || payload === undefined)
261
+ return;
262
+ return {
263
+ header,
264
+ payload
265
+ };
266
+ };
267
+
268
+ // src/agents/registration.ts
269
+ var AGENT_CLAIM_GRANT_TYPE = "urn:workos:agent-auth:grant-type:claim";
270
+ var AGENT_IDENTITY_ASSERTION_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:jwt-bearer";
271
+ var AGENT_IDENTITY_ASSERTION_TYPE = "urn:ietf:params:oauth:token-type:id-jag";
272
+ var DEFAULT_IDENTITY_ROUTE = "/agent/identity";
273
+ var DEFAULT_CLAIM_ROUTE = "/agent/identity/claim";
274
+ var DEFAULT_COMPLETE_ROUTE = "/agent/identity/claim/complete";
275
+ var DEFAULT_GUIDE_ROUTE = "/auth.md";
276
+ var DEFAULT_CLAIM_TTL_MS = 24 * 60 * MILLISECONDS_IN_A_MINUTE;
277
+ var DEFAULT_ATTEMPT_TTL_MS = 10 * MILLISECONDS_IN_A_MINUTE;
278
+ var DEFAULT_ASSERTION_TTL_MS = 60 * MILLISECONDS_IN_A_MINUTE;
279
+ var DEFAULT_ACCESS_TOKEN_TTL_MS = 15 * MILLISECONDS_IN_A_MINUTE;
280
+ var DEFAULT_MAX_AUTH_AGE_MS = 60 * MILLISECONDS_IN_A_MINUTE;
281
+ var DEFAULT_POLL_INTERVAL_SECONDS = 5;
282
+ var DEFAULT_MAX_CODE_ATTEMPTS = 5;
283
+ var MAX_CONCURRENT_UPDATE_RETRIES = 5;
284
+ var TOKEN_BYTES = 32;
285
+ var agentRegistrationDiscoveryMetadata = (config) => {
286
+ const registration = requiredRegistration(config);
287
+ const endpoints = agentRegistrationEndpoints(config);
288
+ const identityTypes = ["identity_assertion"];
289
+ if (registration.allowAnonymous === true)
290
+ identityTypes.unshift("anonymous");
291
+ if (registration.allowServiceAuth === true)
292
+ identityTypes.push("service_auth");
293
+ return {
294
+ claim_endpoint: endpoints.claimEndpoint,
295
+ identity_assertion: {
296
+ assertion_types_supported: [AGENT_IDENTITY_ASSERTION_TYPE]
297
+ },
298
+ identity_endpoint: endpoints.identityEndpoint,
299
+ identity_types_supported: identityTypes,
300
+ skill: endpoints.guide
301
+ };
302
+ };
303
+ var agentRegistrationEndpoints = (config) => {
304
+ const registration = requiredRegistration(config);
305
+ const base = config.authorizationServer;
306
+ const oidcRoute = config.oidcRoute ?? "/oauth2";
307
+ return {
308
+ claimEndpoint: new URL(registration.claimRoute ?? DEFAULT_CLAIM_ROUTE, base).toString(),
309
+ completeEndpoint: new URL(registration.completeRoute ?? DEFAULT_COMPLETE_ROUTE, base).toString(),
310
+ guide: new URL(registration.guideRoute ?? DEFAULT_GUIDE_ROUTE, base).toString(),
311
+ identityEndpoint: new URL(registration.identityRoute ?? DEFAULT_IDENTITY_ROUTE, base).toString(),
312
+ tokenEndpoint: new URL(`${oidcRoute}/token`, base).toString()
313
+ };
314
+ };
315
+ var markdownJson = (value) => `\`\`\`json
316
+ ${JSON.stringify(value, null, 2)}
317
+ \`\`\``;
318
+ var generateAgentRegistrationGuide = (config) => {
319
+ const registration = requiredRegistration(config);
320
+ const endpoints = agentRegistrationEndpoints(config);
321
+ const metadataUrl = new URL(config.metadataRoute ?? "/.well-known/oauth-protected-resource", config.resource).toString();
322
+ const methods = [
323
+ "- `identity_assertion`: present an audience-bound ID-JAG from a trusted provider.",
324
+ ...registration.allowServiceAuth === true ? [
325
+ "- `service_auth`: provide a user login hint and complete the service-owned claim ceremony."
326
+ ] : [],
327
+ ...registration.allowAnonymous === true ? [
328
+ "- `anonymous`: receive pre-claim scopes, then optionally let a signed-in user claim the registration."
329
+ ] : []
330
+ ].join(`
331
+ `);
332
+ return `# Agent registration for ${config.resourceName ?? config.resource}
333
+
334
+ This service supports the open auth.md agent-registration profile. Structured
335
+ OAuth metadata is authoritative; this document is its agent-readable companion.
336
+
337
+ ## 1. Discover
338
+
339
+ Fetch ${metadataUrl}, follow \`authorization_servers\`, then fetch
340
+ \`/.well-known/oauth-authorization-server\`. Read its \`agent_auth\` object and
341
+ top-level \`token_endpoint\` and \`grant_types_supported\` fields.
342
+
343
+ ## 2. Choose a method
344
+
345
+ ${methods}
346
+
347
+ Before asserting a user identity, show the service name and requested scopes to
348
+ the user and obtain consent. Never ask the user to send a password or OTP to the
349
+ agent.
350
+
351
+ ## 3. Register
352
+
353
+ POST one of these bodies to ${endpoints.identityEndpoint}:
354
+
355
+ ${markdownJson({
356
+ assertion: "<ID-JAG>",
357
+ assertion_type: AGENT_IDENTITY_ASSERTION_TYPE,
358
+ type: "identity_assertion"
359
+ })}
360
+
361
+ ${registration.allowServiceAuth === true ? markdownJson({ login_hint: "user@example.com", type: "service_auth" }) : ""}
362
+
363
+ ${registration.allowAnonymous === true ? markdownJson({ type: "anonymous" }) : ""}
364
+
365
+ ## 4. Claim
366
+
367
+ Surface \`verification_uri\` and \`user_code\` together. The user opens the
368
+ service-owned page, signs in using the service's normal MFA/SSO policy, and types
369
+ the code there. Poll ${endpoints.tokenEndpoint} using
370
+ \`grant_type=${AGENT_CLAIM_GRANT_TYPE}\` and the one-time \`claim_token\`.
371
+ Treat \`authorization_pending\` as retryable, honor \`interval\`, and restart
372
+ when the server returns \`expired_token\`.
373
+
374
+ ## 5. Exchange and use credentials
375
+
376
+ Exchange \`identity_assertion\` at ${endpoints.tokenEndpoint} with
377
+ \`grant_type=${AGENT_IDENTITY_ASSERTION_GRANT_TYPE}\`. Present the resulting
378
+ access token as \`Authorization: Bearer <access_token>\`. Credentials are scoped,
379
+ short-lived, revocable, and bound to the registered agent identity.
380
+
381
+ ## Errors and safety
382
+
383
+ - Stop on \`invalid_issuer\`, \`invalid_signature\`, \`invalid_audience\`, or \`replay_detected\`.
384
+ - Reauthenticate the user on \`login_required\`.
385
+ - Open the service-owned confirmation URL on \`interaction_required\`.
386
+ - Never persist claim tokens after completion and never expose credentials in model context.
387
+ `;
388
+ };
389
+ var requiredRegistration = (config) => {
390
+ if (config.agentRegistration === undefined) {
391
+ throw new Error("agentAuth.agentRegistration is not configured");
392
+ }
393
+ return config.agentRegistration;
394
+ };
395
+ var randomCode = () => {
396
+ const bytes = crypto.getRandomValues(new Uint8Array(4));
397
+ const value = new DataView(bytes.buffer).getUint32(0) % 1e6;
398
+ return value.toString().padStart(6, "0");
399
+ };
400
+ var makeSecret = (prefix) => `${prefix}_${generateSecureToken(TOKEN_BYTES)}`;
401
+ var makeAttempt = async ({
402
+ email,
403
+ now,
404
+ registration
405
+ }) => {
406
+ const attemptToken = makeSecret("cat");
407
+ const userCode = randomCode();
408
+ const expiresAt = now + (registration.attemptTtlMs ?? DEFAULT_ATTEMPT_TTL_MS);
409
+ return {
410
+ attempt: {
411
+ attempts: 0,
412
+ email: email.toLowerCase(),
413
+ expiresAt,
414
+ tokenHash: await hashToken(attemptToken),
415
+ userCodeHash: await hashToken(userCode)
416
+ },
417
+ attemptToken,
418
+ expiresAt,
419
+ userCode
420
+ };
421
+ };
422
+ var createFlow = async ({
423
+ agentId,
424
+ kind,
425
+ loginHint,
426
+ now,
427
+ registration,
428
+ status,
429
+ upstream,
430
+ userId,
431
+ withAttempt
432
+ }) => {
433
+ const claimToken = makeSecret("clm");
434
+ const registrationId = `air_${crypto.randomUUID()}`;
435
+ const claimExpiresAt = now + (registration.claimTtlMs ?? DEFAULT_CLAIM_TTL_MS);
436
+ const attempt = withAttempt === undefined ? undefined : await makeAttempt({ email: withAttempt, now, registration });
437
+ const flow = {
438
+ agentId,
439
+ claimAttempt: attempt?.attempt,
440
+ claimExpiresAt,
441
+ claimTokenHash: await hashToken(claimToken),
442
+ createdAt: now,
443
+ expiresAt: claimExpiresAt,
444
+ kind,
445
+ loginHint,
446
+ registrationId,
447
+ status,
448
+ updatedAt: now,
449
+ upstream,
450
+ userId,
451
+ version: 1
452
+ };
453
+ if (!await registration.identityStore.create(flow)) {
454
+ throw new Error("Agent identity registration id collision");
455
+ }
456
+ return { attempt, claimToken, flow };
457
+ };
458
+ var assertionClaims = (config, flow, now) => ({
459
+ aud: config.authorizationServer,
460
+ exp: Math.floor((now + (config.agentRegistration?.assertionTtlMs ?? DEFAULT_ASSERTION_TTL_MS)) / MILLISECONDS_IN_A_SECOND),
461
+ iat: Math.floor(now / MILLISECONDS_IN_A_SECOND),
462
+ iss: config.authorizationServer,
463
+ jti: crypto.randomUUID(),
464
+ registration_version: flow.version,
465
+ sub: flow.registrationId
466
+ });
467
+ var issueAgentServiceAssertion = async (config, flow, now = Date.now()) => ({
468
+ assertion: await signJwt(assertionClaims(config, flow, now), requiredRegistration(config).signingKey, "oauth-id-jag+jwt"),
469
+ expiresAt: now + (requiredRegistration(config).assertionTtlMs ?? DEFAULT_ASSERTION_TTL_MS)
470
+ });
471
+ var activateAgent = async (config, flow, scopes) => {
472
+ const now = Date.now();
473
+ await config.registrationStore.saveRegistration({
474
+ agentId: flow.agentId,
475
+ allowedScopes: scopes.filter((scope) => config.scopes.includes(scope)),
476
+ createdAt: now,
477
+ metadata: {
478
+ identityRegistrationId: flow.registrationId,
479
+ registrationKind: flow.kind
480
+ },
481
+ name: `Agent registration ${flow.registrationId}`,
482
+ status: "active",
483
+ updatedAt: now
484
+ });
485
+ if (flow.userId !== undefined) {
486
+ await config.delegationStore.saveDelegation({
487
+ agentId: flow.agentId,
488
+ createdAt: now,
489
+ delegationId: `agd_${crypto.randomUUID()}`,
490
+ scopes: scopes.filter((scope) => config.scopes.includes(scope)),
491
+ status: "active",
492
+ updatedAt: now,
493
+ userId: flow.userId
494
+ });
495
+ }
496
+ };
497
+ var ceremony = (config, flow, attempt) => ({
498
+ expires_in: Math.max(0, Math.floor((attempt.expiresAt - Date.now()) / MILLISECONDS_IN_A_SECOND)),
499
+ interval: requiredRegistration(config).pollIntervalSeconds ?? DEFAULT_POLL_INTERVAL_SECONDS,
500
+ user_code: attempt.userCode,
501
+ verification_uri: `${agentRegistrationEndpoints(config).completeEndpoint}?claim_attempt_token=${encodeURIComponent(attempt.attemptToken)}`
502
+ });
503
+ var startAgentRegistration = async (config, input, now = Date.now()) => {
504
+ const registration = requiredRegistration(config);
505
+ const agentId = `agent_${crypto.randomUUID()}`;
506
+ if (input.type === "anonymous") {
507
+ if (registration.allowAnonymous !== true) {
508
+ return { error: "anonymous_not_enabled", status: 403 };
509
+ }
510
+ if (registration.revokeAccessTokens === undefined) {
511
+ throw new Error("Anonymous agent registration requires revokeAccessTokens");
512
+ }
513
+ const created2 = await createFlow({
514
+ agentId,
515
+ kind: "anonymous",
516
+ now,
517
+ registration,
518
+ status: "pending"
519
+ });
520
+ await activateAgent(config, created2.flow, registration.preClaimScopes ?? []);
521
+ const assertion2 = await issueAgentServiceAssertion(config, created2.flow, now);
522
+ return {
523
+ assertionExpires: assertion2.expiresAt,
524
+ claimToken: created2.claimToken,
525
+ claimTokenExpires: created2.flow.claimExpiresAt,
526
+ identityAssertion: assertion2.assertion,
527
+ postClaimScopes: registration.postClaimScopes,
528
+ preClaimScopes: registration.preClaimScopes ?? [],
529
+ registrationId: created2.flow.registrationId,
530
+ registrationType: "anonymous",
531
+ status: 200
532
+ };
533
+ }
534
+ if (input.type === "service_auth") {
535
+ if (registration.allowServiceAuth !== true) {
536
+ return { error: "service_auth_not_enabled", status: 403 };
537
+ }
538
+ const email = input.loginHint.trim().toLowerCase();
539
+ if (!/^[^@\s]+@[^@\s]+\.[^@\s]+$/u.test(email)) {
540
+ return { error: "invalid_login_hint", status: 400 };
541
+ }
542
+ const created2 = await createFlow({
543
+ agentId,
544
+ kind: "service_auth",
545
+ loginHint: email,
546
+ now,
547
+ registration,
548
+ status: "pending",
549
+ withAttempt: email
550
+ });
551
+ if (created2.attempt === undefined)
552
+ throw new Error("Claim attempt missing");
553
+ return {
554
+ assertionExpires: 0,
555
+ claim: ceremony(config, created2.flow, created2.attempt),
556
+ claimToken: created2.claimToken,
557
+ claimTokenExpires: created2.flow.claimExpiresAt,
558
+ postClaimScopes: registration.postClaimScopes,
559
+ registrationId: created2.flow.registrationId,
560
+ registrationType: "service_auth",
561
+ status: 200
562
+ };
563
+ }
564
+ if (input.assertionType !== AGENT_IDENTITY_ASSERTION_TYPE || registration.verifyIdentityAssertion === undefined) {
565
+ return { error: "invalid_request", status: 400 };
566
+ }
567
+ const identity = await registration.verifyIdentityAssertion(input.assertion);
568
+ if (identity === undefined) {
569
+ return { error: "invalid_identity_assertion", status: 401 };
570
+ }
571
+ const authAge = now - identity.authenticatedAt;
572
+ if (authAge < -MILLISECONDS_IN_A_MINUTE || authAge > (registration.maxAuthenticationAgeMs ?? DEFAULT_MAX_AUTH_AGE_MS)) {
573
+ return { error: "login_required", status: 401 };
574
+ }
575
+ if (identity.emailVerified !== true && identity.phoneNumberVerified !== true) {
576
+ return { error: "missing_verified_identity", status: 403 };
577
+ }
578
+ const existing = await registration.identityStore.findByUpstreamIdentity({
579
+ clientId: identity.clientId,
580
+ issuer: identity.issuer,
581
+ subject: identity.subject
582
+ });
583
+ if (existing?.status === "claimed") {
584
+ const assertion2 = await issueAgentServiceAssertion(config, existing, now);
585
+ return {
586
+ assertionExpires: assertion2.expiresAt,
587
+ identityAssertion: assertion2.assertion,
588
+ postClaimScopes: registration.postClaimScopes,
589
+ registrationId: existing.registrationId,
590
+ registrationType: "identity_assertion",
591
+ status: 200
592
+ };
593
+ }
594
+ const match = await registration.resolveVerifiedIdentity?.(identity);
595
+ const verifiedEmail = identity.emailVerified === true ? identity.email : undefined;
596
+ if (match?.userId === undefined && verifiedEmail === undefined) {
597
+ return { error: "interaction_required", status: 401 };
598
+ }
599
+ const created = await createFlow({
600
+ agentId,
601
+ kind: "identity_assertion",
602
+ now,
603
+ registration,
604
+ status: match?.userId === undefined ? "pending" : "claimed",
605
+ upstream: {
606
+ clientId: identity.clientId,
607
+ issuer: identity.issuer,
608
+ subject: identity.subject
609
+ },
610
+ userId: match?.userId,
611
+ withAttempt: match?.userId === undefined ? verifiedEmail : undefined
612
+ });
613
+ if (match?.userId === undefined) {
614
+ if (created.attempt === undefined)
615
+ throw new Error("Claim attempt missing");
616
+ return {
617
+ assertionExpires: 0,
618
+ claim: ceremony(config, created.flow, created.attempt),
619
+ claimToken: created.claimToken,
620
+ claimTokenExpires: created.flow.claimExpiresAt,
621
+ postClaimScopes: registration.postClaimScopes,
622
+ registrationId: created.flow.registrationId,
623
+ registrationType: "identity_assertion",
624
+ status: 200
625
+ };
626
+ }
627
+ await activateAgent(config, created.flow, registration.postClaimScopes);
628
+ const assertion = await issueAgentServiceAssertion(config, created.flow, now);
629
+ return {
630
+ assertionExpires: assertion.expiresAt,
631
+ identityAssertion: assertion.assertion,
632
+ postClaimScopes: registration.postClaimScopes,
633
+ registrationId: created.flow.registrationId,
634
+ registrationType: "identity_assertion",
635
+ status: 200
636
+ };
637
+ };
638
+ var beginAgentClaim = async (config, input, now = Date.now()) => {
639
+ const registration = requiredRegistration(config);
640
+ const email = input.email.trim().toLowerCase();
641
+ const claimTokenHash = await hashToken(input.claimToken);
642
+ for (let retry = 0;retry < MAX_CONCURRENT_UPDATE_RETRIES; retry += 1) {
643
+ const flow = await registration.identityStore.findByClaimTokenHash(claimTokenHash);
644
+ if (flow === undefined) {
645
+ return { error: "invalid_claim_token", status: 400 };
646
+ }
647
+ if (flow.claimExpiresAt <= now || flow.status === "revoked") {
648
+ return { error: "claim_expired", status: 400 };
649
+ }
650
+ if (flow.loginHint !== undefined && flow.loginHint !== email) {
651
+ return { error: "invalid_claim_token", status: 400 };
652
+ }
653
+ const attempt = await makeAttempt({ email, now, registration });
654
+ const replacement = {
655
+ ...flow,
656
+ claimAttempt: attempt.attempt,
657
+ loginHint: email,
658
+ updatedAt: now
659
+ };
660
+ if (await registration.identityStore.replace(replacement, flow.version)) {
661
+ return {
662
+ claimAttempt: ceremony(config, replacement, attempt),
663
+ status: 200
664
+ };
665
+ }
666
+ }
667
+ return { error: "concurrent_update", status: 409 };
668
+ };
669
+ var completeAgentClaim = async (config, input, now = Date.now()) => {
670
+ const registration = requiredRegistration(config);
671
+ const user = await registration.resolveAuthenticatedUser(input.request);
672
+ if (user === undefined)
673
+ return { error: "wrong_user", status: 403 };
674
+ const attemptTokenHash = await hashToken(input.attemptToken);
675
+ const userCodeHash = await hashToken(input.userCode);
676
+ for (let retry = 0;retry < MAX_CONCURRENT_UPDATE_RETRIES; retry += 1) {
677
+ const flow = await registration.identityStore.findByAttemptTokenHash(attemptTokenHash);
678
+ const attempt = flow?.claimAttempt;
679
+ if (flow === undefined || attempt === undefined) {
680
+ return { error: "invalid_claim_attempt", status: 400 };
681
+ }
682
+ if (flow.claimExpiresAt <= now || attempt.expiresAt <= now) {
683
+ return { error: "claim_expired", status: 400 };
684
+ }
685
+ if (user.email?.toLowerCase() !== attempt.email) {
686
+ return { error: "wrong_user", status: 403 };
687
+ }
688
+ const matches = await constantTimeEqual(userCodeHash, attempt.userCodeHash);
689
+ if (!matches) {
690
+ const attempts = attempt.attempts + 1;
691
+ const locked = attempts >= (registration.maxCodeAttempts ?? DEFAULT_MAX_CODE_ATTEMPTS);
692
+ const replaced = await registration.identityStore.replace({
693
+ ...flow,
694
+ claimAttempt: locked ? undefined : { ...attempt, attempts },
695
+ updatedAt: now
696
+ }, flow.version);
697
+ if (replaced) {
698
+ return {
699
+ error: "user_code_invalid",
700
+ status: locked ? 429 : 400
701
+ };
702
+ }
703
+ continue;
704
+ }
705
+ if (flow.kind === "anonymous") {
706
+ await registration.revokeAccessTokens?.(flow.agentId);
707
+ }
708
+ const replacement = {
709
+ ...flow,
710
+ claimAttempt: undefined,
711
+ status: "claimed",
712
+ updatedAt: now,
713
+ userId: user.userId
714
+ };
715
+ if (!await registration.identityStore.replace(replacement, flow.version)) {
716
+ continue;
717
+ }
718
+ const persisted = await registration.identityStore.findByRegistrationId(flow.registrationId);
719
+ if (persisted === undefined) {
720
+ throw new Error("Completed agent registration disappeared");
721
+ }
722
+ await activateAgent(config, persisted, registration.postClaimScopes);
723
+ return { status: 204 };
724
+ }
725
+ return { error: "concurrent_update", status: 409 };
726
+ };
727
+ var issueAgentAccessToken = async (config, flow, now) => {
728
+ const registration = requiredRegistration(config);
729
+ const accessToken = `at_${generateSecureToken(TOKEN_BYTES)}`;
730
+ const scopes = (flow.status === "claimed" ? registration.postClaimScopes : registration.preClaimScopes ?? []).filter((scope) => config.scopes.includes(scope));
731
+ const expiresAt = now + (registration.tokenTtlMs ?? DEFAULT_ACCESS_TOKEN_TTL_MS);
732
+ await registration.accessTokenStore.saveToken({
733
+ clientId: flow.agentId,
734
+ createdAt: now,
735
+ expiresAt,
736
+ hashedToken: await hashToken(accessToken),
737
+ ownerId: flow.userId,
738
+ scopes,
739
+ tokenId: crypto.randomUUID()
740
+ });
741
+ return {
742
+ accessToken,
743
+ expiresIn: Math.floor((expiresAt - now) / MILLISECONDS_IN_A_SECOND),
744
+ scopes
745
+ };
746
+ };
747
+ var createAgentRegistrationCredentialVerifier = (accessTokenStore, identityStore) => async (request) => {
748
+ const authorization = request.headers.get("authorization");
749
+ if (authorization?.startsWith("Bearer at_") !== true)
750
+ return;
751
+ const token = authorization.slice("Bearer ".length).trim();
752
+ const record = await accessTokenStore.findByHashedToken(await hashToken(token));
753
+ if (record === undefined || record.expiresAt <= Date.now())
754
+ return;
755
+ if (identityStore !== undefined) {
756
+ const identity = await identityStore.findByAgentId(record.clientId);
757
+ if (identity === undefined || identity.status === "revoked") {
758
+ return;
759
+ }
760
+ }
761
+ return {
762
+ agentId: record.clientId,
763
+ expiresAt: record.expiresAt,
764
+ scopes: record.scopes,
765
+ userId: record.ownerId
766
+ };
767
+ };
768
+ var handleAgentTokenGrant = async (config, body, now = Date.now()) => {
769
+ const registration = requiredRegistration(config);
770
+ if (body.grant_type === AGENT_CLAIM_GRANT_TYPE) {
771
+ if (body.claim_token === undefined) {
772
+ return { body: { error: "invalid_request" }, status: 400 };
773
+ }
774
+ const claimTokenHash = await hashToken(body.claim_token);
775
+ let flow2;
776
+ for (let retry = 0;retry < MAX_CONCURRENT_UPDATE_RETRIES; retry += 1) {
777
+ flow2 = await registration.identityStore.findByClaimTokenHash(claimTokenHash);
778
+ if (flow2 === undefined || flow2.claimExpiresAt <= now) {
779
+ return { body: { error: "expired_token" }, status: 400 };
780
+ }
781
+ if (flow2.status === "claimed")
782
+ break;
783
+ if (flow2.claimAttempt !== undefined && flow2.claimAttempt.expiresAt <= now) {
784
+ return { body: { error: "expired_token" }, status: 400 };
785
+ }
786
+ const intervalMs = (registration.pollIntervalSeconds ?? DEFAULT_POLL_INTERVAL_SECONDS) * MILLISECONDS_IN_A_SECOND;
787
+ if (flow2.lastPolledAt !== undefined && now - flow2.lastPolledAt < intervalMs) {
788
+ return { body: { error: "slow_down" }, status: 400 };
789
+ }
790
+ if (await registration.identityStore.replace({ ...flow2, lastPolledAt: now, updatedAt: now }, flow2.version)) {
791
+ return {
792
+ body: { error: "authorization_pending" },
793
+ status: 400
794
+ };
795
+ }
796
+ }
797
+ if (flow2?.status !== "claimed") {
798
+ return { body: { error: "temporarily_unavailable" }, status: 400 };
799
+ }
800
+ const assertion = await issueAgentServiceAssertion(config, flow2, now);
801
+ const token2 = await issueAgentAccessToken(config, flow2, now);
802
+ return {
803
+ body: {
804
+ access_token: token2.accessToken,
805
+ assertion_expires: new Date(assertion.expiresAt).toISOString(),
806
+ expires_in: token2.expiresIn,
807
+ identity_assertion: assertion.assertion,
808
+ scope: token2.scopes.join(" "),
809
+ token_type: "Bearer"
810
+ },
811
+ status: 200
812
+ };
813
+ }
814
+ if (body.grant_type !== AGENT_IDENTITY_ASSERTION_GRANT_TYPE) {
815
+ return;
816
+ }
817
+ if (body.assertion === undefined) {
818
+ return { body: { error: "invalid_request" }, status: 400 };
819
+ }
820
+ const verified = await verifyJwt(body.assertion, registration.signingKey.publicJwk);
821
+ const payload = verified?.payload;
822
+ if (verified?.header?.typ !== "oauth-id-jag+jwt" || payload?.iss !== config.authorizationServer || payload.aud !== config.authorizationServer || typeof payload.sub !== "string" || typeof payload.exp !== "number" || payload.exp <= Math.floor(now / MILLISECONDS_IN_A_SECOND)) {
823
+ return { body: { error: "invalid_grant" }, status: 400 };
824
+ }
825
+ const flow = await registration.identityStore.findByRegistrationId(payload.sub);
826
+ if (flow === undefined || flow.status === "revoked" || payload.registration_version !== flow.version) {
827
+ return { body: { error: "invalid_grant" }, status: 400 };
828
+ }
829
+ const token = await issueAgentAccessToken(config, flow, now);
830
+ return {
831
+ body: {
832
+ access_token: token.accessToken,
833
+ expires_in: token.expiresIn,
834
+ scope: token.scopes.join(" "),
835
+ token_type: "Bearer"
836
+ },
837
+ status: 200
838
+ };
839
+ };
840
+ var revokeAgentIdentityRegistration = async (config, registrationId, now = Date.now()) => {
841
+ const registration = requiredRegistration(config);
842
+ for (let retry = 0;retry < MAX_CONCURRENT_UPDATE_RETRIES; retry += 1) {
843
+ const flow = await registration.identityStore.findByRegistrationId(registrationId);
844
+ if (flow === undefined)
845
+ return false;
846
+ if (flow.status === "revoked")
847
+ return true;
848
+ await registration.revokeAccessTokens?.(flow.agentId);
849
+ if (await registration.identityStore.replace({
850
+ ...flow,
851
+ claimAttempt: undefined,
852
+ status: "revoked",
853
+ updatedAt: now
854
+ }, flow.version)) {
855
+ return true;
856
+ }
857
+ }
858
+ throw new Error("Could not revoke agent identity after concurrent updates");
859
+ };
860
+ // src/agents/registrationClient.ts
861
+ var isObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
862
+ var secureUrl = (value, allowLocalhost) => {
863
+ if (typeof value !== "string")
864
+ return;
865
+ try {
866
+ const url = new URL(value);
867
+ if (url.protocol === "https:")
868
+ return url.toString();
869
+ if (allowLocalhost && url.protocol === "http:" && (url.hostname === "localhost" || url.hostname === "127.0.0.1")) {
870
+ return url.toString();
871
+ }
872
+ } catch {
873
+ return;
874
+ }
875
+ return;
876
+ };
877
+ var readBoundedJson = async (response, maxBytes) => {
878
+ const length = Number(response.headers.get("content-length"));
879
+ if (Number.isFinite(length) && length > maxBytes) {
880
+ throw new Error("Agent registration metadata exceeds the response limit");
881
+ }
882
+ const bytes = new Uint8Array(await response.arrayBuffer());
883
+ if (bytes.byteLength > maxBytes) {
884
+ throw new Error("Agent registration metadata exceeds the response limit");
885
+ }
886
+ const parsed = JSON.parse(new TextDecoder().decode(bytes));
887
+ return parsed;
888
+ };
889
+ var stringArray = (value) => Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : [];
890
+ var requestJson = async (request, url, init, maxBytes) => {
891
+ const response = await request(url, {
892
+ ...init,
893
+ headers: {
894
+ accept: "application/json",
895
+ ...init.headers
896
+ },
897
+ redirect: "error"
898
+ });
899
+ const body = await readBoundedJson(response, maxBytes);
900
+ if (!isObject(body))
901
+ throw new Error("Expected a JSON object");
902
+ return { body, response };
903
+ };
904
+ var createAgentRegistrationClient = (discovery, options = {}) => {
905
+ const request = options.request ?? fetch;
906
+ const maxBytes = options.maxResponseBytes ?? 256 * 1024;
907
+ const post = (url, body, form = false) => requestJson(request, url, {
908
+ body: form ? new URLSearchParams(Object.fromEntries(Object.entries(body).map(([key, value]) => [
909
+ key,
910
+ String(value)
911
+ ]))) : JSON.stringify(body),
912
+ headers: {
913
+ "content-type": form ? "application/x-www-form-urlencoded" : "application/json"
914
+ },
915
+ method: "POST"
916
+ }, maxBytes);
917
+ return {
918
+ beginAnonymous: () => post(discovery.agentAuth.identityEndpoint, { type: "anonymous" }),
919
+ beginServiceAuth: (loginHint) => post(discovery.agentAuth.identityEndpoint, {
920
+ login_hint: loginHint,
921
+ type: "service_auth"
922
+ }),
923
+ beginVerified: (assertion) => {
924
+ if (!discovery.agentAuth.identityAssertionTypes.includes(AGENT_IDENTITY_ASSERTION_TYPE)) {
925
+ throw new Error("Service does not accept ID-JAG assertions");
926
+ }
927
+ return post(discovery.agentAuth.identityEndpoint, {
928
+ assertion,
929
+ assertion_type: AGENT_IDENTITY_ASSERTION_TYPE,
930
+ type: "identity_assertion"
931
+ });
932
+ },
933
+ claim: (claimToken, email) => post(discovery.agentAuth.claimEndpoint, {
934
+ claim_token: claimToken,
935
+ email
936
+ }),
937
+ exchangeAssertion: (assertion) => post(discovery.tokenEndpoint, {
938
+ assertion,
939
+ grant_type: AGENT_IDENTITY_ASSERTION_GRANT_TYPE,
940
+ resource: discovery.resource
941
+ }, true),
942
+ pollClaim: (claimToken) => post(discovery.tokenEndpoint, {
943
+ claim_token: claimToken,
944
+ grant_type: AGENT_CLAIM_GRANT_TYPE
945
+ }, true)
946
+ };
947
+ };
948
+ var discoverAgentRegistration = async (resource, options = {}) => {
949
+ const request = options.request ?? fetch;
950
+ const maxBytes = options.maxResponseBytes ?? 256 * 1024;
951
+ const allowLocalhost = options.allowInsecureLocalhost === true;
952
+ const resourceUrl = secureUrl(resource, allowLocalhost);
953
+ if (resourceUrl === undefined)
954
+ throw new Error("Resource URL must use HTTPS");
955
+ const resourceMetadataUrl = new URL("/.well-known/oauth-protected-resource", resourceUrl).toString();
956
+ const prm = await requestJson(request, resourceMetadataUrl, {}, maxBytes);
957
+ const advertisedResource = secureUrl(prm.body.resource, allowLocalhost);
958
+ if (advertisedResource === undefined || advertisedResource !== resourceUrl) {
959
+ throw new Error("Protected resource metadata identity mismatch");
960
+ }
961
+ const authorizationServer = secureUrl(stringArray(prm.body.authorization_servers)[0], allowLocalhost);
962
+ if (authorizationServer === undefined) {
963
+ throw new Error("No secure authorization server is advertised");
964
+ }
965
+ const asUrl = new URL("/.well-known/oauth-authorization-server", authorizationServer).toString();
966
+ const metadata = await requestJson(request, asUrl, {}, maxBytes);
967
+ if (secureUrl(metadata.body.issuer, allowLocalhost) !== authorizationServer) {
968
+ throw new Error("Authorization server issuer mismatch");
969
+ }
970
+ const tokenEndpoint = secureUrl(metadata.body.token_endpoint, allowLocalhost);
971
+ const agentAuth = metadata.body.agent_auth;
972
+ if (tokenEndpoint === undefined || !isObject(agentAuth)) {
973
+ throw new Error("Authorization server does not advertise agent registration");
974
+ }
975
+ const identityEndpoint = secureUrl(agentAuth.identity_endpoint, allowLocalhost);
976
+ const claimEndpoint = secureUrl(agentAuth.claim_endpoint, allowLocalhost);
977
+ const skill = secureUrl(agentAuth.skill, allowLocalhost);
978
+ const assertionMetadata = agentAuth.identity_assertion;
979
+ if (identityEndpoint === undefined || claimEndpoint === undefined || skill === undefined || !isObject(assertionMetadata)) {
980
+ throw new Error("Agent registration metadata is incomplete");
981
+ }
982
+ return {
983
+ agentAuth: {
984
+ claimEndpoint,
985
+ identityAssertionTypes: stringArray(assertionMetadata.assertion_types_supported),
986
+ identityEndpoint,
987
+ identityTypes: stringArray(agentAuth.identity_types_supported),
988
+ skill
989
+ },
990
+ authorizationServer,
991
+ resource: resourceUrl,
992
+ resourceMetadataUrl,
993
+ scopes: stringArray(prm.body.scopes_supported),
994
+ tokenEndpoint
995
+ };
996
+ };
997
+ // src/agents/idJag.ts
998
+ init_constants();
999
+ var createInMemoryAgentIdentityAssertionJtiStore = () => {
1000
+ const entries = new Map;
1001
+ return {
1002
+ recordIfFresh: async (issuer, jti, expiresAt) => {
1003
+ const now = Date.now();
1004
+ for (const [key2, expiry] of entries) {
1005
+ if (expiry <= now)
1006
+ entries.delete(key2);
1007
+ }
1008
+ const key = `${issuer}\x00${jti}`;
1009
+ if (entries.has(key))
1010
+ return false;
1011
+ entries.set(key, expiresAt);
1012
+ return true;
1013
+ }
1014
+ };
1015
+ };
1016
+ var issueAgentIdentityAssertion = async ({
1017
+ agentContextId,
1018
+ agentPlatform,
1019
+ audience,
1020
+ clientId,
1021
+ issuer,
1022
+ now = Date.now(),
1023
+ resource,
1024
+ signingKey,
1025
+ ttlMs = 5 * 60 * MILLISECONDS_IN_A_SECOND,
1026
+ user
1027
+ }) => {
1028
+ if (user.emailVerified !== true && user.phoneNumberVerified !== true) {
1029
+ throw new Error("ID-JAG issuance requires a verified email or phone number");
1030
+ }
1031
+ const expiresAt = now + ttlMs;
1032
+ const payload = {
1033
+ aud: audience,
1034
+ auth_time: Math.floor(user.authenticatedAt / MILLISECONDS_IN_A_SECOND),
1035
+ client_id: clientId,
1036
+ exp: Math.floor(expiresAt / MILLISECONDS_IN_A_SECOND),
1037
+ iat: Math.floor(now / MILLISECONDS_IN_A_SECOND),
1038
+ iss: issuer,
1039
+ jti: crypto.randomUUID(),
1040
+ sub: user.subject
1041
+ };
1042
+ if (user.email !== undefined)
1043
+ payload.email = user.email;
1044
+ if (user.emailVerified !== undefined)
1045
+ payload.email_verified = user.emailVerified;
1046
+ if (user.name !== undefined)
1047
+ payload.name = user.name;
1048
+ if (user.phoneNumber !== undefined)
1049
+ payload.phone_number = user.phoneNumber;
1050
+ if (user.phoneNumberVerified !== undefined)
1051
+ payload.phone_number_verified = user.phoneNumberVerified;
1052
+ if (user.methods !== undefined)
1053
+ payload.amr = user.methods;
1054
+ if (resource !== undefined)
1055
+ payload.resource = resource;
1056
+ if (agentPlatform !== undefined)
1057
+ payload.agent_platform = agentPlatform;
1058
+ if (agentContextId !== undefined)
1059
+ payload.agent_context_id = agentContextId;
1060
+ return {
1061
+ assertion: await signJwt(payload, signingKey, "oauth-id-jag+jwt"),
1062
+ assertionType: AGENT_IDENTITY_ASSERTION_TYPE,
1063
+ expiresAt
1064
+ };
1065
+ };
1066
+ var numberClaim = (value) => typeof value === "number" && Number.isFinite(value) ? value : undefined;
1067
+ var stringClaim = (value) => typeof value === "string" && value.length > 0 ? value : undefined;
1068
+ var createAgentIdentityAssertionVerifier = ({
1069
+ audience,
1070
+ clockSkewMs = MILLISECONDS_IN_A_SECOND * 60,
1071
+ jtiStore,
1072
+ maxAssertionLifetimeMs = MILLISECONDS_IN_A_SECOND * 60 * 60,
1073
+ maxAuthenticationAgeMs = MILLISECONDS_IN_A_SECOND * 60 * 60,
1074
+ resolveIssuer
1075
+ }) => async (assertion, now = Date.now()) => {
1076
+ const segments = assertion.split(".");
1077
+ if (segments.length !== 3 || segments[1] === undefined)
1078
+ return;
1079
+ let decoded;
1080
+ try {
1081
+ decoded = JSON.parse(Buffer.from(segments[1], "base64url").toString("utf8"));
1082
+ } catch {
1083
+ return;
1084
+ }
1085
+ if (typeof decoded !== "object" || decoded === null || Array.isArray(decoded)) {
1086
+ return;
1087
+ }
1088
+ const unverified = Object.fromEntries(Object.entries(decoded));
1089
+ const issuer = stringClaim(unverified.iss);
1090
+ if (issuer === undefined)
1091
+ return;
1092
+ const trusted = await resolveIssuer(issuer);
1093
+ if (trusted === undefined)
1094
+ return;
1095
+ const verified = await verifyJwt(assertion, trusted.publicJwk);
1096
+ if (verified?.header?.typ !== "oauth-id-jag+jwt")
1097
+ return;
1098
+ const { payload } = verified;
1099
+ const subject = stringClaim(payload.sub);
1100
+ const jti = stringClaim(payload.jti);
1101
+ const clientId = stringClaim(payload.client_id);
1102
+ const expiresAtSeconds = numberClaim(payload.exp);
1103
+ const issuedAtSeconds = numberClaim(payload.iat);
1104
+ const authenticatedAtSeconds = numberClaim(payload.auth_time);
1105
+ if (payload.iss !== issuer || payload.aud !== audience || subject === undefined || jti === undefined || clientId === undefined || expiresAtSeconds === undefined || issuedAtSeconds === undefined || authenticatedAtSeconds === undefined) {
1106
+ return;
1107
+ }
1108
+ const expiresAt = expiresAtSeconds * MILLISECONDS_IN_A_SECOND;
1109
+ const issuedAt = issuedAtSeconds * MILLISECONDS_IN_A_SECOND;
1110
+ const authenticatedAt = authenticatedAtSeconds * MILLISECONDS_IN_A_SECOND;
1111
+ if (expiresAt <= now - clockSkewMs || issuedAt > now + clockSkewMs || expiresAt <= issuedAt || expiresAt - issuedAt > maxAssertionLifetimeMs + clockSkewMs || authenticatedAt > now + clockSkewMs || authenticatedAt > issuedAt + clockSkewMs || now - authenticatedAt > maxAuthenticationAgeMs + clockSkewMs) {
1112
+ return;
1113
+ }
1114
+ if (trusted.allowedClientIds !== undefined && !trusted.allowedClientIds.includes(clientId)) {
1115
+ return;
1116
+ }
1117
+ const email = stringClaim(payload.email);
1118
+ const phoneNumber = stringClaim(payload.phone_number);
1119
+ const emailVerified = payload.email_verified === true;
1120
+ const phoneNumberVerified = payload.phone_number_verified === true;
1121
+ if (!emailVerified && !phoneNumberVerified)
1122
+ return;
1123
+ if (!await jtiStore.recordIfFresh(issuer, jti, expiresAt)) {
1124
+ return;
1125
+ }
1126
+ return {
1127
+ authenticatedAt,
1128
+ clientId,
1129
+ email,
1130
+ emailVerified,
1131
+ issuer,
1132
+ name: stringClaim(payload.name),
1133
+ phoneNumber,
1134
+ phoneNumberVerified,
1135
+ subject
1136
+ };
1137
+ };
68
1138
  // src/oidc/clientIdMetadata.ts
69
- var secureUrl = (value) => {
1139
+ var secureUrl2 = (value) => {
70
1140
  try {
71
1141
  return new URL(value).protocol === "https:";
72
1142
  } catch {
@@ -77,7 +1147,7 @@ var validateClientIdMetadataDocument = (document, expectedClientId) => {
77
1147
  const errors = [];
78
1148
  if (document.client_id !== expectedClientId)
79
1149
  errors.push("client_id does not match the metadata document URL");
80
- if (!secureUrl(document.client_id))
1150
+ if (!secureUrl2(document.client_id))
81
1151
  errors.push("client_id must use HTTPS");
82
1152
  if (!Array.isArray(document.redirect_uris) || document.redirect_uris.length === 0)
83
1153
  errors.push("redirect_uris is required");
@@ -97,7 +1167,7 @@ var validateClientIdMetadataDocument = (document, expectedClientId) => {
97
1167
  ["tos_uri", document.tos_uri],
98
1168
  ["jwks_uri", document.jwks_uri]
99
1169
  ]) {
100
- if (value !== undefined && !secureUrl(value))
1170
+ if (value !== undefined && !secureUrl2(value))
101
1171
  errors.push(`${name} must use HTTPS`);
102
1172
  }
103
1173
  return errors;
@@ -120,7 +1190,7 @@ var createClientIdMetadataResolver = ({
120
1190
  }) => {
121
1191
  const cache = new Map;
122
1192
  return async (clientId) => {
123
- if (!secureUrl(clientId) || !await allow(clientId))
1193
+ if (!secureUrl2(clientId) || !await allow(clientId))
124
1194
  return;
125
1195
  const cached = cache.get(clientId);
126
1196
  if (cached !== undefined && cached.expiresAt > now())
@@ -150,62 +1220,6 @@ var createClientIdMetadataResolver = ({
150
1220
  return client;
151
1221
  };
152
1222
  };
153
- // src/oidc/keys.ts
154
- var ENCODER = new TextEncoder;
155
- var ES256 = { hash: "SHA-256", name: "ECDSA" };
156
- var KEY_PARAMS = { name: "ECDSA", namedCurve: "P-256" };
157
- var toBase64Url = (bytes) => Buffer.from(bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes)).toString("base64url");
158
- var fromBase64Url = (value) => new Uint8Array(Buffer.from(value, "base64url"));
159
- var encodeSegment = (value) => Buffer.from(JSON.stringify(value)).toString("base64url");
160
- var decodeSegment = (segment) => JSON.parse(Buffer.from(segment, "base64url").toString("utf8"));
161
- var generateSigningKey = async () => {
162
- const pair = await crypto.subtle.generateKey(KEY_PARAMS, true, [
163
- "sign",
164
- "verify"
165
- ]);
166
- const privateJwk = await crypto.subtle.exportKey("jwk", pair.privateKey);
167
- const publicJwk = await crypto.subtle.exportKey("jwk", pair.publicKey);
168
- return { kid: await jwkThumbprint(publicJwk), privateJwk, publicJwk };
169
- };
170
- var jwkThumbprint = async (jwk) => {
171
- const canonical = JSON.stringify({
172
- crv: jwk.crv,
173
- kty: jwk.kty,
174
- x: jwk.x,
175
- y: jwk.y
176
- });
177
- return toBase64Url(await crypto.subtle.digest("SHA-256", ENCODER.encode(canonical)));
178
- };
179
- var signJwt = async (payload, signing) => {
180
- const key = await crypto.subtle.importKey("jwk", signing.privateJwk, KEY_PARAMS, false, ["sign"]);
181
- const input = `${encodeSegment({ alg: "ES256", kid: signing.kid, typ: "JWT" })}.${encodeSegment(payload)}`;
182
- const signature = await crypto.subtle.sign(ES256, key, ENCODER.encode(input));
183
- return `${input}.${toBase64Url(signature)}`;
184
- };
185
- var toPublicJwk = (key) => ({
186
- alg: "ES256",
187
- crv: key.publicJwk.crv,
188
- kid: key.kid,
189
- kty: key.publicJwk.kty,
190
- use: "sig",
191
- x: key.publicJwk.x,
192
- y: key.publicJwk.y
193
- });
194
- var verifyJwt = async (token, publicJwk) => {
195
- const [headerSegment, payloadSegment, signatureSegment] = token.split(".");
196
- if (headerSegment === undefined || payloadSegment === undefined || signatureSegment === undefined) {
197
- return;
198
- }
199
- const key = await crypto.subtle.importKey("jwk", publicJwk, KEY_PARAMS, false, ["verify"]);
200
- const valid = await crypto.subtle.verify(ES256, key, fromBase64Url(signatureSegment), ENCODER.encode(`${headerSegment}.${payloadSegment}`));
201
- if (!valid)
202
- return;
203
- return {
204
- header: decodeSegment(headerSegment),
205
- payload: decodeSegment(payloadSegment)
206
- };
207
- };
208
-
209
1223
  // src/oidc/dpop.ts
210
1224
  init_constants();
211
1225
  var DEFAULT_MAX_AGE_MS = 60000;
@@ -449,6 +1463,67 @@ var failureResponse = (config, failure, requiredScopes) => new Response(JSON.str
449
1463
  },
450
1464
  status: failure.code === "Forbidden" ? 403 : 401
451
1465
  });
1466
+ var json = (value, status = 200) => new Response(JSON.stringify(value), {
1467
+ headers: {
1468
+ "cache-control": "no-store",
1469
+ "content-type": "application/json"
1470
+ },
1471
+ status
1472
+ });
1473
+ var recordBody = (body) => {
1474
+ if (typeof body !== "object" || body === null || Array.isArray(body)) {
1475
+ return;
1476
+ }
1477
+ return Object.fromEntries(Object.entries(body));
1478
+ };
1479
+ var registrationResponse = (result) => {
1480
+ const body = {
1481
+ ...result.assertionExpires > 0 ? {
1482
+ assertion_expires: new Date(result.assertionExpires).toISOString()
1483
+ } : {},
1484
+ ...result.claim === undefined ? {} : { claim: result.claim },
1485
+ ...result.claimToken === undefined ? {} : { claim_token: result.claimToken },
1486
+ ...result.claimTokenExpires === undefined ? {} : {
1487
+ claim_token_expires: new Date(result.claimTokenExpires).toISOString()
1488
+ },
1489
+ ...result.identityAssertion === undefined ? {} : { identity_assertion: result.identityAssertion },
1490
+ ...result.preClaimScopes === undefined ? {} : { pre_claim_scopes: result.preClaimScopes },
1491
+ post_claim_scopes: result.postClaimScopes,
1492
+ registration_id: result.registrationId,
1493
+ registration_type: result.registrationType
1494
+ };
1495
+ if (result.registrationType === "identity_assertion" && result.identityAssertion === undefined) {
1496
+ return json({
1497
+ ...body,
1498
+ error: "interaction_required",
1499
+ error_description: "Authenticate at the service and confirm the account link."
1500
+ }, 401);
1501
+ }
1502
+ return json(body);
1503
+ };
1504
+ var parseRegistrationInput = (value) => {
1505
+ if (value.type === "anonymous") {
1506
+ const input2 = { type: "anonymous" };
1507
+ return input2;
1508
+ }
1509
+ if (value.type === "service_auth" && typeof value.login_hint === "string") {
1510
+ const input2 = {
1511
+ loginHint: value.login_hint,
1512
+ type: "service_auth"
1513
+ };
1514
+ return input2;
1515
+ }
1516
+ if (value.type !== "identity_assertion" || value.assertion_type !== AGENT_IDENTITY_ASSERTION_TYPE || typeof value.assertion !== "string") {
1517
+ return;
1518
+ }
1519
+ const input = {
1520
+ assertion: value.assertion,
1521
+ assertionType: AGENT_IDENTITY_ASSERTION_TYPE,
1522
+ type: "identity_assertion"
1523
+ };
1524
+ return input;
1525
+ };
1526
+ var escapeHtml = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;");
452
1527
  var agentAuthPlugin = (config) => {
453
1528
  const plugin = new Elysia().derive(({ request }) => ({
454
1529
  protectAgent: async (requiredScopes, handleAuth, handleAuthFail) => {
@@ -479,9 +1554,83 @@ var agentAuthPlugin = (config) => {
479
1554
  }));
480
1555
  if (config === undefined)
481
1556
  return plugin.as("global");
482
- return plugin.get(config.metadataRoute ?? DEFAULT_AGENT_RESOURCE_METADATA_ROUTE, () => agentProtectedResourceMetadata(config)).as("global");
1557
+ if (config.agentRegistration === undefined) {
1558
+ return plugin.get(config.metadataRoute ?? DEFAULT_AGENT_RESOURCE_METADATA_ROUTE, () => agentProtectedResourceMetadata(config)).as("global");
1559
+ }
1560
+ const registration = config.agentRegistration;
1561
+ const identityRoute = registration.identityRoute ?? "/agent/identity";
1562
+ const claimRoute = registration.claimRoute ?? "/agent/identity/claim";
1563
+ const completeRoute = registration.completeRoute ?? "/agent/identity/claim/complete";
1564
+ const guideRoute = registration.guideRoute ?? "/auth.md";
1565
+ return plugin.get(config.metadataRoute ?? DEFAULT_AGENT_RESOURCE_METADATA_ROUTE, () => agentProtectedResourceMetadata(config)).get(guideRoute, () => new Response(generateAgentRegistrationGuide(config), {
1566
+ headers: {
1567
+ "cache-control": "public, max-age=300",
1568
+ "content-type": "text/markdown; charset=utf-8"
1569
+ }
1570
+ })).get(completeRoute, ({ query }) => {
1571
+ const token = typeof query.claim_attempt_token === "string" ? query.claim_attempt_token : "";
1572
+ if (token.length === 0)
1573
+ return new Response("Invalid claim link", { status: 400 });
1574
+ return new Response(`<!doctype html><html><head><meta charset="utf-8"><meta name="robots" content="noindex"><title>Confirm agent registration</title></head><body><main><h1>Confirm agent registration</h1><p>Sign in to this service, verify the agent and scopes shown by your agent, then enter the six-digit code.</p><form method="post"><input type="hidden" name="claim_attempt_token" value="${escapeHtml(token)}"><label>Code <input name="user_code" inputmode="numeric" autocomplete="one-time-code" pattern="[0-9]{6}" required></label><button type="submit">Confirm</button></form></main></body></html>`, {
1575
+ headers: {
1576
+ "cache-control": "no-store",
1577
+ "content-security-policy": "default-src 'none'; form-action 'self'; style-src 'none'; base-uri 'none'; frame-ancestors 'none'",
1578
+ "content-type": "text/html; charset=utf-8",
1579
+ "x-content-type-options": "nosniff"
1580
+ }
1581
+ });
1582
+ }).post(identityRoute, async ({ body }) => {
1583
+ const value = recordBody(body);
1584
+ if (value === undefined || typeof value.type !== "string") {
1585
+ return json({ error: "invalid_request" }, 400);
1586
+ }
1587
+ const input = parseRegistrationInput(value);
1588
+ if (input === undefined)
1589
+ return json({ error: "invalid_request" }, 400);
1590
+ const result = await startAgentRegistration(config, input);
1591
+ if ("error" in result) {
1592
+ return json({
1593
+ error: result.error,
1594
+ ...result.message === undefined ? {} : { error_description: result.message }
1595
+ }, result.status);
1596
+ }
1597
+ return registrationResponse(result);
1598
+ }).post(claimRoute, async ({ body }) => {
1599
+ const value = recordBody(body);
1600
+ if (value === undefined || typeof value.claim_token !== "string" || typeof value.email !== "string") {
1601
+ return json({ error: "invalid_request" }, 400);
1602
+ }
1603
+ const result = await beginAgentClaim(config, {
1604
+ claimToken: value.claim_token,
1605
+ email: value.email
1606
+ });
1607
+ if ("error" in result)
1608
+ return json({ error: result.error }, result.status);
1609
+ return json({ claim_attempt: result.claimAttempt });
1610
+ }).post(completeRoute, async ({ body, request }) => {
1611
+ let value = recordBody(body);
1612
+ if (value === undefined && typeof body === "string") {
1613
+ value = Object.fromEntries(new URLSearchParams(body));
1614
+ }
1615
+ if (value === undefined || typeof value.claim_attempt_token !== "string" || typeof value.user_code !== "string") {
1616
+ return json({ error: "invalid_request" }, 400);
1617
+ }
1618
+ const result = await completeAgentClaim(config, {
1619
+ attemptToken: value.claim_attempt_token,
1620
+ request,
1621
+ userCode: value.user_code
1622
+ });
1623
+ if ("error" in result)
1624
+ return json({ error: result.error }, result.status);
1625
+ return new Response(null, { status: 204 });
1626
+ }).as("global");
483
1627
  };
484
1628
  // src/agents/inMemoryStores.ts
1629
+ var cloneIdentityRegistration = (value) => ({
1630
+ ...value,
1631
+ claimAttempt: value.claimAttempt === undefined ? undefined : { ...value.claimAttempt },
1632
+ upstream: value.upstream === undefined ? undefined : { ...value.upstream }
1633
+ });
485
1634
  var cloneRegistration = (value) => ({
486
1635
  ...value,
487
1636
  allowedScopes: [...value.allowedScopes],
@@ -516,6 +1665,48 @@ var createInMemoryAgentDelegationStore = () => {
516
1665
  }
517
1666
  };
518
1667
  };
1668
+ var createInMemoryAgentIdentityRegistrationStore = () => {
1669
+ const registrations = new Map;
1670
+ return {
1671
+ create: async (registration) => {
1672
+ const conflicts = [...registrations.values()].some((existing) => existing.registrationId === registration.registrationId || existing.agentId === registration.agentId || existing.claimTokenHash === registration.claimTokenHash || existing.claimAttempt !== undefined && existing.claimAttempt.tokenHash === registration.claimAttempt?.tokenHash || existing.upstream !== undefined && registration.upstream !== undefined && existing.upstream.clientId === registration.upstream.clientId && existing.upstream.issuer === registration.upstream.issuer && existing.upstream.subject === registration.upstream.subject);
1673
+ if (conflicts)
1674
+ return false;
1675
+ registrations.set(registration.registrationId, cloneIdentityRegistration(registration));
1676
+ return true;
1677
+ },
1678
+ findByAgentId: async (agentId) => {
1679
+ const value = [...registrations.values()].find((registration) => registration.agentId === agentId);
1680
+ return value === undefined ? undefined : cloneIdentityRegistration(value);
1681
+ },
1682
+ findByAttemptTokenHash: async (attemptTokenHash) => {
1683
+ const value = [...registrations.values()].find((registration) => registration.claimAttempt?.tokenHash === attemptTokenHash);
1684
+ return value === undefined ? undefined : cloneIdentityRegistration(value);
1685
+ },
1686
+ findByClaimTokenHash: async (claimTokenHash) => {
1687
+ const value = [...registrations.values()].find((registration) => registration.claimTokenHash === claimTokenHash);
1688
+ return value === undefined ? undefined : cloneIdentityRegistration(value);
1689
+ },
1690
+ findByRegistrationId: async (registrationId) => {
1691
+ const value = registrations.get(registrationId);
1692
+ return value === undefined ? undefined : cloneIdentityRegistration(value);
1693
+ },
1694
+ findByUpstreamIdentity: async ({ clientId, issuer, subject }) => {
1695
+ const value = [...registrations.values()].find((registration) => registration.upstream?.clientId === clientId && registration.upstream?.issuer === issuer && registration.upstream.subject === subject);
1696
+ return value === undefined ? undefined : cloneIdentityRegistration(value);
1697
+ },
1698
+ replace: async (registration, expectedVersion) => {
1699
+ const current = registrations.get(registration.registrationId);
1700
+ if (current?.version !== expectedVersion)
1701
+ return false;
1702
+ registrations.set(registration.registrationId, cloneIdentityRegistration({
1703
+ ...registration,
1704
+ version: expectedVersion + 1
1705
+ }));
1706
+ return true;
1707
+ }
1708
+ };
1709
+ };
519
1710
  var createInMemoryAgentRegistrationStore = () => {
520
1711
  const registrations = new Map;
521
1712
  return {
@@ -1824,7 +3015,7 @@ function getColumnNameAndConfig2(a, b) {
1824
3015
  config: typeof a === "object" ? a : b
1825
3016
  };
1826
3017
  }
1827
- var textDecoder2 = typeof TextDecoder === "undefined" ? null : new TextDecoder;
3018
+ var textDecoder3 = typeof TextDecoder === "undefined" ? null : new TextDecoder;
1828
3019
  function assertUnreachable2(_x) {
1829
3020
  throw new Error("Didn't expect to get here");
1830
3021
  }
@@ -2875,7 +4066,7 @@ var PgJson = class extends PgColumn {
2875
4066
  return "json";
2876
4067
  }
2877
4068
  };
2878
- function json(name2) {
4069
+ function json2(name2) {
2879
4070
  return new PgJsonBuilder(name2 ?? "");
2880
4071
  }
2881
4072
 
@@ -3412,7 +4603,7 @@ function getPgColumnBuilders() {
3412
4603
  inet,
3413
4604
  integer,
3414
4605
  interval,
3415
- json,
4606
+ json: json2,
3416
4607
  jsonb,
3417
4608
  line,
3418
4609
  macaddr,
@@ -3629,6 +4820,9 @@ var Index = class {
3629
4820
  function index(name2) {
3630
4821
  return new IndexBuilderOn(false, name2);
3631
4822
  }
4823
+ function uniqueIndex(name2) {
4824
+ return new IndexBuilderOn(true, name2);
4825
+ }
3632
4826
  // node_modules/drizzle-orm/pg-core/checks.js
3633
4827
  var CheckBuilder = class {
3634
4828
  static [entityKind] = "PgCheckBuilder";
@@ -11377,6 +12571,38 @@ var agentDelegationsTable = pgTable("auth_agent_delegations", {
11377
12571
  updated_at_ms: bigint("updated_at_ms", { mode: "number" }).notNull(),
11378
12572
  user_id: varchar("user_id", { length: ID_LENGTH }).notNull()
11379
12573
  });
12574
+ var agentIdentityRegistrationsTable = pgTable("auth_agent_identity_registrations", {
12575
+ agent_id: varchar("agent_id", { length: ID_LENGTH }).notNull().unique(),
12576
+ claim_attempt: jsonb("claim_attempt").$type(),
12577
+ claim_attempt_token_hash: varchar("claim_attempt_token_hash", {
12578
+ length: ID_LENGTH
12579
+ }).unique(),
12580
+ claim_expires_at_ms: bigint("claim_expires_at_ms", {
12581
+ mode: "number"
12582
+ }).notNull(),
12583
+ claim_token_hash: varchar("claim_token_hash", {
12584
+ length: ID_LENGTH
12585
+ }).notNull().unique(),
12586
+ created_at_ms: bigint("created_at_ms", { mode: "number" }).notNull(),
12587
+ expires_at_ms: bigint("expires_at_ms", { mode: "number" }).notNull(),
12588
+ kind: varchar("kind", { length: 32 }).$type().notNull(),
12589
+ last_polled_at_ms: bigint("last_polled_at_ms", { mode: "number" }),
12590
+ login_hint: varchar("login_hint", { length: ID_LENGTH }),
12591
+ registration_id: varchar("registration_id", {
12592
+ length: ID_LENGTH
12593
+ }).primaryKey(),
12594
+ status: varchar("status", { length: STATUS_LENGTH }).$type().notNull(),
12595
+ updated_at_ms: bigint("updated_at_ms", { mode: "number" }).notNull(),
12596
+ upstream_client_id: varchar("upstream_client_id", {
12597
+ length: ID_LENGTH
12598
+ }),
12599
+ upstream_issuer: varchar("upstream_issuer", { length: ID_LENGTH }),
12600
+ upstream_subject: varchar("upstream_subject", { length: ID_LENGTH }),
12601
+ user_id: varchar("user_id", { length: ID_LENGTH }),
12602
+ version: integer("version").notNull()
12603
+ }, (table) => [
12604
+ uniqueIndex("auth_agent_identity_upstream_unique").on(table.upstream_issuer, table.upstream_subject, table.upstream_client_id)
12605
+ ]);
11380
12606
  var agentRegistrationsTable = pgTable("auth_agent_registrations", {
11381
12607
  agent_id: varchar("agent_id", { length: ID_LENGTH }).primaryKey(),
11382
12608
  allowed_scopes: jsonb("allowed_scopes").$type().notNull().default([]),
@@ -11409,7 +12635,49 @@ var toDelegation = (row) => ({
11409
12635
  updatedAt: row.updated_at_ms,
11410
12636
  userId: row.user_id
11411
12637
  });
12638
+ var toIdentityRegistration = (row) => ({
12639
+ agentId: row.agent_id,
12640
+ claimAttempt: row.claim_attempt ?? undefined,
12641
+ claimExpiresAt: row.claim_expires_at_ms,
12642
+ claimTokenHash: row.claim_token_hash,
12643
+ createdAt: row.created_at_ms,
12644
+ expiresAt: row.expires_at_ms,
12645
+ kind: row.kind,
12646
+ lastPolledAt: row.last_polled_at_ms ?? undefined,
12647
+ loginHint: row.login_hint ?? undefined,
12648
+ registrationId: row.registration_id,
12649
+ status: row.status,
12650
+ updatedAt: row.updated_at_ms,
12651
+ upstream: row.upstream_client_id === null || row.upstream_issuer === null || row.upstream_subject === null ? undefined : {
12652
+ clientId: row.upstream_client_id,
12653
+ issuer: row.upstream_issuer,
12654
+ subject: row.upstream_subject
12655
+ },
12656
+ userId: row.user_id ?? undefined,
12657
+ version: row.version
12658
+ });
12659
+ var identityRegistrationValues = (registration) => ({
12660
+ agent_id: registration.agentId,
12661
+ claim_attempt: registration.claimAttempt ?? null,
12662
+ claim_attempt_token_hash: registration.claimAttempt?.tokenHash ?? null,
12663
+ claim_expires_at_ms: registration.claimExpiresAt,
12664
+ claim_token_hash: registration.claimTokenHash,
12665
+ created_at_ms: registration.createdAt,
12666
+ expires_at_ms: registration.expiresAt,
12667
+ kind: registration.kind,
12668
+ last_polled_at_ms: registration.lastPolledAt ?? null,
12669
+ login_hint: registration.loginHint ?? null,
12670
+ registration_id: registration.registrationId,
12671
+ status: registration.status,
12672
+ updated_at_ms: registration.updatedAt,
12673
+ upstream_client_id: registration.upstream?.clientId ?? null,
12674
+ upstream_issuer: registration.upstream?.issuer ?? null,
12675
+ upstream_subject: registration.upstream?.subject ?? null,
12676
+ user_id: registration.userId ?? null,
12677
+ version: registration.version
12678
+ });
11412
12679
  var createNeonAgentDelegationStore = (databaseUrl) => createPostgresAgentDelegationStore(createNeonDatabase(databaseUrl));
12680
+ var createNeonAgentIdentityRegistrationStore = (databaseUrl) => createPostgresAgentIdentityRegistrationStore(createNeonDatabase(databaseUrl));
11413
12681
  var createNeonAgentRegistrationStore = (databaseUrl) => createPostgresAgentRegistrationStore(createNeonDatabase(databaseUrl));
11414
12682
  var createPostgresAgentDelegationStore = (db) => ({
11415
12683
  findActiveDelegation: async ({
@@ -11450,6 +12718,41 @@ var createPostgresAgentDelegationStore = (db) => ({
11450
12718
  });
11451
12719
  }
11452
12720
  });
12721
+ var createPostgresAgentIdentityRegistrationStore = (db) => ({
12722
+ create: async (registration) => {
12723
+ const rows = await db.insert(agentIdentityRegistrationsTable).values(identityRegistrationValues(registration)).onConflictDoNothing().returning({ id: agentIdentityRegistrationsTable.registration_id });
12724
+ return rows.length === 1;
12725
+ },
12726
+ findByAgentId: async (agentId) => {
12727
+ const [row] = await db.select().from(agentIdentityRegistrationsTable).where(eq(agentIdentityRegistrationsTable.agent_id, agentId)).limit(1);
12728
+ return row === undefined ? undefined : toIdentityRegistration(row);
12729
+ },
12730
+ findByAttemptTokenHash: async (attemptTokenHash) => {
12731
+ const [row] = await db.select().from(agentIdentityRegistrationsTable).where(eq(agentIdentityRegistrationsTable.claim_attempt_token_hash, attemptTokenHash)).limit(1);
12732
+ return row === undefined ? undefined : toIdentityRegistration(row);
12733
+ },
12734
+ findByClaimTokenHash: async (claimTokenHash) => {
12735
+ const [row] = await db.select().from(agentIdentityRegistrationsTable).where(eq(agentIdentityRegistrationsTable.claim_token_hash, claimTokenHash)).limit(1);
12736
+ return row === undefined ? undefined : toIdentityRegistration(row);
12737
+ },
12738
+ findByRegistrationId: async (registrationId) => {
12739
+ const [row] = await db.select().from(agentIdentityRegistrationsTable).where(eq(agentIdentityRegistrationsTable.registration_id, registrationId)).limit(1);
12740
+ return row === undefined ? undefined : toIdentityRegistration(row);
12741
+ },
12742
+ findByUpstreamIdentity: async ({ clientId, issuer, subject }) => {
12743
+ const [row] = await db.select().from(agentIdentityRegistrationsTable).where(and(eq(agentIdentityRegistrationsTable.upstream_client_id, clientId), eq(agentIdentityRegistrationsTable.upstream_issuer, issuer), eq(agentIdentityRegistrationsTable.upstream_subject, subject))).limit(1);
12744
+ return row === undefined ? undefined : toIdentityRegistration(row);
12745
+ },
12746
+ replace: async (registration, expectedVersion) => {
12747
+ const next = {
12748
+ ...registration,
12749
+ version: expectedVersion + 1
12750
+ };
12751
+ const values = identityRegistrationValues(next);
12752
+ const rows = await db.update(agentIdentityRegistrationsTable).set(values).where(and(eq(agentIdentityRegistrationsTable.registration_id, registration.registrationId), eq(agentIdentityRegistrationsTable.version, expectedVersion))).returning({ id: agentIdentityRegistrationsTable.registration_id });
12753
+ return rows.length === 1;
12754
+ }
12755
+ });
11453
12756
  var createPostgresAgentRegistrationStore = (db) => ({
11454
12757
  findByAgentId: async (agentId) => {
11455
12758
  const [row] = await db.select().from(agentRegistrationsTable).where(eq(agentRegistrationsTable.agent_id, agentId)).limit(1);
@@ -11482,24 +12785,46 @@ var createPostgresAgentRegistrationStore = (db) => ({
11482
12785
  });
11483
12786
  export {
11484
12787
  validateClientIdMetadataDocument,
12788
+ startAgentRegistration,
12789
+ revokeAgentIdentityRegistration,
11485
12790
  resolveAgentPrincipal,
12791
+ issueAgentServiceAssertion,
12792
+ issueAgentIdentityAssertion,
12793
+ handleAgentTokenGrant,
12794
+ generateAgentRegistrationGuide,
12795
+ discoverAgentRegistration,
11486
12796
  createPostgresAgentRegistrationStore,
12797
+ createPostgresAgentIdentityRegistrationStore,
11487
12798
  createPostgresAgentDelegationStore,
11488
12799
  createOidcAgentCredentialVerifier,
11489
12800
  createNeonAgentRegistrationStore,
12801
+ createNeonAgentIdentityRegistrationStore,
11490
12802
  createNeonAgentDelegationStore,
11491
12803
  createInMemoryAgentRegistrationStore,
12804
+ createInMemoryAgentIdentityRegistrationStore,
12805
+ createInMemoryAgentIdentityAssertionJtiStore,
11492
12806
  createInMemoryAgentDelegationStore,
11493
12807
  createClientIdMetadataResolver,
12808
+ createAgentRegistrationCredentialVerifier,
12809
+ createAgentRegistrationClient,
12810
+ createAgentIdentityAssertionVerifier,
12811
+ completeAgentClaim,
11494
12812
  clientIdMetadataToOAuthClient,
12813
+ beginAgentClaim,
11495
12814
  agentRegistrationsTable,
12815
+ agentRegistrationEndpoints,
12816
+ agentRegistrationDiscoveryMetadata,
11496
12817
  agentProtectedResourceMetadata,
12818
+ agentIdentityRegistrationsTable,
11497
12819
  agentHasScopes,
11498
12820
  agentDelegationsTable,
11499
12821
  agentAuthPlugin,
11500
12822
  agentAuthChallenge,
11501
- DEFAULT_AGENT_RESOURCE_METADATA_ROUTE
12823
+ DEFAULT_AGENT_RESOURCE_METADATA_ROUTE,
12824
+ AGENT_IDENTITY_ASSERTION_TYPE,
12825
+ AGENT_IDENTITY_ASSERTION_GRANT_TYPE,
12826
+ AGENT_CLAIM_GRANT_TYPE
11502
12827
  };
11503
12828
 
11504
- //# debugId=BF8C5C40CCB4AFB564756E2164756E21
12829
+ //# debugId=4AABAB4CBDEE339264756E2164756E21
11505
12830
  //# sourceMappingURL=index.js.map