@absolutejs/auth 0.54.9 → 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.
- package/README.md +19 -5
- package/dist/agents/config.d.ts +16 -0
- package/dist/agents/idJag.d.ts +49 -0
- package/dist/agents/inMemoryStores.d.ts +2 -1
- package/dist/agents/index.d.ts +5 -2
- package/dist/agents/index.js +1391 -66
- package/dist/agents/index.js.map +12 -8
- package/dist/agents/postgresStores.d.ts +280 -1
- package/dist/agents/registration.d.ts +162 -0
- package/dist/agents/registrationClient.d.ts +50 -0
- package/dist/agents/routes.d.ts +112 -1
- package/dist/agents/types.d.ts +59 -0
- package/dist/apikeys/routes.d.ts +2 -2
- package/dist/cli/migrate.js +57 -8
- package/dist/cli/migrate.js.map +4 -4
- package/dist/credentials/login.d.ts +4 -4
- package/dist/credentials/routes.d.ts +4 -4
- package/dist/index.d.ts +185 -2
- package/dist/index.js +1388 -102
- package/dist/index.js.map +15 -12
- package/dist/manifest.js +12 -6
- package/dist/manifest.js.map +4 -4
- package/dist/manifest.json +2 -2
- package/dist/mfa/routes.d.ts +2 -2
- package/dist/mfa/sms.d.ts +2 -2
- package/dist/oidc/clientAuth.d.ts +6 -2
- package/dist/oidc/config.d.ts +19 -5
- package/dist/oidc/keys.d.ts +7 -3
- package/dist/oidc/logout.d.ts +2 -2
- package/dist/oidc/routes.d.ts +13 -11
- package/dist/organizations/routes.d.ts +1 -1
- package/dist/portal/routes.d.ts +3 -3
- package/dist/roles/routes.d.ts +1 -1
- package/dist/routes/refresh.d.ts +1 -1
- package/dist/routes/revoke.d.ts +1 -1
- package/dist/routes/sessions.d.ts +3 -3
- package/dist/sso/discoveryRoute.d.ts +1 -1
- package/dist/sso/oidcRoutes.d.ts +2 -2
- package/dist/sso/samlRoutes.d.ts +4 -4
- package/docs/AGENT-AUTH.md +54 -0
- package/docs/MIGRATE-FROM-LUCIA.md +235 -0
- package/docs/OAUTH-PROVIDER-QUIRKS.md +150 -0
- package/docs/UI-COMPONENTS.md +226 -0
- package/package.json +6 -3
package/dist/index.js
CHANGED
|
@@ -3112,6 +3112,673 @@ var resolveAgentPrincipal = async (request, config) => {
|
|
|
3112
3112
|
};
|
|
3113
3113
|
};
|
|
3114
3114
|
|
|
3115
|
+
// src/agents/registration.ts
|
|
3116
|
+
init_constants();
|
|
3117
|
+
init_crypto();
|
|
3118
|
+
|
|
3119
|
+
// src/oidc/keys.ts
|
|
3120
|
+
var ENCODER = new TextEncoder;
|
|
3121
|
+
var ES256 = { hash: "SHA-256", name: "ECDSA" };
|
|
3122
|
+
var KEY_PARAMS = { name: "ECDSA", namedCurve: "P-256" };
|
|
3123
|
+
var toBase64Url = (bytes) => Buffer.from(bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes)).toString("base64url");
|
|
3124
|
+
var fromBase64Url = (value) => new Uint8Array(Buffer.from(value, "base64url"));
|
|
3125
|
+
var encodeSegment = (value) => Buffer.from(JSON.stringify(value)).toString("base64url");
|
|
3126
|
+
var decodeSegment = (segment) => {
|
|
3127
|
+
try {
|
|
3128
|
+
const value = JSON.parse(Buffer.from(segment, "base64url").toString("utf8"));
|
|
3129
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
3130
|
+
return;
|
|
3131
|
+
}
|
|
3132
|
+
return Object.fromEntries(Object.entries(value));
|
|
3133
|
+
} catch {
|
|
3134
|
+
return;
|
|
3135
|
+
}
|
|
3136
|
+
};
|
|
3137
|
+
var generateSigningKey = async () => {
|
|
3138
|
+
const pair = await crypto.subtle.generateKey(KEY_PARAMS, true, [
|
|
3139
|
+
"sign",
|
|
3140
|
+
"verify"
|
|
3141
|
+
]);
|
|
3142
|
+
const privateJwk = await crypto.subtle.exportKey("jwk", pair.privateKey);
|
|
3143
|
+
const publicJwk = await crypto.subtle.exportKey("jwk", pair.publicKey);
|
|
3144
|
+
return { kid: await jwkThumbprint(publicJwk), privateJwk, publicJwk };
|
|
3145
|
+
};
|
|
3146
|
+
var jwkThumbprint = async (jwk) => {
|
|
3147
|
+
const canonical = JSON.stringify({
|
|
3148
|
+
crv: jwk.crv,
|
|
3149
|
+
kty: jwk.kty,
|
|
3150
|
+
x: jwk.x,
|
|
3151
|
+
y: jwk.y
|
|
3152
|
+
});
|
|
3153
|
+
return toBase64Url(await crypto.subtle.digest("SHA-256", ENCODER.encode(canonical)));
|
|
3154
|
+
};
|
|
3155
|
+
var signJwt = async (payload, signing, typ = "JWT") => {
|
|
3156
|
+
const key = await crypto.subtle.importKey("jwk", signing.privateJwk, KEY_PARAMS, false, ["sign"]);
|
|
3157
|
+
const input = `${encodeSegment({ alg: "ES256", kid: signing.kid, typ })}.${encodeSegment(payload)}`;
|
|
3158
|
+
const signature = await crypto.subtle.sign(ES256, key, ENCODER.encode(input));
|
|
3159
|
+
return `${input}.${toBase64Url(signature)}`;
|
|
3160
|
+
};
|
|
3161
|
+
var toPublicJwk = (key) => ({
|
|
3162
|
+
alg: "ES256",
|
|
3163
|
+
crv: key.publicJwk.crv,
|
|
3164
|
+
kid: key.kid,
|
|
3165
|
+
kty: key.publicJwk.kty,
|
|
3166
|
+
use: "sig",
|
|
3167
|
+
x: key.publicJwk.x,
|
|
3168
|
+
y: key.publicJwk.y
|
|
3169
|
+
});
|
|
3170
|
+
var verifyJwt = async (token, publicJwk) => {
|
|
3171
|
+
const [headerSegment, payloadSegment, signatureSegment] = token.split(".");
|
|
3172
|
+
if (headerSegment === undefined || payloadSegment === undefined || signatureSegment === undefined) {
|
|
3173
|
+
return;
|
|
3174
|
+
}
|
|
3175
|
+
const key = await crypto.subtle.importKey("jwk", publicJwk, KEY_PARAMS, false, ["verify"]);
|
|
3176
|
+
const valid = await crypto.subtle.verify(ES256, key, fromBase64Url(signatureSegment), ENCODER.encode(`${headerSegment}.${payloadSegment}`));
|
|
3177
|
+
if (!valid)
|
|
3178
|
+
return;
|
|
3179
|
+
const header = decodeSegment(headerSegment);
|
|
3180
|
+
const payload = decodeSegment(payloadSegment);
|
|
3181
|
+
if (header === undefined || payload === undefined)
|
|
3182
|
+
return;
|
|
3183
|
+
return {
|
|
3184
|
+
header,
|
|
3185
|
+
payload
|
|
3186
|
+
};
|
|
3187
|
+
};
|
|
3188
|
+
|
|
3189
|
+
// src/agents/registration.ts
|
|
3190
|
+
var AGENT_CLAIM_GRANT_TYPE = "urn:workos:agent-auth:grant-type:claim";
|
|
3191
|
+
var AGENT_IDENTITY_ASSERTION_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:jwt-bearer";
|
|
3192
|
+
var AGENT_IDENTITY_ASSERTION_TYPE = "urn:ietf:params:oauth:token-type:id-jag";
|
|
3193
|
+
var DEFAULT_IDENTITY_ROUTE = "/agent/identity";
|
|
3194
|
+
var DEFAULT_CLAIM_ROUTE = "/agent/identity/claim";
|
|
3195
|
+
var DEFAULT_COMPLETE_ROUTE = "/agent/identity/claim/complete";
|
|
3196
|
+
var DEFAULT_GUIDE_ROUTE = "/auth.md";
|
|
3197
|
+
var DEFAULT_CLAIM_TTL_MS = 24 * 60 * MILLISECONDS_IN_A_MINUTE;
|
|
3198
|
+
var DEFAULT_ATTEMPT_TTL_MS = 10 * MILLISECONDS_IN_A_MINUTE;
|
|
3199
|
+
var DEFAULT_ASSERTION_TTL_MS = 60 * MILLISECONDS_IN_A_MINUTE;
|
|
3200
|
+
var DEFAULT_ACCESS_TOKEN_TTL_MS2 = 15 * MILLISECONDS_IN_A_MINUTE;
|
|
3201
|
+
var DEFAULT_MAX_AUTH_AGE_MS = 60 * MILLISECONDS_IN_A_MINUTE;
|
|
3202
|
+
var DEFAULT_POLL_INTERVAL_SECONDS = 5;
|
|
3203
|
+
var DEFAULT_MAX_CODE_ATTEMPTS = 5;
|
|
3204
|
+
var MAX_CONCURRENT_UPDATE_RETRIES = 5;
|
|
3205
|
+
var TOKEN_BYTES2 = 32;
|
|
3206
|
+
var agentRegistrationDiscoveryMetadata = (config) => {
|
|
3207
|
+
const registration = requiredRegistration(config);
|
|
3208
|
+
const endpoints = agentRegistrationEndpoints(config);
|
|
3209
|
+
const identityTypes = ["identity_assertion"];
|
|
3210
|
+
if (registration.allowAnonymous === true)
|
|
3211
|
+
identityTypes.unshift("anonymous");
|
|
3212
|
+
if (registration.allowServiceAuth === true)
|
|
3213
|
+
identityTypes.push("service_auth");
|
|
3214
|
+
return {
|
|
3215
|
+
claim_endpoint: endpoints.claimEndpoint,
|
|
3216
|
+
identity_assertion: {
|
|
3217
|
+
assertion_types_supported: [AGENT_IDENTITY_ASSERTION_TYPE]
|
|
3218
|
+
},
|
|
3219
|
+
identity_endpoint: endpoints.identityEndpoint,
|
|
3220
|
+
identity_types_supported: identityTypes,
|
|
3221
|
+
skill: endpoints.guide
|
|
3222
|
+
};
|
|
3223
|
+
};
|
|
3224
|
+
var agentRegistrationEndpoints = (config) => {
|
|
3225
|
+
const registration = requiredRegistration(config);
|
|
3226
|
+
const base = config.authorizationServer;
|
|
3227
|
+
const oidcRoute = config.oidcRoute ?? "/oauth2";
|
|
3228
|
+
return {
|
|
3229
|
+
claimEndpoint: new URL(registration.claimRoute ?? DEFAULT_CLAIM_ROUTE, base).toString(),
|
|
3230
|
+
completeEndpoint: new URL(registration.completeRoute ?? DEFAULT_COMPLETE_ROUTE, base).toString(),
|
|
3231
|
+
guide: new URL(registration.guideRoute ?? DEFAULT_GUIDE_ROUTE, base).toString(),
|
|
3232
|
+
identityEndpoint: new URL(registration.identityRoute ?? DEFAULT_IDENTITY_ROUTE, base).toString(),
|
|
3233
|
+
tokenEndpoint: new URL(`${oidcRoute}/token`, base).toString()
|
|
3234
|
+
};
|
|
3235
|
+
};
|
|
3236
|
+
var markdownJson = (value) => `\`\`\`json
|
|
3237
|
+
${JSON.stringify(value, null, 2)}
|
|
3238
|
+
\`\`\``;
|
|
3239
|
+
var generateAgentRegistrationGuide = (config) => {
|
|
3240
|
+
const registration = requiredRegistration(config);
|
|
3241
|
+
const endpoints = agentRegistrationEndpoints(config);
|
|
3242
|
+
const metadataUrl = new URL(config.metadataRoute ?? "/.well-known/oauth-protected-resource", config.resource).toString();
|
|
3243
|
+
const methods = [
|
|
3244
|
+
"- `identity_assertion`: present an audience-bound ID-JAG from a trusted provider.",
|
|
3245
|
+
...registration.allowServiceAuth === true ? [
|
|
3246
|
+
"- `service_auth`: provide a user login hint and complete the service-owned claim ceremony."
|
|
3247
|
+
] : [],
|
|
3248
|
+
...registration.allowAnonymous === true ? [
|
|
3249
|
+
"- `anonymous`: receive pre-claim scopes, then optionally let a signed-in user claim the registration."
|
|
3250
|
+
] : []
|
|
3251
|
+
].join(`
|
|
3252
|
+
`);
|
|
3253
|
+
return `# Agent registration for ${config.resourceName ?? config.resource}
|
|
3254
|
+
|
|
3255
|
+
This service supports the open auth.md agent-registration profile. Structured
|
|
3256
|
+
OAuth metadata is authoritative; this document is its agent-readable companion.
|
|
3257
|
+
|
|
3258
|
+
## 1. Discover
|
|
3259
|
+
|
|
3260
|
+
Fetch ${metadataUrl}, follow \`authorization_servers\`, then fetch
|
|
3261
|
+
\`/.well-known/oauth-authorization-server\`. Read its \`agent_auth\` object and
|
|
3262
|
+
top-level \`token_endpoint\` and \`grant_types_supported\` fields.
|
|
3263
|
+
|
|
3264
|
+
## 2. Choose a method
|
|
3265
|
+
|
|
3266
|
+
${methods}
|
|
3267
|
+
|
|
3268
|
+
Before asserting a user identity, show the service name and requested scopes to
|
|
3269
|
+
the user and obtain consent. Never ask the user to send a password or OTP to the
|
|
3270
|
+
agent.
|
|
3271
|
+
|
|
3272
|
+
## 3. Register
|
|
3273
|
+
|
|
3274
|
+
POST one of these bodies to ${endpoints.identityEndpoint}:
|
|
3275
|
+
|
|
3276
|
+
${markdownJson({
|
|
3277
|
+
assertion: "<ID-JAG>",
|
|
3278
|
+
assertion_type: AGENT_IDENTITY_ASSERTION_TYPE,
|
|
3279
|
+
type: "identity_assertion"
|
|
3280
|
+
})}
|
|
3281
|
+
|
|
3282
|
+
${registration.allowServiceAuth === true ? markdownJson({ login_hint: "user@example.com", type: "service_auth" }) : ""}
|
|
3283
|
+
|
|
3284
|
+
${registration.allowAnonymous === true ? markdownJson({ type: "anonymous" }) : ""}
|
|
3285
|
+
|
|
3286
|
+
## 4. Claim
|
|
3287
|
+
|
|
3288
|
+
Surface \`verification_uri\` and \`user_code\` together. The user opens the
|
|
3289
|
+
service-owned page, signs in using the service's normal MFA/SSO policy, and types
|
|
3290
|
+
the code there. Poll ${endpoints.tokenEndpoint} using
|
|
3291
|
+
\`grant_type=${AGENT_CLAIM_GRANT_TYPE}\` and the one-time \`claim_token\`.
|
|
3292
|
+
Treat \`authorization_pending\` as retryable, honor \`interval\`, and restart
|
|
3293
|
+
when the server returns \`expired_token\`.
|
|
3294
|
+
|
|
3295
|
+
## 5. Exchange and use credentials
|
|
3296
|
+
|
|
3297
|
+
Exchange \`identity_assertion\` at ${endpoints.tokenEndpoint} with
|
|
3298
|
+
\`grant_type=${AGENT_IDENTITY_ASSERTION_GRANT_TYPE}\`. Present the resulting
|
|
3299
|
+
access token as \`Authorization: Bearer <access_token>\`. Credentials are scoped,
|
|
3300
|
+
short-lived, revocable, and bound to the registered agent identity.
|
|
3301
|
+
|
|
3302
|
+
## Errors and safety
|
|
3303
|
+
|
|
3304
|
+
- Stop on \`invalid_issuer\`, \`invalid_signature\`, \`invalid_audience\`, or \`replay_detected\`.
|
|
3305
|
+
- Reauthenticate the user on \`login_required\`.
|
|
3306
|
+
- Open the service-owned confirmation URL on \`interaction_required\`.
|
|
3307
|
+
- Never persist claim tokens after completion and never expose credentials in model context.
|
|
3308
|
+
`;
|
|
3309
|
+
};
|
|
3310
|
+
var requiredRegistration = (config) => {
|
|
3311
|
+
if (config.agentRegistration === undefined) {
|
|
3312
|
+
throw new Error("agentAuth.agentRegistration is not configured");
|
|
3313
|
+
}
|
|
3314
|
+
return config.agentRegistration;
|
|
3315
|
+
};
|
|
3316
|
+
var randomCode = () => {
|
|
3317
|
+
const bytes = crypto.getRandomValues(new Uint8Array(4));
|
|
3318
|
+
const value = new DataView(bytes.buffer).getUint32(0) % 1e6;
|
|
3319
|
+
return value.toString().padStart(6, "0");
|
|
3320
|
+
};
|
|
3321
|
+
var makeSecret = (prefix) => `${prefix}_${generateSecureToken(TOKEN_BYTES2)}`;
|
|
3322
|
+
var makeAttempt = async ({
|
|
3323
|
+
email,
|
|
3324
|
+
now,
|
|
3325
|
+
registration
|
|
3326
|
+
}) => {
|
|
3327
|
+
const attemptToken = makeSecret("cat");
|
|
3328
|
+
const userCode = randomCode();
|
|
3329
|
+
const expiresAt = now + (registration.attemptTtlMs ?? DEFAULT_ATTEMPT_TTL_MS);
|
|
3330
|
+
return {
|
|
3331
|
+
attempt: {
|
|
3332
|
+
attempts: 0,
|
|
3333
|
+
email: email.toLowerCase(),
|
|
3334
|
+
expiresAt,
|
|
3335
|
+
tokenHash: await hashToken(attemptToken),
|
|
3336
|
+
userCodeHash: await hashToken(userCode)
|
|
3337
|
+
},
|
|
3338
|
+
attemptToken,
|
|
3339
|
+
expiresAt,
|
|
3340
|
+
userCode
|
|
3341
|
+
};
|
|
3342
|
+
};
|
|
3343
|
+
var createFlow = async ({
|
|
3344
|
+
agentId,
|
|
3345
|
+
kind,
|
|
3346
|
+
loginHint,
|
|
3347
|
+
now,
|
|
3348
|
+
registration,
|
|
3349
|
+
status,
|
|
3350
|
+
upstream,
|
|
3351
|
+
userId,
|
|
3352
|
+
withAttempt
|
|
3353
|
+
}) => {
|
|
3354
|
+
const claimToken = makeSecret("clm");
|
|
3355
|
+
const registrationId = `air_${crypto.randomUUID()}`;
|
|
3356
|
+
const claimExpiresAt = now + (registration.claimTtlMs ?? DEFAULT_CLAIM_TTL_MS);
|
|
3357
|
+
const attempt = withAttempt === undefined ? undefined : await makeAttempt({ email: withAttempt, now, registration });
|
|
3358
|
+
const flow = {
|
|
3359
|
+
agentId,
|
|
3360
|
+
claimAttempt: attempt?.attempt,
|
|
3361
|
+
claimExpiresAt,
|
|
3362
|
+
claimTokenHash: await hashToken(claimToken),
|
|
3363
|
+
createdAt: now,
|
|
3364
|
+
expiresAt: claimExpiresAt,
|
|
3365
|
+
kind,
|
|
3366
|
+
loginHint,
|
|
3367
|
+
registrationId,
|
|
3368
|
+
status,
|
|
3369
|
+
updatedAt: now,
|
|
3370
|
+
upstream,
|
|
3371
|
+
userId,
|
|
3372
|
+
version: 1
|
|
3373
|
+
};
|
|
3374
|
+
if (!await registration.identityStore.create(flow)) {
|
|
3375
|
+
throw new Error("Agent identity registration id collision");
|
|
3376
|
+
}
|
|
3377
|
+
return { attempt, claimToken, flow };
|
|
3378
|
+
};
|
|
3379
|
+
var assertionClaims = (config, flow, now) => ({
|
|
3380
|
+
aud: config.authorizationServer,
|
|
3381
|
+
exp: Math.floor((now + (config.agentRegistration?.assertionTtlMs ?? DEFAULT_ASSERTION_TTL_MS)) / MILLISECONDS_IN_A_SECOND),
|
|
3382
|
+
iat: Math.floor(now / MILLISECONDS_IN_A_SECOND),
|
|
3383
|
+
iss: config.authorizationServer,
|
|
3384
|
+
jti: crypto.randomUUID(),
|
|
3385
|
+
registration_version: flow.version,
|
|
3386
|
+
sub: flow.registrationId
|
|
3387
|
+
});
|
|
3388
|
+
var issueAgentServiceAssertion = async (config, flow, now = Date.now()) => ({
|
|
3389
|
+
assertion: await signJwt(assertionClaims(config, flow, now), requiredRegistration(config).signingKey, "oauth-id-jag+jwt"),
|
|
3390
|
+
expiresAt: now + (requiredRegistration(config).assertionTtlMs ?? DEFAULT_ASSERTION_TTL_MS)
|
|
3391
|
+
});
|
|
3392
|
+
var activateAgent = async (config, flow, scopes) => {
|
|
3393
|
+
const now = Date.now();
|
|
3394
|
+
await config.registrationStore.saveRegistration({
|
|
3395
|
+
agentId: flow.agentId,
|
|
3396
|
+
allowedScopes: scopes.filter((scope) => config.scopes.includes(scope)),
|
|
3397
|
+
createdAt: now,
|
|
3398
|
+
metadata: {
|
|
3399
|
+
identityRegistrationId: flow.registrationId,
|
|
3400
|
+
registrationKind: flow.kind
|
|
3401
|
+
},
|
|
3402
|
+
name: `Agent registration ${flow.registrationId}`,
|
|
3403
|
+
status: "active",
|
|
3404
|
+
updatedAt: now
|
|
3405
|
+
});
|
|
3406
|
+
if (flow.userId !== undefined) {
|
|
3407
|
+
await config.delegationStore.saveDelegation({
|
|
3408
|
+
agentId: flow.agentId,
|
|
3409
|
+
createdAt: now,
|
|
3410
|
+
delegationId: `agd_${crypto.randomUUID()}`,
|
|
3411
|
+
scopes: scopes.filter((scope) => config.scopes.includes(scope)),
|
|
3412
|
+
status: "active",
|
|
3413
|
+
updatedAt: now,
|
|
3414
|
+
userId: flow.userId
|
|
3415
|
+
});
|
|
3416
|
+
}
|
|
3417
|
+
};
|
|
3418
|
+
var ceremony = (config, flow, attempt) => ({
|
|
3419
|
+
expires_in: Math.max(0, Math.floor((attempt.expiresAt - Date.now()) / MILLISECONDS_IN_A_SECOND)),
|
|
3420
|
+
interval: requiredRegistration(config).pollIntervalSeconds ?? DEFAULT_POLL_INTERVAL_SECONDS,
|
|
3421
|
+
user_code: attempt.userCode,
|
|
3422
|
+
verification_uri: `${agentRegistrationEndpoints(config).completeEndpoint}?claim_attempt_token=${encodeURIComponent(attempt.attemptToken)}`
|
|
3423
|
+
});
|
|
3424
|
+
var startAgentRegistration = async (config, input, now = Date.now()) => {
|
|
3425
|
+
const registration = requiredRegistration(config);
|
|
3426
|
+
const agentId = `agent_${crypto.randomUUID()}`;
|
|
3427
|
+
if (input.type === "anonymous") {
|
|
3428
|
+
if (registration.allowAnonymous !== true) {
|
|
3429
|
+
return { error: "anonymous_not_enabled", status: 403 };
|
|
3430
|
+
}
|
|
3431
|
+
if (registration.revokeAccessTokens === undefined) {
|
|
3432
|
+
throw new Error("Anonymous agent registration requires revokeAccessTokens");
|
|
3433
|
+
}
|
|
3434
|
+
const created2 = await createFlow({
|
|
3435
|
+
agentId,
|
|
3436
|
+
kind: "anonymous",
|
|
3437
|
+
now,
|
|
3438
|
+
registration,
|
|
3439
|
+
status: "pending"
|
|
3440
|
+
});
|
|
3441
|
+
await activateAgent(config, created2.flow, registration.preClaimScopes ?? []);
|
|
3442
|
+
const assertion2 = await issueAgentServiceAssertion(config, created2.flow, now);
|
|
3443
|
+
return {
|
|
3444
|
+
assertionExpires: assertion2.expiresAt,
|
|
3445
|
+
claimToken: created2.claimToken,
|
|
3446
|
+
claimTokenExpires: created2.flow.claimExpiresAt,
|
|
3447
|
+
identityAssertion: assertion2.assertion,
|
|
3448
|
+
postClaimScopes: registration.postClaimScopes,
|
|
3449
|
+
preClaimScopes: registration.preClaimScopes ?? [],
|
|
3450
|
+
registrationId: created2.flow.registrationId,
|
|
3451
|
+
registrationType: "anonymous",
|
|
3452
|
+
status: 200
|
|
3453
|
+
};
|
|
3454
|
+
}
|
|
3455
|
+
if (input.type === "service_auth") {
|
|
3456
|
+
if (registration.allowServiceAuth !== true) {
|
|
3457
|
+
return { error: "service_auth_not_enabled", status: 403 };
|
|
3458
|
+
}
|
|
3459
|
+
const email = input.loginHint.trim().toLowerCase();
|
|
3460
|
+
if (!/^[^@\s]+@[^@\s]+\.[^@\s]+$/u.test(email)) {
|
|
3461
|
+
return { error: "invalid_login_hint", status: 400 };
|
|
3462
|
+
}
|
|
3463
|
+
const created2 = await createFlow({
|
|
3464
|
+
agentId,
|
|
3465
|
+
kind: "service_auth",
|
|
3466
|
+
loginHint: email,
|
|
3467
|
+
now,
|
|
3468
|
+
registration,
|
|
3469
|
+
status: "pending",
|
|
3470
|
+
withAttempt: email
|
|
3471
|
+
});
|
|
3472
|
+
if (created2.attempt === undefined)
|
|
3473
|
+
throw new Error("Claim attempt missing");
|
|
3474
|
+
return {
|
|
3475
|
+
assertionExpires: 0,
|
|
3476
|
+
claim: ceremony(config, created2.flow, created2.attempt),
|
|
3477
|
+
claimToken: created2.claimToken,
|
|
3478
|
+
claimTokenExpires: created2.flow.claimExpiresAt,
|
|
3479
|
+
postClaimScopes: registration.postClaimScopes,
|
|
3480
|
+
registrationId: created2.flow.registrationId,
|
|
3481
|
+
registrationType: "service_auth",
|
|
3482
|
+
status: 200
|
|
3483
|
+
};
|
|
3484
|
+
}
|
|
3485
|
+
if (input.assertionType !== AGENT_IDENTITY_ASSERTION_TYPE || registration.verifyIdentityAssertion === undefined) {
|
|
3486
|
+
return { error: "invalid_request", status: 400 };
|
|
3487
|
+
}
|
|
3488
|
+
const identity = await registration.verifyIdentityAssertion(input.assertion);
|
|
3489
|
+
if (identity === undefined) {
|
|
3490
|
+
return { error: "invalid_identity_assertion", status: 401 };
|
|
3491
|
+
}
|
|
3492
|
+
const authAge = now - identity.authenticatedAt;
|
|
3493
|
+
if (authAge < -MILLISECONDS_IN_A_MINUTE || authAge > (registration.maxAuthenticationAgeMs ?? DEFAULT_MAX_AUTH_AGE_MS)) {
|
|
3494
|
+
return { error: "login_required", status: 401 };
|
|
3495
|
+
}
|
|
3496
|
+
if (identity.emailVerified !== true && identity.phoneNumberVerified !== true) {
|
|
3497
|
+
return { error: "missing_verified_identity", status: 403 };
|
|
3498
|
+
}
|
|
3499
|
+
const existing = await registration.identityStore.findByUpstreamIdentity({
|
|
3500
|
+
clientId: identity.clientId,
|
|
3501
|
+
issuer: identity.issuer,
|
|
3502
|
+
subject: identity.subject
|
|
3503
|
+
});
|
|
3504
|
+
if (existing?.status === "claimed") {
|
|
3505
|
+
const assertion2 = await issueAgentServiceAssertion(config, existing, now);
|
|
3506
|
+
return {
|
|
3507
|
+
assertionExpires: assertion2.expiresAt,
|
|
3508
|
+
identityAssertion: assertion2.assertion,
|
|
3509
|
+
postClaimScopes: registration.postClaimScopes,
|
|
3510
|
+
registrationId: existing.registrationId,
|
|
3511
|
+
registrationType: "identity_assertion",
|
|
3512
|
+
status: 200
|
|
3513
|
+
};
|
|
3514
|
+
}
|
|
3515
|
+
const match = await registration.resolveVerifiedIdentity?.(identity);
|
|
3516
|
+
const verifiedEmail = identity.emailVerified === true ? identity.email : undefined;
|
|
3517
|
+
if (match?.userId === undefined && verifiedEmail === undefined) {
|
|
3518
|
+
return { error: "interaction_required", status: 401 };
|
|
3519
|
+
}
|
|
3520
|
+
const created = await createFlow({
|
|
3521
|
+
agentId,
|
|
3522
|
+
kind: "identity_assertion",
|
|
3523
|
+
now,
|
|
3524
|
+
registration,
|
|
3525
|
+
status: match?.userId === undefined ? "pending" : "claimed",
|
|
3526
|
+
upstream: {
|
|
3527
|
+
clientId: identity.clientId,
|
|
3528
|
+
issuer: identity.issuer,
|
|
3529
|
+
subject: identity.subject
|
|
3530
|
+
},
|
|
3531
|
+
userId: match?.userId,
|
|
3532
|
+
withAttempt: match?.userId === undefined ? verifiedEmail : undefined
|
|
3533
|
+
});
|
|
3534
|
+
if (match?.userId === undefined) {
|
|
3535
|
+
if (created.attempt === undefined)
|
|
3536
|
+
throw new Error("Claim attempt missing");
|
|
3537
|
+
return {
|
|
3538
|
+
assertionExpires: 0,
|
|
3539
|
+
claim: ceremony(config, created.flow, created.attempt),
|
|
3540
|
+
claimToken: created.claimToken,
|
|
3541
|
+
claimTokenExpires: created.flow.claimExpiresAt,
|
|
3542
|
+
postClaimScopes: registration.postClaimScopes,
|
|
3543
|
+
registrationId: created.flow.registrationId,
|
|
3544
|
+
registrationType: "identity_assertion",
|
|
3545
|
+
status: 200
|
|
3546
|
+
};
|
|
3547
|
+
}
|
|
3548
|
+
await activateAgent(config, created.flow, registration.postClaimScopes);
|
|
3549
|
+
const assertion = await issueAgentServiceAssertion(config, created.flow, now);
|
|
3550
|
+
return {
|
|
3551
|
+
assertionExpires: assertion.expiresAt,
|
|
3552
|
+
identityAssertion: assertion.assertion,
|
|
3553
|
+
postClaimScopes: registration.postClaimScopes,
|
|
3554
|
+
registrationId: created.flow.registrationId,
|
|
3555
|
+
registrationType: "identity_assertion",
|
|
3556
|
+
status: 200
|
|
3557
|
+
};
|
|
3558
|
+
};
|
|
3559
|
+
var beginAgentClaim = async (config, input, now = Date.now()) => {
|
|
3560
|
+
const registration = requiredRegistration(config);
|
|
3561
|
+
const email = input.email.trim().toLowerCase();
|
|
3562
|
+
const claimTokenHash = await hashToken(input.claimToken);
|
|
3563
|
+
for (let retry = 0;retry < MAX_CONCURRENT_UPDATE_RETRIES; retry += 1) {
|
|
3564
|
+
const flow = await registration.identityStore.findByClaimTokenHash(claimTokenHash);
|
|
3565
|
+
if (flow === undefined) {
|
|
3566
|
+
return { error: "invalid_claim_token", status: 400 };
|
|
3567
|
+
}
|
|
3568
|
+
if (flow.claimExpiresAt <= now || flow.status === "revoked") {
|
|
3569
|
+
return { error: "claim_expired", status: 400 };
|
|
3570
|
+
}
|
|
3571
|
+
if (flow.loginHint !== undefined && flow.loginHint !== email) {
|
|
3572
|
+
return { error: "invalid_claim_token", status: 400 };
|
|
3573
|
+
}
|
|
3574
|
+
const attempt = await makeAttempt({ email, now, registration });
|
|
3575
|
+
const replacement = {
|
|
3576
|
+
...flow,
|
|
3577
|
+
claimAttempt: attempt.attempt,
|
|
3578
|
+
loginHint: email,
|
|
3579
|
+
updatedAt: now
|
|
3580
|
+
};
|
|
3581
|
+
if (await registration.identityStore.replace(replacement, flow.version)) {
|
|
3582
|
+
return {
|
|
3583
|
+
claimAttempt: ceremony(config, replacement, attempt),
|
|
3584
|
+
status: 200
|
|
3585
|
+
};
|
|
3586
|
+
}
|
|
3587
|
+
}
|
|
3588
|
+
return { error: "concurrent_update", status: 409 };
|
|
3589
|
+
};
|
|
3590
|
+
var completeAgentClaim = async (config, input, now = Date.now()) => {
|
|
3591
|
+
const registration = requiredRegistration(config);
|
|
3592
|
+
const user = await registration.resolveAuthenticatedUser(input.request);
|
|
3593
|
+
if (user === undefined)
|
|
3594
|
+
return { error: "wrong_user", status: 403 };
|
|
3595
|
+
const attemptTokenHash = await hashToken(input.attemptToken);
|
|
3596
|
+
const userCodeHash = await hashToken(input.userCode);
|
|
3597
|
+
for (let retry = 0;retry < MAX_CONCURRENT_UPDATE_RETRIES; retry += 1) {
|
|
3598
|
+
const flow = await registration.identityStore.findByAttemptTokenHash(attemptTokenHash);
|
|
3599
|
+
const attempt = flow?.claimAttempt;
|
|
3600
|
+
if (flow === undefined || attempt === undefined) {
|
|
3601
|
+
return { error: "invalid_claim_attempt", status: 400 };
|
|
3602
|
+
}
|
|
3603
|
+
if (flow.claimExpiresAt <= now || attempt.expiresAt <= now) {
|
|
3604
|
+
return { error: "claim_expired", status: 400 };
|
|
3605
|
+
}
|
|
3606
|
+
if (user.email?.toLowerCase() !== attempt.email) {
|
|
3607
|
+
return { error: "wrong_user", status: 403 };
|
|
3608
|
+
}
|
|
3609
|
+
const matches = await constantTimeEqual(userCodeHash, attempt.userCodeHash);
|
|
3610
|
+
if (!matches) {
|
|
3611
|
+
const attempts = attempt.attempts + 1;
|
|
3612
|
+
const locked = attempts >= (registration.maxCodeAttempts ?? DEFAULT_MAX_CODE_ATTEMPTS);
|
|
3613
|
+
const replaced = await registration.identityStore.replace({
|
|
3614
|
+
...flow,
|
|
3615
|
+
claimAttempt: locked ? undefined : { ...attempt, attempts },
|
|
3616
|
+
updatedAt: now
|
|
3617
|
+
}, flow.version);
|
|
3618
|
+
if (replaced) {
|
|
3619
|
+
return {
|
|
3620
|
+
error: "user_code_invalid",
|
|
3621
|
+
status: locked ? 429 : 400
|
|
3622
|
+
};
|
|
3623
|
+
}
|
|
3624
|
+
continue;
|
|
3625
|
+
}
|
|
3626
|
+
if (flow.kind === "anonymous") {
|
|
3627
|
+
await registration.revokeAccessTokens?.(flow.agentId);
|
|
3628
|
+
}
|
|
3629
|
+
const replacement = {
|
|
3630
|
+
...flow,
|
|
3631
|
+
claimAttempt: undefined,
|
|
3632
|
+
status: "claimed",
|
|
3633
|
+
updatedAt: now,
|
|
3634
|
+
userId: user.userId
|
|
3635
|
+
};
|
|
3636
|
+
if (!await registration.identityStore.replace(replacement, flow.version)) {
|
|
3637
|
+
continue;
|
|
3638
|
+
}
|
|
3639
|
+
const persisted = await registration.identityStore.findByRegistrationId(flow.registrationId);
|
|
3640
|
+
if (persisted === undefined) {
|
|
3641
|
+
throw new Error("Completed agent registration disappeared");
|
|
3642
|
+
}
|
|
3643
|
+
await activateAgent(config, persisted, registration.postClaimScopes);
|
|
3644
|
+
return { status: 204 };
|
|
3645
|
+
}
|
|
3646
|
+
return { error: "concurrent_update", status: 409 };
|
|
3647
|
+
};
|
|
3648
|
+
var issueAgentAccessToken = async (config, flow, now) => {
|
|
3649
|
+
const registration = requiredRegistration(config);
|
|
3650
|
+
const accessToken = `at_${generateSecureToken(TOKEN_BYTES2)}`;
|
|
3651
|
+
const scopes = (flow.status === "claimed" ? registration.postClaimScopes : registration.preClaimScopes ?? []).filter((scope) => config.scopes.includes(scope));
|
|
3652
|
+
const expiresAt = now + (registration.tokenTtlMs ?? DEFAULT_ACCESS_TOKEN_TTL_MS2);
|
|
3653
|
+
await registration.accessTokenStore.saveToken({
|
|
3654
|
+
clientId: flow.agentId,
|
|
3655
|
+
createdAt: now,
|
|
3656
|
+
expiresAt,
|
|
3657
|
+
hashedToken: await hashToken(accessToken),
|
|
3658
|
+
ownerId: flow.userId,
|
|
3659
|
+
scopes,
|
|
3660
|
+
tokenId: crypto.randomUUID()
|
|
3661
|
+
});
|
|
3662
|
+
return {
|
|
3663
|
+
accessToken,
|
|
3664
|
+
expiresIn: Math.floor((expiresAt - now) / MILLISECONDS_IN_A_SECOND),
|
|
3665
|
+
scopes
|
|
3666
|
+
};
|
|
3667
|
+
};
|
|
3668
|
+
var createAgentRegistrationCredentialVerifier = (accessTokenStore, identityStore) => async (request) => {
|
|
3669
|
+
const authorization = request.headers.get("authorization");
|
|
3670
|
+
if (authorization?.startsWith("Bearer at_") !== true)
|
|
3671
|
+
return;
|
|
3672
|
+
const token = authorization.slice("Bearer ".length).trim();
|
|
3673
|
+
const record = await accessTokenStore.findByHashedToken(await hashToken(token));
|
|
3674
|
+
if (record === undefined || record.expiresAt <= Date.now())
|
|
3675
|
+
return;
|
|
3676
|
+
if (identityStore !== undefined) {
|
|
3677
|
+
const identity = await identityStore.findByAgentId(record.clientId);
|
|
3678
|
+
if (identity === undefined || identity.status === "revoked") {
|
|
3679
|
+
return;
|
|
3680
|
+
}
|
|
3681
|
+
}
|
|
3682
|
+
return {
|
|
3683
|
+
agentId: record.clientId,
|
|
3684
|
+
expiresAt: record.expiresAt,
|
|
3685
|
+
scopes: record.scopes,
|
|
3686
|
+
userId: record.ownerId
|
|
3687
|
+
};
|
|
3688
|
+
};
|
|
3689
|
+
var handleAgentTokenGrant = async (config, body, now = Date.now()) => {
|
|
3690
|
+
const registration = requiredRegistration(config);
|
|
3691
|
+
if (body.grant_type === AGENT_CLAIM_GRANT_TYPE) {
|
|
3692
|
+
if (body.claim_token === undefined) {
|
|
3693
|
+
return { body: { error: "invalid_request" }, status: 400 };
|
|
3694
|
+
}
|
|
3695
|
+
const claimTokenHash = await hashToken(body.claim_token);
|
|
3696
|
+
let flow2;
|
|
3697
|
+
for (let retry = 0;retry < MAX_CONCURRENT_UPDATE_RETRIES; retry += 1) {
|
|
3698
|
+
flow2 = await registration.identityStore.findByClaimTokenHash(claimTokenHash);
|
|
3699
|
+
if (flow2 === undefined || flow2.claimExpiresAt <= now) {
|
|
3700
|
+
return { body: { error: "expired_token" }, status: 400 };
|
|
3701
|
+
}
|
|
3702
|
+
if (flow2.status === "claimed")
|
|
3703
|
+
break;
|
|
3704
|
+
if (flow2.claimAttempt !== undefined && flow2.claimAttempt.expiresAt <= now) {
|
|
3705
|
+
return { body: { error: "expired_token" }, status: 400 };
|
|
3706
|
+
}
|
|
3707
|
+
const intervalMs = (registration.pollIntervalSeconds ?? DEFAULT_POLL_INTERVAL_SECONDS) * MILLISECONDS_IN_A_SECOND;
|
|
3708
|
+
if (flow2.lastPolledAt !== undefined && now - flow2.lastPolledAt < intervalMs) {
|
|
3709
|
+
return { body: { error: "slow_down" }, status: 400 };
|
|
3710
|
+
}
|
|
3711
|
+
if (await registration.identityStore.replace({ ...flow2, lastPolledAt: now, updatedAt: now }, flow2.version)) {
|
|
3712
|
+
return {
|
|
3713
|
+
body: { error: "authorization_pending" },
|
|
3714
|
+
status: 400
|
|
3715
|
+
};
|
|
3716
|
+
}
|
|
3717
|
+
}
|
|
3718
|
+
if (flow2?.status !== "claimed") {
|
|
3719
|
+
return { body: { error: "temporarily_unavailable" }, status: 400 };
|
|
3720
|
+
}
|
|
3721
|
+
const assertion = await issueAgentServiceAssertion(config, flow2, now);
|
|
3722
|
+
const token2 = await issueAgentAccessToken(config, flow2, now);
|
|
3723
|
+
return {
|
|
3724
|
+
body: {
|
|
3725
|
+
access_token: token2.accessToken,
|
|
3726
|
+
assertion_expires: new Date(assertion.expiresAt).toISOString(),
|
|
3727
|
+
expires_in: token2.expiresIn,
|
|
3728
|
+
identity_assertion: assertion.assertion,
|
|
3729
|
+
scope: token2.scopes.join(" "),
|
|
3730
|
+
token_type: "Bearer"
|
|
3731
|
+
},
|
|
3732
|
+
status: 200
|
|
3733
|
+
};
|
|
3734
|
+
}
|
|
3735
|
+
if (body.grant_type !== AGENT_IDENTITY_ASSERTION_GRANT_TYPE) {
|
|
3736
|
+
return;
|
|
3737
|
+
}
|
|
3738
|
+
if (body.assertion === undefined) {
|
|
3739
|
+
return { body: { error: "invalid_request" }, status: 400 };
|
|
3740
|
+
}
|
|
3741
|
+
const verified = await verifyJwt(body.assertion, registration.signingKey.publicJwk);
|
|
3742
|
+
const payload = verified?.payload;
|
|
3743
|
+
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)) {
|
|
3744
|
+
return { body: { error: "invalid_grant" }, status: 400 };
|
|
3745
|
+
}
|
|
3746
|
+
const flow = await registration.identityStore.findByRegistrationId(payload.sub);
|
|
3747
|
+
if (flow === undefined || flow.status === "revoked" || payload.registration_version !== flow.version) {
|
|
3748
|
+
return { body: { error: "invalid_grant" }, status: 400 };
|
|
3749
|
+
}
|
|
3750
|
+
const token = await issueAgentAccessToken(config, flow, now);
|
|
3751
|
+
return {
|
|
3752
|
+
body: {
|
|
3753
|
+
access_token: token.accessToken,
|
|
3754
|
+
expires_in: token.expiresIn,
|
|
3755
|
+
scope: token.scopes.join(" "),
|
|
3756
|
+
token_type: "Bearer"
|
|
3757
|
+
},
|
|
3758
|
+
status: 200
|
|
3759
|
+
};
|
|
3760
|
+
};
|
|
3761
|
+
var revokeAgentIdentityRegistration = async (config, registrationId, now = Date.now()) => {
|
|
3762
|
+
const registration = requiredRegistration(config);
|
|
3763
|
+
for (let retry = 0;retry < MAX_CONCURRENT_UPDATE_RETRIES; retry += 1) {
|
|
3764
|
+
const flow = await registration.identityStore.findByRegistrationId(registrationId);
|
|
3765
|
+
if (flow === undefined)
|
|
3766
|
+
return false;
|
|
3767
|
+
if (flow.status === "revoked")
|
|
3768
|
+
return true;
|
|
3769
|
+
await registration.revokeAccessTokens?.(flow.agentId);
|
|
3770
|
+
if (await registration.identityStore.replace({
|
|
3771
|
+
...flow,
|
|
3772
|
+
claimAttempt: undefined,
|
|
3773
|
+
status: "revoked",
|
|
3774
|
+
updatedAt: now
|
|
3775
|
+
}, flow.version)) {
|
|
3776
|
+
return true;
|
|
3777
|
+
}
|
|
3778
|
+
}
|
|
3779
|
+
throw new Error("Could not revoke agent identity after concurrent updates");
|
|
3780
|
+
};
|
|
3781
|
+
|
|
3115
3782
|
// src/agents/routes.ts
|
|
3116
3783
|
var quoteHeaderValue = (value) => {
|
|
3117
3784
|
const printable = [...value].filter((character) => {
|
|
@@ -3151,6 +3818,67 @@ var failureResponse = (config, failure, requiredScopes) => new Response(JSON.str
|
|
|
3151
3818
|
},
|
|
3152
3819
|
status: failure.code === "Forbidden" ? 403 : 401
|
|
3153
3820
|
});
|
|
3821
|
+
var json = (value, status = 200) => new Response(JSON.stringify(value), {
|
|
3822
|
+
headers: {
|
|
3823
|
+
"cache-control": "no-store",
|
|
3824
|
+
"content-type": "application/json"
|
|
3825
|
+
},
|
|
3826
|
+
status
|
|
3827
|
+
});
|
|
3828
|
+
var recordBody = (body) => {
|
|
3829
|
+
if (typeof body !== "object" || body === null || Array.isArray(body)) {
|
|
3830
|
+
return;
|
|
3831
|
+
}
|
|
3832
|
+
return Object.fromEntries(Object.entries(body));
|
|
3833
|
+
};
|
|
3834
|
+
var registrationResponse = (result) => {
|
|
3835
|
+
const body = {
|
|
3836
|
+
...result.assertionExpires > 0 ? {
|
|
3837
|
+
assertion_expires: new Date(result.assertionExpires).toISOString()
|
|
3838
|
+
} : {},
|
|
3839
|
+
...result.claim === undefined ? {} : { claim: result.claim },
|
|
3840
|
+
...result.claimToken === undefined ? {} : { claim_token: result.claimToken },
|
|
3841
|
+
...result.claimTokenExpires === undefined ? {} : {
|
|
3842
|
+
claim_token_expires: new Date(result.claimTokenExpires).toISOString()
|
|
3843
|
+
},
|
|
3844
|
+
...result.identityAssertion === undefined ? {} : { identity_assertion: result.identityAssertion },
|
|
3845
|
+
...result.preClaimScopes === undefined ? {} : { pre_claim_scopes: result.preClaimScopes },
|
|
3846
|
+
post_claim_scopes: result.postClaimScopes,
|
|
3847
|
+
registration_id: result.registrationId,
|
|
3848
|
+
registration_type: result.registrationType
|
|
3849
|
+
};
|
|
3850
|
+
if (result.registrationType === "identity_assertion" && result.identityAssertion === undefined) {
|
|
3851
|
+
return json({
|
|
3852
|
+
...body,
|
|
3853
|
+
error: "interaction_required",
|
|
3854
|
+
error_description: "Authenticate at the service and confirm the account link."
|
|
3855
|
+
}, 401);
|
|
3856
|
+
}
|
|
3857
|
+
return json(body);
|
|
3858
|
+
};
|
|
3859
|
+
var parseRegistrationInput = (value) => {
|
|
3860
|
+
if (value.type === "anonymous") {
|
|
3861
|
+
const input2 = { type: "anonymous" };
|
|
3862
|
+
return input2;
|
|
3863
|
+
}
|
|
3864
|
+
if (value.type === "service_auth" && typeof value.login_hint === "string") {
|
|
3865
|
+
const input2 = {
|
|
3866
|
+
loginHint: value.login_hint,
|
|
3867
|
+
type: "service_auth"
|
|
3868
|
+
};
|
|
3869
|
+
return input2;
|
|
3870
|
+
}
|
|
3871
|
+
if (value.type !== "identity_assertion" || value.assertion_type !== AGENT_IDENTITY_ASSERTION_TYPE || typeof value.assertion !== "string") {
|
|
3872
|
+
return;
|
|
3873
|
+
}
|
|
3874
|
+
const input = {
|
|
3875
|
+
assertion: value.assertion,
|
|
3876
|
+
assertionType: AGENT_IDENTITY_ASSERTION_TYPE,
|
|
3877
|
+
type: "identity_assertion"
|
|
3878
|
+
};
|
|
3879
|
+
return input;
|
|
3880
|
+
};
|
|
3881
|
+
var escapeHtml = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """);
|
|
3154
3882
|
var agentAuthPlugin = (config) => {
|
|
3155
3883
|
const plugin = new Elysia2().derive(({ request }) => ({
|
|
3156
3884
|
protectAgent: async (requiredScopes, handleAuth, handleAuthFail) => {
|
|
@@ -3181,7 +3909,76 @@ var agentAuthPlugin = (config) => {
|
|
|
3181
3909
|
}));
|
|
3182
3910
|
if (config === undefined)
|
|
3183
3911
|
return plugin.as("global");
|
|
3184
|
-
|
|
3912
|
+
if (config.agentRegistration === undefined) {
|
|
3913
|
+
return plugin.get(config.metadataRoute ?? DEFAULT_AGENT_RESOURCE_METADATA_ROUTE, () => agentProtectedResourceMetadata(config)).as("global");
|
|
3914
|
+
}
|
|
3915
|
+
const registration = config.agentRegistration;
|
|
3916
|
+
const identityRoute = registration.identityRoute ?? "/agent/identity";
|
|
3917
|
+
const claimRoute = registration.claimRoute ?? "/agent/identity/claim";
|
|
3918
|
+
const completeRoute = registration.completeRoute ?? "/agent/identity/claim/complete";
|
|
3919
|
+
const guideRoute = registration.guideRoute ?? "/auth.md";
|
|
3920
|
+
return plugin.get(config.metadataRoute ?? DEFAULT_AGENT_RESOURCE_METADATA_ROUTE, () => agentProtectedResourceMetadata(config)).get(guideRoute, () => new Response(generateAgentRegistrationGuide(config), {
|
|
3921
|
+
headers: {
|
|
3922
|
+
"cache-control": "public, max-age=300",
|
|
3923
|
+
"content-type": "text/markdown; charset=utf-8"
|
|
3924
|
+
}
|
|
3925
|
+
})).get(completeRoute, ({ query }) => {
|
|
3926
|
+
const token = typeof query.claim_attempt_token === "string" ? query.claim_attempt_token : "";
|
|
3927
|
+
if (token.length === 0)
|
|
3928
|
+
return new Response("Invalid claim link", { status: 400 });
|
|
3929
|
+
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>`, {
|
|
3930
|
+
headers: {
|
|
3931
|
+
"cache-control": "no-store",
|
|
3932
|
+
"content-security-policy": "default-src 'none'; form-action 'self'; style-src 'none'; base-uri 'none'; frame-ancestors 'none'",
|
|
3933
|
+
"content-type": "text/html; charset=utf-8",
|
|
3934
|
+
"x-content-type-options": "nosniff"
|
|
3935
|
+
}
|
|
3936
|
+
});
|
|
3937
|
+
}).post(identityRoute, async ({ body }) => {
|
|
3938
|
+
const value = recordBody(body);
|
|
3939
|
+
if (value === undefined || typeof value.type !== "string") {
|
|
3940
|
+
return json({ error: "invalid_request" }, 400);
|
|
3941
|
+
}
|
|
3942
|
+
const input = parseRegistrationInput(value);
|
|
3943
|
+
if (input === undefined)
|
|
3944
|
+
return json({ error: "invalid_request" }, 400);
|
|
3945
|
+
const result = await startAgentRegistration(config, input);
|
|
3946
|
+
if ("error" in result) {
|
|
3947
|
+
return json({
|
|
3948
|
+
error: result.error,
|
|
3949
|
+
...result.message === undefined ? {} : { error_description: result.message }
|
|
3950
|
+
}, result.status);
|
|
3951
|
+
}
|
|
3952
|
+
return registrationResponse(result);
|
|
3953
|
+
}).post(claimRoute, async ({ body }) => {
|
|
3954
|
+
const value = recordBody(body);
|
|
3955
|
+
if (value === undefined || typeof value.claim_token !== "string" || typeof value.email !== "string") {
|
|
3956
|
+
return json({ error: "invalid_request" }, 400);
|
|
3957
|
+
}
|
|
3958
|
+
const result = await beginAgentClaim(config, {
|
|
3959
|
+
claimToken: value.claim_token,
|
|
3960
|
+
email: value.email
|
|
3961
|
+
});
|
|
3962
|
+
if ("error" in result)
|
|
3963
|
+
return json({ error: result.error }, result.status);
|
|
3964
|
+
return json({ claim_attempt: result.claimAttempt });
|
|
3965
|
+
}).post(completeRoute, async ({ body, request }) => {
|
|
3966
|
+
let value = recordBody(body);
|
|
3967
|
+
if (value === undefined && typeof body === "string") {
|
|
3968
|
+
value = Object.fromEntries(new URLSearchParams(body));
|
|
3969
|
+
}
|
|
3970
|
+
if (value === undefined || typeof value.claim_attempt_token !== "string" || typeof value.user_code !== "string") {
|
|
3971
|
+
return json({ error: "invalid_request" }, 400);
|
|
3972
|
+
}
|
|
3973
|
+
const result = await completeAgentClaim(config, {
|
|
3974
|
+
attemptToken: value.claim_attempt_token,
|
|
3975
|
+
request,
|
|
3976
|
+
userCode: value.user_code
|
|
3977
|
+
});
|
|
3978
|
+
if ("error" in result)
|
|
3979
|
+
return json({ error: result.error }, result.status);
|
|
3980
|
+
return new Response(null, { status: 204 });
|
|
3981
|
+
}).as("global");
|
|
3185
3982
|
};
|
|
3186
3983
|
|
|
3187
3984
|
// src/audit/config.ts
|
|
@@ -4381,7 +5178,7 @@ var protectRoutePlugin = ({
|
|
|
4381
5178
|
})).as("global");
|
|
4382
5179
|
|
|
4383
5180
|
// src/htmx/renderers.ts
|
|
4384
|
-
var
|
|
5181
|
+
var escapeHtml2 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """);
|
|
4385
5182
|
var defaultAuthorizationHref = (provider, client) => client ? `/oauth2/${provider}/authorization?client=${client}` : `/oauth2/${provider}/authorization`;
|
|
4386
5183
|
var resolveAuthHtmxRenderers = (config) => {
|
|
4387
5184
|
const { connectorTargets, featuredLoginProviders, providerData } = config;
|
|
@@ -4394,10 +5191,10 @@ var resolveAuthHtmxRenderers = (config) => {
|
|
|
4394
5191
|
return `<div class="grid-2">
|
|
4395
5192
|
<div class="card"><h2 class="card__title">Canonical account</h2><p class="muted">Absolute Auth keeps one canonical user and links every OAuth identity to it. Conflicting identities raise a merge request.</p></div>
|
|
4396
5193
|
<div class="card text-left"><h2 class="card__title">Profile fields</h2>
|
|
4397
|
-
<div class="spread"><span class="muted">Subject</span><span>${
|
|
4398
|
-
<div class="spread"><span class="muted">Name</span><span>${
|
|
4399
|
-
<div class="spread"><span class="muted">Email</span><span>${
|
|
4400
|
-
<div class="spread"><span class="muted">Primary identity</span><span>${
|
|
5194
|
+
<div class="spread"><span class="muted">Subject</span><span>${escapeHtml2(user.sub)}</span></div>
|
|
5195
|
+
<div class="spread"><span class="muted">Name</span><span>${escapeHtml2(fullName || "\u2014")}</span></div>
|
|
5196
|
+
<div class="spread"><span class="muted">Email</span><span>${escapeHtml2(user.email ?? "\u2014")}</span></div>
|
|
5197
|
+
<div class="spread"><span class="muted">Primary identity</span><span>${escapeHtml2(user.primary_auth_identity_id ?? "\u2014")}</span></div>
|
|
4401
5198
|
</div>
|
|
4402
5199
|
</div>`;
|
|
4403
5200
|
};
|
|
@@ -4406,18 +5203,18 @@ var resolveAuthHtmxRenderers = (config) => {
|
|
|
4406
5203
|
return `<a class="btn btn--primary btn--sm" href="/htmx">Sign in</a>`;
|
|
4407
5204
|
}
|
|
4408
5205
|
const label = user.email ?? user.first_name ?? "Account";
|
|
4409
|
-
return `<span class="muted">${
|
|
5206
|
+
return `<span class="muted">${escapeHtml2(label)}</span><a class="btn btn--ghost btn--sm" href="/htmx/signout">Sign out</a>`;
|
|
4410
5207
|
};
|
|
4411
|
-
const connectorLinks = () => connectorTargets.map((target) => `<div class="card text-left"><h2 class="card__title row"><img class="entity__logo" alt="" src="${providerLogo(target.provider)}" />${
|
|
5208
|
+
const connectorLinks = () => connectorTargets.map((target) => `<div class="card text-left"><h2 class="card__title row"><img class="entity__logo" alt="" src="${providerLogo(target.provider)}" />${escapeHtml2(target.label)}</h2><p class="muted">${escapeHtml2(target.description)}</p><a class="btn btn--primary" href="${authorizationHref(target.provider, "connector")}">Link ${escapeHtml2(target.label)}</a></div>`).join("");
|
|
4412
5209
|
const connectors = (payload) => {
|
|
4413
|
-
const scopeList = (scopes) => scopes.map((scope) => `<span class="scope">${
|
|
4414
|
-
const bindings = payload.bindings.length === 0 ? `<div class="empty-state">No external accounts linked.</div>` : `<div class="entity-list">${payload.bindings.map((binding) => `<div class="entity"><div class="entity__meta"><span class="entity__title">${
|
|
4415
|
-
const grants = payload.grants.length === 0 ? `<div class="empty-state">No connector grants yet.</div>` : `<div class="entity-list">${payload.grants.map((grant) => `<div class="entity"><div class="entity__meta"><span class="entity__title">${
|
|
5210
|
+
const scopeList = (scopes) => scopes.map((scope) => `<span class="scope">${escapeHtml2(scope)}</span>`).join("");
|
|
5211
|
+
const bindings = payload.bindings.length === 0 ? `<div class="empty-state">No external accounts linked.</div>` : `<div class="entity-list">${payload.bindings.map((binding) => `<div class="entity"><div class="entity__meta"><span class="entity__title">${escapeHtml2(binding.label ?? binding.externalAccountId)}<span class="pill">${escapeHtml2(binding.connectorProvider)}</span></span><span class="entity__sub">${escapeHtml2(binding.externalAccountType)} \xB7 ${escapeHtml2(binding.status)}</span><div class="scope-list">${scopeList(binding.availableScopes)}</div></div><div class="entity__actions"><form hx-delete="/htmx/connectors/bindings/${binding.id}" hx-target="#connector-list" hx-swap="innerHTML"><button class="btn btn--danger btn--sm" type="submit">Remove</button></form></div></div>`).join("")}</div>`;
|
|
5212
|
+
const grants = payload.grants.length === 0 ? `<div class="empty-state">No connector grants yet.</div>` : `<div class="entity-list">${payload.grants.map((grant) => `<div class="entity"><div class="entity__meta"><span class="entity__title">${escapeHtml2(grant.authProviderKey)}<span class="pill pill--indigo">${escapeHtml2(grant.status)}</span></span><span class="entity__sub">Subject ${escapeHtml2(grant.providerSubject)}</span><div class="scope-list">${scopeList(grant.grantedScopes)}</div></div><div class="entity__actions"><form hx-delete="/htmx/connectors/grants/${grant.id}" hx-target="#connector-list" hx-swap="innerHTML"><button class="btn btn--danger btn--sm" type="submit">Remove</button></form></div></div>`).join("")}</div>`;
|
|
4416
5213
|
return `<h3 class="provider-heading">External accounts</h3>${bindings}<h3 class="provider-heading">Grants</h3>${grants}`;
|
|
4417
5214
|
};
|
|
4418
5215
|
const identities = (payload, query) => {
|
|
4419
5216
|
const pending = payload.mergeRequests.filter((req) => req.status === "pending");
|
|
4420
|
-
const mergesHtml = pending.length === 0 ? "" : `<div class="stack"><h3 class="provider-heading">Merge requests</h3><div class="entity-list">${pending.map((req) => `<div class="entity card--danger"><div class="entity__meta"><span class="entity__title">${
|
|
5217
|
+
const mergesHtml = pending.length === 0 ? "" : `<div class="stack"><h3 class="provider-heading">Merge requests</h3><div class="entity-list">${pending.map((req) => `<div class="entity card--danger"><div class="entity__meta"><span class="entity__title">${escapeHtml2(providerLabel(req.conflicting_auth_provider))} conflict</span><span class="entity__sub">Subject ${escapeHtml2(req.conflicting_provider_subject)}</span></div><div class="entity__actions">
|
|
4421
5218
|
<form hx-post="/htmx/merge/${req.id}" hx-target="#identities-list" hx-swap="innerHTML" hx-include="#identity-query"><button class="btn btn--primary btn--sm" type="submit">Merge</button></form>
|
|
4422
5219
|
<form hx-delete="/htmx/merge/${req.id}" hx-target="#identities-list" hx-swap="innerHTML" hx-include="#identity-query"><button class="btn btn--ghost btn--sm" type="submit">Dismiss</button></form>
|
|
4423
5220
|
</div></div>`).join("")}</div></div>`;
|
|
@@ -4426,19 +5223,19 @@ var resolveAuthHtmxRenderers = (config) => {
|
|
|
4426
5223
|
identities: list.filter((identity) => term === "" || providerLabel(provider).toLowerCase().includes(term) || identity.id.toLowerCase().includes(term) || identity.provider_subject.toLowerCase().includes(term)),
|
|
4427
5224
|
provider
|
|
4428
5225
|
})).filter((group) => group.identities.length > 0);
|
|
4429
|
-
const groupsHtml = groups.length === 0 ? `<div class="empty-state">No identities match your search.</div>` : groups.map((group) => `<div class="provider-group"><h3 class="provider-heading">${providerLogo(group.provider) ? `<img class="entity__logo" alt="" src="${providerLogo(group.provider)}" />` : ""}${
|
|
5226
|
+
const groupsHtml = groups.length === 0 ? `<div class="empty-state">No identities match your search.</div>` : groups.map((group) => `<div class="provider-group"><h3 class="provider-heading">${providerLogo(group.provider) ? `<img class="entity__logo" alt="" src="${providerLogo(group.provider)}" />` : ""}${escapeHtml2(providerLabel(group.provider))}</h3><div class="entity-list">${group.identities.map((identity) => `<div class="entity"><div class="entity__main"><div class="entity__meta"><span class="entity__title">${escapeHtml2(identity.provider_subject)}${identity.isPrimary ? `<span class="pill pill--primary">Primary</span>` : ""}</span><span class="entity__sub">${escapeHtml2(identity.id)}</span></div></div><div class="entity__actions">
|
|
4430
5227
|
${identity.isPrimary ? "" : `<form hx-post="/htmx/identities/${identity.id}/primary" hx-target="#identities-list" hx-swap="innerHTML" hx-include="#identity-query"><button class="btn btn--neutral btn--sm" type="submit">Set primary</button></form>`}
|
|
4431
5228
|
<form hx-delete="/htmx/identities/${identity.id}" hx-target="#identities-list" hx-swap="innerHTML" hx-include="#identity-query"><button class="btn btn--danger btn--sm" type="submit">Remove</button></form>
|
|
4432
5229
|
</div></div>`).join("")}</div></div>`).join("");
|
|
4433
5230
|
return `${mergesHtml}${groupsHtml}`;
|
|
4434
5231
|
};
|
|
4435
|
-
const protectedView = (user) => `<section class="auth-section stack"><div><h1 class="page-heading">Protected page</h1><p class="muted">Your authenticated session resolves to this user record.</p></div><pre class="json">${
|
|
5232
|
+
const protectedView = (user) => `<section class="auth-section stack"><div><h1 class="page-heading">Protected page</h1><p class="muted">Your authenticated session resolves to this user record.</p></div><pre class="json">${escapeHtml2(JSON.stringify(user, null, 2))}</pre></section>`;
|
|
4436
5233
|
const providerLogin = (verb, includeDropdown) => {
|
|
4437
|
-
const featured = featuredLoginProviders.map((provider) => `<a class="oauth-button" href="${authorizationHref(provider)}"><img class="oauth-button__icon" alt="" src="${providerLogo(provider)}" /><span class="oauth-button__text">${
|
|
5234
|
+
const featured = featuredLoginProviders.map((provider) => `<a class="oauth-button" href="${authorizationHref(provider)}"><img class="oauth-button__icon" alt="" src="${providerLogo(provider)}" /><span class="oauth-button__text">${escapeHtml2(verb)} ${escapeHtml2(providerLabel(provider))}</span></a>`).join("");
|
|
4438
5235
|
if (!includeDropdown) {
|
|
4439
5236
|
return `<div class="oauth-grid">${featured}</div>`;
|
|
4440
5237
|
}
|
|
4441
|
-
const options = providerOptions.map((provider) => `<option value="${provider}">${
|
|
5238
|
+
const options = providerOptions.map((provider) => `<option value="${provider}">${escapeHtml2(providerLabel(provider))}</option>`).join("");
|
|
4442
5239
|
return `<div class="oauth-grid">${featured}
|
|
4443
5240
|
<div class="separator"><span class="separator__line"></span><span class="separator__text">or any provider</span><span class="separator__line"></span></div>
|
|
4444
5241
|
<form class="oauth-grid" action="/htmx/login-redirect" method="get">
|
|
@@ -4454,7 +5251,7 @@ var resolveAuthHtmxRenderers = (config) => {
|
|
|
4454
5251
|
authMenu: overrides.authMenu ?? authMenu,
|
|
4455
5252
|
connectorLinks: overrides.connectorLinks ?? connectorLinks,
|
|
4456
5253
|
connectors: overrides.connectors ?? connectors,
|
|
4457
|
-
escapeHtml,
|
|
5254
|
+
escapeHtml: escapeHtml2,
|
|
4458
5255
|
identities: overrides.identities ?? identities,
|
|
4459
5256
|
protected: overrides.protected ?? protectedView,
|
|
4460
5257
|
providerLogin: overrides.providerLogin ?? providerLogin
|
|
@@ -5071,75 +5868,17 @@ import { Elysia as Elysia18, t as t14 } from "elysia";
|
|
|
5071
5868
|
// src/oidc/config.ts
|
|
5072
5869
|
init_constants();
|
|
5073
5870
|
init_crypto();
|
|
5074
|
-
|
|
5075
|
-
// src/oidc/keys.ts
|
|
5076
|
-
var ENCODER = new TextEncoder;
|
|
5077
|
-
var ES256 = { hash: "SHA-256", name: "ECDSA" };
|
|
5078
|
-
var KEY_PARAMS = { name: "ECDSA", namedCurve: "P-256" };
|
|
5079
|
-
var toBase64Url = (bytes) => Buffer.from(bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes)).toString("base64url");
|
|
5080
|
-
var fromBase64Url = (value) => new Uint8Array(Buffer.from(value, "base64url"));
|
|
5081
|
-
var encodeSegment = (value) => Buffer.from(JSON.stringify(value)).toString("base64url");
|
|
5082
|
-
var decodeSegment = (segment) => JSON.parse(Buffer.from(segment, "base64url").toString("utf8"));
|
|
5083
|
-
var generateSigningKey = async () => {
|
|
5084
|
-
const pair = await crypto.subtle.generateKey(KEY_PARAMS, true, [
|
|
5085
|
-
"sign",
|
|
5086
|
-
"verify"
|
|
5087
|
-
]);
|
|
5088
|
-
const privateJwk = await crypto.subtle.exportKey("jwk", pair.privateKey);
|
|
5089
|
-
const publicJwk = await crypto.subtle.exportKey("jwk", pair.publicKey);
|
|
5090
|
-
return { kid: await jwkThumbprint(publicJwk), privateJwk, publicJwk };
|
|
5091
|
-
};
|
|
5092
|
-
var jwkThumbprint = async (jwk) => {
|
|
5093
|
-
const canonical = JSON.stringify({
|
|
5094
|
-
crv: jwk.crv,
|
|
5095
|
-
kty: jwk.kty,
|
|
5096
|
-
x: jwk.x,
|
|
5097
|
-
y: jwk.y
|
|
5098
|
-
});
|
|
5099
|
-
return toBase64Url(await crypto.subtle.digest("SHA-256", ENCODER.encode(canonical)));
|
|
5100
|
-
};
|
|
5101
|
-
var signJwt = async (payload, signing) => {
|
|
5102
|
-
const key = await crypto.subtle.importKey("jwk", signing.privateJwk, KEY_PARAMS, false, ["sign"]);
|
|
5103
|
-
const input = `${encodeSegment({ alg: "ES256", kid: signing.kid, typ: "JWT" })}.${encodeSegment(payload)}`;
|
|
5104
|
-
const signature = await crypto.subtle.sign(ES256, key, ENCODER.encode(input));
|
|
5105
|
-
return `${input}.${toBase64Url(signature)}`;
|
|
5106
|
-
};
|
|
5107
|
-
var toPublicJwk = (key) => ({
|
|
5108
|
-
alg: "ES256",
|
|
5109
|
-
crv: key.publicJwk.crv,
|
|
5110
|
-
kid: key.kid,
|
|
5111
|
-
kty: key.publicJwk.kty,
|
|
5112
|
-
use: "sig",
|
|
5113
|
-
x: key.publicJwk.x,
|
|
5114
|
-
y: key.publicJwk.y
|
|
5115
|
-
});
|
|
5116
|
-
var verifyJwt = async (token, publicJwk) => {
|
|
5117
|
-
const [headerSegment, payloadSegment, signatureSegment] = token.split(".");
|
|
5118
|
-
if (headerSegment === undefined || payloadSegment === undefined || signatureSegment === undefined) {
|
|
5119
|
-
return;
|
|
5120
|
-
}
|
|
5121
|
-
const key = await crypto.subtle.importKey("jwk", publicJwk, KEY_PARAMS, false, ["verify"]);
|
|
5122
|
-
const valid = await crypto.subtle.verify(ES256, key, fromBase64Url(signatureSegment), ENCODER.encode(`${headerSegment}.${payloadSegment}`));
|
|
5123
|
-
if (!valid)
|
|
5124
|
-
return;
|
|
5125
|
-
return {
|
|
5126
|
-
header: decodeSegment(headerSegment),
|
|
5127
|
-
payload: decodeSegment(payloadSegment)
|
|
5128
|
-
};
|
|
5129
|
-
};
|
|
5130
|
-
|
|
5131
|
-
// src/oidc/config.ts
|
|
5132
5871
|
var DEFAULT_OIDC_ROUTE = "/oauth2";
|
|
5133
5872
|
var MS_PER_SECOND = 1000;
|
|
5134
|
-
var
|
|
5873
|
+
var TOKEN_BYTES3 = 32;
|
|
5135
5874
|
var REFRESH_TTL_DAYS = 30;
|
|
5136
|
-
var
|
|
5875
|
+
var DEFAULT_ACCESS_TOKEN_TTL_MS3 = MILLISECONDS_IN_AN_HOUR;
|
|
5137
5876
|
var DEFAULT_ID_TOKEN_TTL_MS = MILLISECONDS_IN_AN_HOUR;
|
|
5138
5877
|
var DEFAULT_REFRESH_TOKEN_TTL_MS = MILLISECONDS_IN_A_DAY * REFRESH_TTL_DAYS;
|
|
5139
5878
|
var resolveAccessTtl = (ttl, scopes) => {
|
|
5140
5879
|
if (typeof ttl === "function")
|
|
5141
5880
|
return ttl({ scopes });
|
|
5142
|
-
return ttl ??
|
|
5881
|
+
return ttl ?? DEFAULT_ACCESS_TOKEN_TTL_MS3;
|
|
5143
5882
|
};
|
|
5144
5883
|
var nowSeconds = (milliseconds) => Math.floor(milliseconds / MS_PER_SECOND);
|
|
5145
5884
|
var narrowScopes = (available, requested) => requested === undefined || requested.length === 0 ? available : requested.filter((scope) => available.includes(scope));
|
|
@@ -5280,7 +6019,7 @@ var issueTokenSet = async ({
|
|
|
5280
6019
|
idPayload.nonce = nonce;
|
|
5281
6020
|
if (acr !== undefined)
|
|
5282
6021
|
idPayload.acr = acr;
|
|
5283
|
-
const refreshToken = generateSecureToken(
|
|
6022
|
+
const refreshToken = generateSecureToken(TOKEN_BYTES3);
|
|
5284
6023
|
await config.refreshTokenStore.saveToken({
|
|
5285
6024
|
acr,
|
|
5286
6025
|
claims,
|
|
@@ -6714,7 +7453,7 @@ var HTTP_UNAUTHORIZED2 = 401;
|
|
|
6714
7453
|
var HTTP_NOT_IMPLEMENTED = 501;
|
|
6715
7454
|
var CODE_TTL_MINUTES = 10;
|
|
6716
7455
|
var CODE_TTL_MS = MILLISECONDS_IN_A_MINUTE * CODE_TTL_MINUTES;
|
|
6717
|
-
var
|
|
7456
|
+
var TOKEN_BYTES4 = 32;
|
|
6718
7457
|
var BASIC_PREFIX2 = "Basic ";
|
|
6719
7458
|
var jsonResponse = (value, status) => new Response(JSON.stringify(value), {
|
|
6720
7459
|
headers: {
|
|
@@ -6797,7 +7536,7 @@ var oidcProviderRoutes = (config) => {
|
|
|
6797
7536
|
const userinfoRoute = `${oidcRoute}/userinfo`;
|
|
6798
7537
|
const registrationBaseUrl = `${issuer}${registrationRoute}`;
|
|
6799
7538
|
const tokenUrl = `${issuer}${oidcRoute}/token`;
|
|
6800
|
-
const resolveClient = async (clientId) => await clientStore.findClient(clientId) ??
|
|
7539
|
+
const resolveClient = async (clientId) => await clientStore.findClient(clientId) ?? config.resolveClientIdMetadata?.(clientId);
|
|
6801
7540
|
const authenticateClient = async (clientId, clientSecret) => {
|
|
6802
7541
|
const client = await resolveClient(clientId);
|
|
6803
7542
|
if (client === undefined)
|
|
@@ -7059,13 +7798,17 @@ var oidcProviderRoutes = (config) => {
|
|
|
7059
7798
|
if (config.vciConfig !== undefined) {
|
|
7060
7799
|
grantTypes.push(PRE_AUTHORIZED_CODE_GRANT);
|
|
7061
7800
|
}
|
|
7801
|
+
for (const grantType of config.publicTokenGrantTypes ?? []) {
|
|
7802
|
+
if (!grantTypes.includes(grantType))
|
|
7803
|
+
grantTypes.push(grantType);
|
|
7804
|
+
}
|
|
7062
7805
|
const discovery = {
|
|
7063
7806
|
authorization_endpoint: `${issuer}${authorizeRoute}`,
|
|
7064
7807
|
authorization_response_iss_parameter_supported: true,
|
|
7065
7808
|
backchannel_logout_session_supported: false,
|
|
7066
7809
|
backchannel_logout_supported: true,
|
|
7067
|
-
code_challenge_methods_supported: ["S256"],
|
|
7068
7810
|
client_id_metadata_document_supported: config.resolveClientIdMetadata !== undefined,
|
|
7811
|
+
code_challenge_methods_supported: ["S256"],
|
|
7069
7812
|
dpop_signing_alg_values_supported: ["ES256"],
|
|
7070
7813
|
end_session_endpoint: `${issuer}${endSessionRoute}`,
|
|
7071
7814
|
grant_types_supported: grantTypes,
|
|
@@ -7113,6 +7856,11 @@ var oidcProviderRoutes = (config) => {
|
|
|
7113
7856
|
if (config.acrValuesSupported !== undefined && config.acrValuesSupported.length > 0) {
|
|
7114
7857
|
discovery.acr_values_supported = config.acrValuesSupported;
|
|
7115
7858
|
}
|
|
7859
|
+
const reservedDiscoveryKeys = new Set(Object.keys(discovery));
|
|
7860
|
+
for (const [key, value] of Object.entries(config.additionalDiscoveryMetadata ?? {})) {
|
|
7861
|
+
if (!reservedDiscoveryKeys.has(key))
|
|
7862
|
+
discovery[key] = value;
|
|
7863
|
+
}
|
|
7116
7864
|
const handleEndSession = async ({
|
|
7117
7865
|
cookie,
|
|
7118
7866
|
inMemorySession,
|
|
@@ -7287,7 +8035,7 @@ var oidcProviderRoutes = (config) => {
|
|
|
7287
8035
|
if (requestedAcr !== undefined && (userAcr === undefined || !requestedAcr.includes(userAcr))) {
|
|
7288
8036
|
return errorRedirect("insufficient_user_authentication");
|
|
7289
8037
|
}
|
|
7290
|
-
const code = generateSecureToken(
|
|
8038
|
+
const code = generateSecureToken(TOKEN_BYTES4);
|
|
7291
8039
|
await authorizationCodeStore.saveCode({
|
|
7292
8040
|
acr: userAcr,
|
|
7293
8041
|
claims: getClaims?.(userSession.user),
|
|
@@ -7352,6 +8100,15 @@ var oidcProviderRoutes = (config) => {
|
|
|
7352
8100
|
token_type: result.token_type
|
|
7353
8101
|
}, HTTP_OK2);
|
|
7354
8102
|
}
|
|
8103
|
+
if (typeof body.grant_type === "string" && config.publicTokenGrantTypes?.includes(body.grant_type) === true && config.handlePublicTokenGrant !== undefined) {
|
|
8104
|
+
const result = await config.handlePublicTokenGrant({
|
|
8105
|
+
body,
|
|
8106
|
+
request
|
|
8107
|
+
});
|
|
8108
|
+
if (result !== undefined) {
|
|
8109
|
+
return jsonResponse(result.body, result.status);
|
|
8110
|
+
}
|
|
8111
|
+
}
|
|
7355
8112
|
const basic = readBasicAuth2(headers.authorization);
|
|
7356
8113
|
const auth = await authenticateTokenClient({
|
|
7357
8114
|
basicClientId: basic.clientId,
|
|
@@ -7387,8 +8144,10 @@ var oidcProviderRoutes = (config) => {
|
|
|
7387
8144
|
return oauthError2(HTTP_BAD_REQUEST2, "unsupported_grant_type");
|
|
7388
8145
|
}, {
|
|
7389
8146
|
body: t14.Object({
|
|
8147
|
+
assertion: t14.Optional(t14.String()),
|
|
7390
8148
|
audience: t14.Optional(t14.String()),
|
|
7391
8149
|
auth_req_id: t14.Optional(t14.String()),
|
|
8150
|
+
claim_token: t14.Optional(t14.String()),
|
|
7392
8151
|
client_assertion: t14.Optional(t14.String()),
|
|
7393
8152
|
client_assertion_type: t14.Optional(t14.String()),
|
|
7394
8153
|
client_id: t14.Optional(t14.String()),
|
|
@@ -13742,7 +14501,7 @@ var PgJson = class extends PgColumn {
|
|
|
13742
14501
|
return "json";
|
|
13743
14502
|
}
|
|
13744
14503
|
};
|
|
13745
|
-
function
|
|
14504
|
+
function json2(name2) {
|
|
13746
14505
|
return new PgJsonBuilder(name2 ?? "");
|
|
13747
14506
|
}
|
|
13748
14507
|
|
|
@@ -14279,7 +15038,7 @@ function getPgColumnBuilders() {
|
|
|
14279
15038
|
inet,
|
|
14280
15039
|
integer,
|
|
14281
15040
|
interval,
|
|
14282
|
-
json,
|
|
15041
|
+
json: json2,
|
|
14283
15042
|
jsonb,
|
|
14284
15043
|
line,
|
|
14285
15044
|
macaddr,
|
|
@@ -14496,6 +15255,9 @@ var Index = class {
|
|
|
14496
15255
|
function index(name2) {
|
|
14497
15256
|
return new IndexBuilderOn(false, name2);
|
|
14498
15257
|
}
|
|
15258
|
+
function uniqueIndex(name2) {
|
|
15259
|
+
return new IndexBuilderOn(true, name2);
|
|
15260
|
+
}
|
|
14499
15261
|
// node_modules/drizzle-orm/pg-core/checks.js
|
|
14500
15262
|
var CheckBuilder = class {
|
|
14501
15263
|
static [entityKind] = "PgCheckBuilder";
|
|
@@ -24966,6 +25728,284 @@ var createPostgresScimTokenStore = (db) => ({
|
|
|
24966
25728
|
});
|
|
24967
25729
|
}
|
|
24968
25730
|
});
|
|
25731
|
+
// src/agents/registrationClient.ts
|
|
25732
|
+
var isObject2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
25733
|
+
var secureUrl = (value, allowLocalhost) => {
|
|
25734
|
+
if (typeof value !== "string")
|
|
25735
|
+
return;
|
|
25736
|
+
try {
|
|
25737
|
+
const url = new URL(value);
|
|
25738
|
+
if (url.protocol === "https:")
|
|
25739
|
+
return url.toString();
|
|
25740
|
+
if (allowLocalhost && url.protocol === "http:" && (url.hostname === "localhost" || url.hostname === "127.0.0.1")) {
|
|
25741
|
+
return url.toString();
|
|
25742
|
+
}
|
|
25743
|
+
} catch {
|
|
25744
|
+
return;
|
|
25745
|
+
}
|
|
25746
|
+
return;
|
|
25747
|
+
};
|
|
25748
|
+
var readBoundedJson = async (response, maxBytes) => {
|
|
25749
|
+
const length = Number(response.headers.get("content-length"));
|
|
25750
|
+
if (Number.isFinite(length) && length > maxBytes) {
|
|
25751
|
+
throw new Error("Agent registration metadata exceeds the response limit");
|
|
25752
|
+
}
|
|
25753
|
+
const bytes = new Uint8Array(await response.arrayBuffer());
|
|
25754
|
+
if (bytes.byteLength > maxBytes) {
|
|
25755
|
+
throw new Error("Agent registration metadata exceeds the response limit");
|
|
25756
|
+
}
|
|
25757
|
+
const parsed = JSON.parse(new TextDecoder().decode(bytes));
|
|
25758
|
+
return parsed;
|
|
25759
|
+
};
|
|
25760
|
+
var stringArray = (value) => Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : [];
|
|
25761
|
+
var requestJson = async (request, url, init, maxBytes) => {
|
|
25762
|
+
const response = await request(url, {
|
|
25763
|
+
...init,
|
|
25764
|
+
headers: {
|
|
25765
|
+
accept: "application/json",
|
|
25766
|
+
...init.headers
|
|
25767
|
+
},
|
|
25768
|
+
redirect: "error"
|
|
25769
|
+
});
|
|
25770
|
+
const body = await readBoundedJson(response, maxBytes);
|
|
25771
|
+
if (!isObject2(body))
|
|
25772
|
+
throw new Error("Expected a JSON object");
|
|
25773
|
+
return { body, response };
|
|
25774
|
+
};
|
|
25775
|
+
var createAgentRegistrationClient = (discovery, options = {}) => {
|
|
25776
|
+
const request = options.request ?? fetch;
|
|
25777
|
+
const maxBytes = options.maxResponseBytes ?? 256 * 1024;
|
|
25778
|
+
const post = (url, body, form = false) => requestJson(request, url, {
|
|
25779
|
+
body: form ? new URLSearchParams(Object.fromEntries(Object.entries(body).map(([key, value]) => [
|
|
25780
|
+
key,
|
|
25781
|
+
String(value)
|
|
25782
|
+
]))) : JSON.stringify(body),
|
|
25783
|
+
headers: {
|
|
25784
|
+
"content-type": form ? "application/x-www-form-urlencoded" : "application/json"
|
|
25785
|
+
},
|
|
25786
|
+
method: "POST"
|
|
25787
|
+
}, maxBytes);
|
|
25788
|
+
return {
|
|
25789
|
+
beginAnonymous: () => post(discovery.agentAuth.identityEndpoint, { type: "anonymous" }),
|
|
25790
|
+
beginServiceAuth: (loginHint) => post(discovery.agentAuth.identityEndpoint, {
|
|
25791
|
+
login_hint: loginHint,
|
|
25792
|
+
type: "service_auth"
|
|
25793
|
+
}),
|
|
25794
|
+
beginVerified: (assertion) => {
|
|
25795
|
+
if (!discovery.agentAuth.identityAssertionTypes.includes(AGENT_IDENTITY_ASSERTION_TYPE)) {
|
|
25796
|
+
throw new Error("Service does not accept ID-JAG assertions");
|
|
25797
|
+
}
|
|
25798
|
+
return post(discovery.agentAuth.identityEndpoint, {
|
|
25799
|
+
assertion,
|
|
25800
|
+
assertion_type: AGENT_IDENTITY_ASSERTION_TYPE,
|
|
25801
|
+
type: "identity_assertion"
|
|
25802
|
+
});
|
|
25803
|
+
},
|
|
25804
|
+
claim: (claimToken, email) => post(discovery.agentAuth.claimEndpoint, {
|
|
25805
|
+
claim_token: claimToken,
|
|
25806
|
+
email
|
|
25807
|
+
}),
|
|
25808
|
+
exchangeAssertion: (assertion) => post(discovery.tokenEndpoint, {
|
|
25809
|
+
assertion,
|
|
25810
|
+
grant_type: AGENT_IDENTITY_ASSERTION_GRANT_TYPE,
|
|
25811
|
+
resource: discovery.resource
|
|
25812
|
+
}, true),
|
|
25813
|
+
pollClaim: (claimToken) => post(discovery.tokenEndpoint, {
|
|
25814
|
+
claim_token: claimToken,
|
|
25815
|
+
grant_type: AGENT_CLAIM_GRANT_TYPE
|
|
25816
|
+
}, true)
|
|
25817
|
+
};
|
|
25818
|
+
};
|
|
25819
|
+
var discoverAgentRegistration = async (resource, options = {}) => {
|
|
25820
|
+
const request = options.request ?? fetch;
|
|
25821
|
+
const maxBytes = options.maxResponseBytes ?? 256 * 1024;
|
|
25822
|
+
const allowLocalhost = options.allowInsecureLocalhost === true;
|
|
25823
|
+
const resourceUrl = secureUrl(resource, allowLocalhost);
|
|
25824
|
+
if (resourceUrl === undefined)
|
|
25825
|
+
throw new Error("Resource URL must use HTTPS");
|
|
25826
|
+
const resourceMetadataUrl = new URL("/.well-known/oauth-protected-resource", resourceUrl).toString();
|
|
25827
|
+
const prm = await requestJson(request, resourceMetadataUrl, {}, maxBytes);
|
|
25828
|
+
const advertisedResource = secureUrl(prm.body.resource, allowLocalhost);
|
|
25829
|
+
if (advertisedResource === undefined || advertisedResource !== resourceUrl) {
|
|
25830
|
+
throw new Error("Protected resource metadata identity mismatch");
|
|
25831
|
+
}
|
|
25832
|
+
const authorizationServer = secureUrl(stringArray(prm.body.authorization_servers)[0], allowLocalhost);
|
|
25833
|
+
if (authorizationServer === undefined) {
|
|
25834
|
+
throw new Error("No secure authorization server is advertised");
|
|
25835
|
+
}
|
|
25836
|
+
const asUrl = new URL("/.well-known/oauth-authorization-server", authorizationServer).toString();
|
|
25837
|
+
const metadata = await requestJson(request, asUrl, {}, maxBytes);
|
|
25838
|
+
if (secureUrl(metadata.body.issuer, allowLocalhost) !== authorizationServer) {
|
|
25839
|
+
throw new Error("Authorization server issuer mismatch");
|
|
25840
|
+
}
|
|
25841
|
+
const tokenEndpoint = secureUrl(metadata.body.token_endpoint, allowLocalhost);
|
|
25842
|
+
const agentAuth = metadata.body.agent_auth;
|
|
25843
|
+
if (tokenEndpoint === undefined || !isObject2(agentAuth)) {
|
|
25844
|
+
throw new Error("Authorization server does not advertise agent registration");
|
|
25845
|
+
}
|
|
25846
|
+
const identityEndpoint = secureUrl(agentAuth.identity_endpoint, allowLocalhost);
|
|
25847
|
+
const claimEndpoint = secureUrl(agentAuth.claim_endpoint, allowLocalhost);
|
|
25848
|
+
const skill = secureUrl(agentAuth.skill, allowLocalhost);
|
|
25849
|
+
const assertionMetadata = agentAuth.identity_assertion;
|
|
25850
|
+
if (identityEndpoint === undefined || claimEndpoint === undefined || skill === undefined || !isObject2(assertionMetadata)) {
|
|
25851
|
+
throw new Error("Agent registration metadata is incomplete");
|
|
25852
|
+
}
|
|
25853
|
+
return {
|
|
25854
|
+
agentAuth: {
|
|
25855
|
+
claimEndpoint,
|
|
25856
|
+
identityAssertionTypes: stringArray(assertionMetadata.assertion_types_supported),
|
|
25857
|
+
identityEndpoint,
|
|
25858
|
+
identityTypes: stringArray(agentAuth.identity_types_supported),
|
|
25859
|
+
skill
|
|
25860
|
+
},
|
|
25861
|
+
authorizationServer,
|
|
25862
|
+
resource: resourceUrl,
|
|
25863
|
+
resourceMetadataUrl,
|
|
25864
|
+
scopes: stringArray(prm.body.scopes_supported),
|
|
25865
|
+
tokenEndpoint
|
|
25866
|
+
};
|
|
25867
|
+
};
|
|
25868
|
+
// src/agents/idJag.ts
|
|
25869
|
+
init_constants();
|
|
25870
|
+
var createInMemoryAgentIdentityAssertionJtiStore = () => {
|
|
25871
|
+
const entries = new Map;
|
|
25872
|
+
return {
|
|
25873
|
+
recordIfFresh: async (issuer, jti, expiresAt) => {
|
|
25874
|
+
const now = Date.now();
|
|
25875
|
+
for (const [key2, expiry] of entries) {
|
|
25876
|
+
if (expiry <= now)
|
|
25877
|
+
entries.delete(key2);
|
|
25878
|
+
}
|
|
25879
|
+
const key = `${issuer}\x00${jti}`;
|
|
25880
|
+
if (entries.has(key))
|
|
25881
|
+
return false;
|
|
25882
|
+
entries.set(key, expiresAt);
|
|
25883
|
+
return true;
|
|
25884
|
+
}
|
|
25885
|
+
};
|
|
25886
|
+
};
|
|
25887
|
+
var issueAgentIdentityAssertion = async ({
|
|
25888
|
+
agentContextId,
|
|
25889
|
+
agentPlatform,
|
|
25890
|
+
audience,
|
|
25891
|
+
clientId,
|
|
25892
|
+
issuer,
|
|
25893
|
+
now = Date.now(),
|
|
25894
|
+
resource,
|
|
25895
|
+
signingKey,
|
|
25896
|
+
ttlMs = 5 * 60 * MILLISECONDS_IN_A_SECOND,
|
|
25897
|
+
user
|
|
25898
|
+
}) => {
|
|
25899
|
+
if (user.emailVerified !== true && user.phoneNumberVerified !== true) {
|
|
25900
|
+
throw new Error("ID-JAG issuance requires a verified email or phone number");
|
|
25901
|
+
}
|
|
25902
|
+
const expiresAt = now + ttlMs;
|
|
25903
|
+
const payload = {
|
|
25904
|
+
aud: audience,
|
|
25905
|
+
auth_time: Math.floor(user.authenticatedAt / MILLISECONDS_IN_A_SECOND),
|
|
25906
|
+
client_id: clientId,
|
|
25907
|
+
exp: Math.floor(expiresAt / MILLISECONDS_IN_A_SECOND),
|
|
25908
|
+
iat: Math.floor(now / MILLISECONDS_IN_A_SECOND),
|
|
25909
|
+
iss: issuer,
|
|
25910
|
+
jti: crypto.randomUUID(),
|
|
25911
|
+
sub: user.subject
|
|
25912
|
+
};
|
|
25913
|
+
if (user.email !== undefined)
|
|
25914
|
+
payload.email = user.email;
|
|
25915
|
+
if (user.emailVerified !== undefined)
|
|
25916
|
+
payload.email_verified = user.emailVerified;
|
|
25917
|
+
if (user.name !== undefined)
|
|
25918
|
+
payload.name = user.name;
|
|
25919
|
+
if (user.phoneNumber !== undefined)
|
|
25920
|
+
payload.phone_number = user.phoneNumber;
|
|
25921
|
+
if (user.phoneNumberVerified !== undefined)
|
|
25922
|
+
payload.phone_number_verified = user.phoneNumberVerified;
|
|
25923
|
+
if (user.methods !== undefined)
|
|
25924
|
+
payload.amr = user.methods;
|
|
25925
|
+
if (resource !== undefined)
|
|
25926
|
+
payload.resource = resource;
|
|
25927
|
+
if (agentPlatform !== undefined)
|
|
25928
|
+
payload.agent_platform = agentPlatform;
|
|
25929
|
+
if (agentContextId !== undefined)
|
|
25930
|
+
payload.agent_context_id = agentContextId;
|
|
25931
|
+
return {
|
|
25932
|
+
assertion: await signJwt(payload, signingKey, "oauth-id-jag+jwt"),
|
|
25933
|
+
assertionType: AGENT_IDENTITY_ASSERTION_TYPE,
|
|
25934
|
+
expiresAt
|
|
25935
|
+
};
|
|
25936
|
+
};
|
|
25937
|
+
var numberClaim2 = (value) => typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
25938
|
+
var stringClaim2 = (value) => typeof value === "string" && value.length > 0 ? value : undefined;
|
|
25939
|
+
var createAgentIdentityAssertionVerifier = ({
|
|
25940
|
+
audience,
|
|
25941
|
+
clockSkewMs = MILLISECONDS_IN_A_SECOND * 60,
|
|
25942
|
+
jtiStore,
|
|
25943
|
+
maxAssertionLifetimeMs = MILLISECONDS_IN_A_SECOND * 60 * 60,
|
|
25944
|
+
maxAuthenticationAgeMs = MILLISECONDS_IN_A_SECOND * 60 * 60,
|
|
25945
|
+
resolveIssuer
|
|
25946
|
+
}) => async (assertion, now = Date.now()) => {
|
|
25947
|
+
const segments = assertion.split(".");
|
|
25948
|
+
if (segments.length !== 3 || segments[1] === undefined)
|
|
25949
|
+
return;
|
|
25950
|
+
let decoded;
|
|
25951
|
+
try {
|
|
25952
|
+
decoded = JSON.parse(Buffer.from(segments[1], "base64url").toString("utf8"));
|
|
25953
|
+
} catch {
|
|
25954
|
+
return;
|
|
25955
|
+
}
|
|
25956
|
+
if (typeof decoded !== "object" || decoded === null || Array.isArray(decoded)) {
|
|
25957
|
+
return;
|
|
25958
|
+
}
|
|
25959
|
+
const unverified = Object.fromEntries(Object.entries(decoded));
|
|
25960
|
+
const issuer = stringClaim2(unverified.iss);
|
|
25961
|
+
if (issuer === undefined)
|
|
25962
|
+
return;
|
|
25963
|
+
const trusted = await resolveIssuer(issuer);
|
|
25964
|
+
if (trusted === undefined)
|
|
25965
|
+
return;
|
|
25966
|
+
const verified = await verifyJwt(assertion, trusted.publicJwk);
|
|
25967
|
+
if (verified?.header?.typ !== "oauth-id-jag+jwt")
|
|
25968
|
+
return;
|
|
25969
|
+
const { payload } = verified;
|
|
25970
|
+
const subject = stringClaim2(payload.sub);
|
|
25971
|
+
const jti = stringClaim2(payload.jti);
|
|
25972
|
+
const clientId = stringClaim2(payload.client_id);
|
|
25973
|
+
const expiresAtSeconds = numberClaim2(payload.exp);
|
|
25974
|
+
const issuedAtSeconds = numberClaim2(payload.iat);
|
|
25975
|
+
const authenticatedAtSeconds = numberClaim2(payload.auth_time);
|
|
25976
|
+
if (payload.iss !== issuer || payload.aud !== audience || subject === undefined || jti === undefined || clientId === undefined || expiresAtSeconds === undefined || issuedAtSeconds === undefined || authenticatedAtSeconds === undefined) {
|
|
25977
|
+
return;
|
|
25978
|
+
}
|
|
25979
|
+
const expiresAt = expiresAtSeconds * MILLISECONDS_IN_A_SECOND;
|
|
25980
|
+
const issuedAt = issuedAtSeconds * MILLISECONDS_IN_A_SECOND;
|
|
25981
|
+
const authenticatedAt = authenticatedAtSeconds * MILLISECONDS_IN_A_SECOND;
|
|
25982
|
+
if (expiresAt <= now - clockSkewMs || issuedAt > now + clockSkewMs || expiresAt <= issuedAt || expiresAt - issuedAt > maxAssertionLifetimeMs + clockSkewMs || authenticatedAt > now + clockSkewMs || authenticatedAt > issuedAt + clockSkewMs || now - authenticatedAt > maxAuthenticationAgeMs + clockSkewMs) {
|
|
25983
|
+
return;
|
|
25984
|
+
}
|
|
25985
|
+
if (trusted.allowedClientIds !== undefined && !trusted.allowedClientIds.includes(clientId)) {
|
|
25986
|
+
return;
|
|
25987
|
+
}
|
|
25988
|
+
const email = stringClaim2(payload.email);
|
|
25989
|
+
const phoneNumber = stringClaim2(payload.phone_number);
|
|
25990
|
+
const emailVerified = payload.email_verified === true;
|
|
25991
|
+
const phoneNumberVerified = payload.phone_number_verified === true;
|
|
25992
|
+
if (!emailVerified && !phoneNumberVerified)
|
|
25993
|
+
return;
|
|
25994
|
+
if (!await jtiStore.recordIfFresh(issuer, jti, expiresAt)) {
|
|
25995
|
+
return;
|
|
25996
|
+
}
|
|
25997
|
+
return {
|
|
25998
|
+
authenticatedAt,
|
|
25999
|
+
clientId,
|
|
26000
|
+
email,
|
|
26001
|
+
emailVerified,
|
|
26002
|
+
issuer,
|
|
26003
|
+
name: stringClaim2(payload.name),
|
|
26004
|
+
phoneNumber,
|
|
26005
|
+
phoneNumberVerified,
|
|
26006
|
+
subject
|
|
26007
|
+
};
|
|
26008
|
+
};
|
|
24969
26009
|
// src/agents/oidcAdapter.ts
|
|
24970
26010
|
var BEARER_PREFIX6 = "Bearer ";
|
|
24971
26011
|
var MS_PER_SECOND7 = 1000;
|
|
@@ -25027,6 +26067,11 @@ var createOidcAgentCredentialVerifier = ({
|
|
|
25027
26067
|
return verifier;
|
|
25028
26068
|
};
|
|
25029
26069
|
// src/agents/inMemoryStores.ts
|
|
26070
|
+
var cloneIdentityRegistration = (value) => ({
|
|
26071
|
+
...value,
|
|
26072
|
+
claimAttempt: value.claimAttempt === undefined ? undefined : { ...value.claimAttempt },
|
|
26073
|
+
upstream: value.upstream === undefined ? undefined : { ...value.upstream }
|
|
26074
|
+
});
|
|
25030
26075
|
var cloneRegistration = (value) => ({
|
|
25031
26076
|
...value,
|
|
25032
26077
|
allowedScopes: [...value.allowedScopes],
|
|
@@ -25061,6 +26106,48 @@ var createInMemoryAgentDelegationStore = () => {
|
|
|
25061
26106
|
}
|
|
25062
26107
|
};
|
|
25063
26108
|
};
|
|
26109
|
+
var createInMemoryAgentIdentityRegistrationStore = () => {
|
|
26110
|
+
const registrations = new Map;
|
|
26111
|
+
return {
|
|
26112
|
+
create: async (registration) => {
|
|
26113
|
+
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);
|
|
26114
|
+
if (conflicts)
|
|
26115
|
+
return false;
|
|
26116
|
+
registrations.set(registration.registrationId, cloneIdentityRegistration(registration));
|
|
26117
|
+
return true;
|
|
26118
|
+
},
|
|
26119
|
+
findByAgentId: async (agentId) => {
|
|
26120
|
+
const value = [...registrations.values()].find((registration) => registration.agentId === agentId);
|
|
26121
|
+
return value === undefined ? undefined : cloneIdentityRegistration(value);
|
|
26122
|
+
},
|
|
26123
|
+
findByAttemptTokenHash: async (attemptTokenHash) => {
|
|
26124
|
+
const value = [...registrations.values()].find((registration) => registration.claimAttempt?.tokenHash === attemptTokenHash);
|
|
26125
|
+
return value === undefined ? undefined : cloneIdentityRegistration(value);
|
|
26126
|
+
},
|
|
26127
|
+
findByClaimTokenHash: async (claimTokenHash) => {
|
|
26128
|
+
const value = [...registrations.values()].find((registration) => registration.claimTokenHash === claimTokenHash);
|
|
26129
|
+
return value === undefined ? undefined : cloneIdentityRegistration(value);
|
|
26130
|
+
},
|
|
26131
|
+
findByRegistrationId: async (registrationId) => {
|
|
26132
|
+
const value = registrations.get(registrationId);
|
|
26133
|
+
return value === undefined ? undefined : cloneIdentityRegistration(value);
|
|
26134
|
+
},
|
|
26135
|
+
findByUpstreamIdentity: async ({ clientId, issuer, subject }) => {
|
|
26136
|
+
const value = [...registrations.values()].find((registration) => registration.upstream?.clientId === clientId && registration.upstream?.issuer === issuer && registration.upstream.subject === subject);
|
|
26137
|
+
return value === undefined ? undefined : cloneIdentityRegistration(value);
|
|
26138
|
+
},
|
|
26139
|
+
replace: async (registration, expectedVersion) => {
|
|
26140
|
+
const current = registrations.get(registration.registrationId);
|
|
26141
|
+
if (current?.version !== expectedVersion)
|
|
26142
|
+
return false;
|
|
26143
|
+
registrations.set(registration.registrationId, cloneIdentityRegistration({
|
|
26144
|
+
...registration,
|
|
26145
|
+
version: expectedVersion + 1
|
|
26146
|
+
}));
|
|
26147
|
+
return true;
|
|
26148
|
+
}
|
|
26149
|
+
};
|
|
26150
|
+
};
|
|
25064
26151
|
var createInMemoryAgentRegistrationStore = () => {
|
|
25065
26152
|
const registrations = new Map;
|
|
25066
26153
|
return {
|
|
@@ -25096,6 +26183,38 @@ var agentDelegationsTable = pgTable("auth_agent_delegations", {
|
|
|
25096
26183
|
updated_at_ms: bigint("updated_at_ms", { mode: "number" }).notNull(),
|
|
25097
26184
|
user_id: varchar("user_id", { length: ID_LENGTH7 }).notNull()
|
|
25098
26185
|
});
|
|
26186
|
+
var agentIdentityRegistrationsTable = pgTable("auth_agent_identity_registrations", {
|
|
26187
|
+
agent_id: varchar("agent_id", { length: ID_LENGTH7 }).notNull().unique(),
|
|
26188
|
+
claim_attempt: jsonb("claim_attempt").$type(),
|
|
26189
|
+
claim_attempt_token_hash: varchar("claim_attempt_token_hash", {
|
|
26190
|
+
length: ID_LENGTH7
|
|
26191
|
+
}).unique(),
|
|
26192
|
+
claim_expires_at_ms: bigint("claim_expires_at_ms", {
|
|
26193
|
+
mode: "number"
|
|
26194
|
+
}).notNull(),
|
|
26195
|
+
claim_token_hash: varchar("claim_token_hash", {
|
|
26196
|
+
length: ID_LENGTH7
|
|
26197
|
+
}).notNull().unique(),
|
|
26198
|
+
created_at_ms: bigint("created_at_ms", { mode: "number" }).notNull(),
|
|
26199
|
+
expires_at_ms: bigint("expires_at_ms", { mode: "number" }).notNull(),
|
|
26200
|
+
kind: varchar("kind", { length: 32 }).$type().notNull(),
|
|
26201
|
+
last_polled_at_ms: bigint("last_polled_at_ms", { mode: "number" }),
|
|
26202
|
+
login_hint: varchar("login_hint", { length: ID_LENGTH7 }),
|
|
26203
|
+
registration_id: varchar("registration_id", {
|
|
26204
|
+
length: ID_LENGTH7
|
|
26205
|
+
}).primaryKey(),
|
|
26206
|
+
status: varchar("status", { length: STATUS_LENGTH2 }).$type().notNull(),
|
|
26207
|
+
updated_at_ms: bigint("updated_at_ms", { mode: "number" }).notNull(),
|
|
26208
|
+
upstream_client_id: varchar("upstream_client_id", {
|
|
26209
|
+
length: ID_LENGTH7
|
|
26210
|
+
}),
|
|
26211
|
+
upstream_issuer: varchar("upstream_issuer", { length: ID_LENGTH7 }),
|
|
26212
|
+
upstream_subject: varchar("upstream_subject", { length: ID_LENGTH7 }),
|
|
26213
|
+
user_id: varchar("user_id", { length: ID_LENGTH7 }),
|
|
26214
|
+
version: integer("version").notNull()
|
|
26215
|
+
}, (table) => [
|
|
26216
|
+
uniqueIndex("auth_agent_identity_upstream_unique").on(table.upstream_issuer, table.upstream_subject, table.upstream_client_id)
|
|
26217
|
+
]);
|
|
25099
26218
|
var agentRegistrationsTable = pgTable("auth_agent_registrations", {
|
|
25100
26219
|
agent_id: varchar("agent_id", { length: ID_LENGTH7 }).primaryKey(),
|
|
25101
26220
|
allowed_scopes: jsonb("allowed_scopes").$type().notNull().default([]),
|
|
@@ -25128,7 +26247,49 @@ var toDelegation = (row) => ({
|
|
|
25128
26247
|
updatedAt: row.updated_at_ms,
|
|
25129
26248
|
userId: row.user_id
|
|
25130
26249
|
});
|
|
26250
|
+
var toIdentityRegistration = (row) => ({
|
|
26251
|
+
agentId: row.agent_id,
|
|
26252
|
+
claimAttempt: row.claim_attempt ?? undefined,
|
|
26253
|
+
claimExpiresAt: row.claim_expires_at_ms,
|
|
26254
|
+
claimTokenHash: row.claim_token_hash,
|
|
26255
|
+
createdAt: row.created_at_ms,
|
|
26256
|
+
expiresAt: row.expires_at_ms,
|
|
26257
|
+
kind: row.kind,
|
|
26258
|
+
lastPolledAt: row.last_polled_at_ms ?? undefined,
|
|
26259
|
+
loginHint: row.login_hint ?? undefined,
|
|
26260
|
+
registrationId: row.registration_id,
|
|
26261
|
+
status: row.status,
|
|
26262
|
+
updatedAt: row.updated_at_ms,
|
|
26263
|
+
upstream: row.upstream_client_id === null || row.upstream_issuer === null || row.upstream_subject === null ? undefined : {
|
|
26264
|
+
clientId: row.upstream_client_id,
|
|
26265
|
+
issuer: row.upstream_issuer,
|
|
26266
|
+
subject: row.upstream_subject
|
|
26267
|
+
},
|
|
26268
|
+
userId: row.user_id ?? undefined,
|
|
26269
|
+
version: row.version
|
|
26270
|
+
});
|
|
26271
|
+
var identityRegistrationValues = (registration) => ({
|
|
26272
|
+
agent_id: registration.agentId,
|
|
26273
|
+
claim_attempt: registration.claimAttempt ?? null,
|
|
26274
|
+
claim_attempt_token_hash: registration.claimAttempt?.tokenHash ?? null,
|
|
26275
|
+
claim_expires_at_ms: registration.claimExpiresAt,
|
|
26276
|
+
claim_token_hash: registration.claimTokenHash,
|
|
26277
|
+
created_at_ms: registration.createdAt,
|
|
26278
|
+
expires_at_ms: registration.expiresAt,
|
|
26279
|
+
kind: registration.kind,
|
|
26280
|
+
last_polled_at_ms: registration.lastPolledAt ?? null,
|
|
26281
|
+
login_hint: registration.loginHint ?? null,
|
|
26282
|
+
registration_id: registration.registrationId,
|
|
26283
|
+
status: registration.status,
|
|
26284
|
+
updated_at_ms: registration.updatedAt,
|
|
26285
|
+
upstream_client_id: registration.upstream?.clientId ?? null,
|
|
26286
|
+
upstream_issuer: registration.upstream?.issuer ?? null,
|
|
26287
|
+
upstream_subject: registration.upstream?.subject ?? null,
|
|
26288
|
+
user_id: registration.userId ?? null,
|
|
26289
|
+
version: registration.version
|
|
26290
|
+
});
|
|
25131
26291
|
var createNeonAgentDelegationStore = (databaseUrl) => createPostgresAgentDelegationStore(createNeonDatabase(databaseUrl));
|
|
26292
|
+
var createNeonAgentIdentityRegistrationStore = (databaseUrl) => createPostgresAgentIdentityRegistrationStore(createNeonDatabase(databaseUrl));
|
|
25132
26293
|
var createNeonAgentRegistrationStore = (databaseUrl) => createPostgresAgentRegistrationStore(createNeonDatabase(databaseUrl));
|
|
25133
26294
|
var createPostgresAgentDelegationStore = (db) => ({
|
|
25134
26295
|
findActiveDelegation: async ({
|
|
@@ -25169,6 +26330,41 @@ var createPostgresAgentDelegationStore = (db) => ({
|
|
|
25169
26330
|
});
|
|
25170
26331
|
}
|
|
25171
26332
|
});
|
|
26333
|
+
var createPostgresAgentIdentityRegistrationStore = (db) => ({
|
|
26334
|
+
create: async (registration) => {
|
|
26335
|
+
const rows = await db.insert(agentIdentityRegistrationsTable).values(identityRegistrationValues(registration)).onConflictDoNothing().returning({ id: agentIdentityRegistrationsTable.registration_id });
|
|
26336
|
+
return rows.length === 1;
|
|
26337
|
+
},
|
|
26338
|
+
findByAgentId: async (agentId) => {
|
|
26339
|
+
const [row] = await db.select().from(agentIdentityRegistrationsTable).where(eq(agentIdentityRegistrationsTable.agent_id, agentId)).limit(1);
|
|
26340
|
+
return row === undefined ? undefined : toIdentityRegistration(row);
|
|
26341
|
+
},
|
|
26342
|
+
findByAttemptTokenHash: async (attemptTokenHash) => {
|
|
26343
|
+
const [row] = await db.select().from(agentIdentityRegistrationsTable).where(eq(agentIdentityRegistrationsTable.claim_attempt_token_hash, attemptTokenHash)).limit(1);
|
|
26344
|
+
return row === undefined ? undefined : toIdentityRegistration(row);
|
|
26345
|
+
},
|
|
26346
|
+
findByClaimTokenHash: async (claimTokenHash) => {
|
|
26347
|
+
const [row] = await db.select().from(agentIdentityRegistrationsTable).where(eq(agentIdentityRegistrationsTable.claim_token_hash, claimTokenHash)).limit(1);
|
|
26348
|
+
return row === undefined ? undefined : toIdentityRegistration(row);
|
|
26349
|
+
},
|
|
26350
|
+
findByRegistrationId: async (registrationId) => {
|
|
26351
|
+
const [row] = await db.select().from(agentIdentityRegistrationsTable).where(eq(agentIdentityRegistrationsTable.registration_id, registrationId)).limit(1);
|
|
26352
|
+
return row === undefined ? undefined : toIdentityRegistration(row);
|
|
26353
|
+
},
|
|
26354
|
+
findByUpstreamIdentity: async ({ clientId, issuer, subject }) => {
|
|
26355
|
+
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);
|
|
26356
|
+
return row === undefined ? undefined : toIdentityRegistration(row);
|
|
26357
|
+
},
|
|
26358
|
+
replace: async (registration, expectedVersion) => {
|
|
26359
|
+
const next = {
|
|
26360
|
+
...registration,
|
|
26361
|
+
version: expectedVersion + 1
|
|
26362
|
+
};
|
|
26363
|
+
const values = identityRegistrationValues(next);
|
|
26364
|
+
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 });
|
|
26365
|
+
return rows.length === 1;
|
|
26366
|
+
}
|
|
26367
|
+
});
|
|
25172
26368
|
var createPostgresAgentRegistrationStore = (db) => ({
|
|
25173
26369
|
findByAgentId: async (agentId) => {
|
|
25174
26370
|
const [row] = await db.select().from(agentRegistrationsTable).where(eq(agentRegistrationsTable.agent_id, agentId)).limit(1);
|
|
@@ -25402,7 +26598,7 @@ var createPostgresApiKeyStore = (db) => ({
|
|
|
25402
26598
|
}
|
|
25403
26599
|
});
|
|
25404
26600
|
// src/oidc/clientIdMetadata.ts
|
|
25405
|
-
var
|
|
26601
|
+
var secureUrl2 = (value) => {
|
|
25406
26602
|
try {
|
|
25407
26603
|
return new URL(value).protocol === "https:";
|
|
25408
26604
|
} catch {
|
|
@@ -25413,7 +26609,7 @@ var validateClientIdMetadataDocument = (document, expectedClientId) => {
|
|
|
25413
26609
|
const errors = [];
|
|
25414
26610
|
if (document.client_id !== expectedClientId)
|
|
25415
26611
|
errors.push("client_id does not match the metadata document URL");
|
|
25416
|
-
if (!
|
|
26612
|
+
if (!secureUrl2(document.client_id))
|
|
25417
26613
|
errors.push("client_id must use HTTPS");
|
|
25418
26614
|
if (!Array.isArray(document.redirect_uris) || document.redirect_uris.length === 0)
|
|
25419
26615
|
errors.push("redirect_uris is required");
|
|
@@ -25433,7 +26629,7 @@ var validateClientIdMetadataDocument = (document, expectedClientId) => {
|
|
|
25433
26629
|
["tos_uri", document.tos_uri],
|
|
25434
26630
|
["jwks_uri", document.jwks_uri]
|
|
25435
26631
|
]) {
|
|
25436
|
-
if (value !== undefined && !
|
|
26632
|
+
if (value !== undefined && !secureUrl2(value))
|
|
25437
26633
|
errors.push(`${name2} must use HTTPS`);
|
|
25438
26634
|
}
|
|
25439
26635
|
return errors;
|
|
@@ -25456,7 +26652,7 @@ var createClientIdMetadataResolver = ({
|
|
|
25456
26652
|
}) => {
|
|
25457
26653
|
const cache = new Map;
|
|
25458
26654
|
return async (clientId) => {
|
|
25459
|
-
if (!
|
|
26655
|
+
if (!secureUrl2(clientId) || !await allow(clientId))
|
|
25460
26656
|
return;
|
|
25461
26657
|
const cached = cache.get(clientId);
|
|
25462
26658
|
if (cached !== undefined && cached.expiresAt > now())
|
|
@@ -27368,10 +28564,20 @@ var mfaTotpLockoutMigration = {
|
|
|
27368
28564
|
};
|
|
27369
28565
|
var blockMigrations = {
|
|
27370
28566
|
adaptive: initMigration("adaptive", [knownDevicesTable, loginHistoryTable]),
|
|
27371
|
-
agents:
|
|
27372
|
-
|
|
27373
|
-
|
|
27374
|
-
|
|
28567
|
+
agents: {
|
|
28568
|
+
block: "agents",
|
|
28569
|
+
migrations: [
|
|
28570
|
+
...initMigration("agents", [
|
|
28571
|
+
agentRegistrationsTable,
|
|
28572
|
+
agentDelegationsTable,
|
|
28573
|
+
agentIdentityRegistrationsTable
|
|
28574
|
+
]).migrations,
|
|
28575
|
+
{
|
|
28576
|
+
id: "0002_identity_registration",
|
|
28577
|
+
sql: tablesToInitSql([agentIdentityRegistrationsTable])
|
|
28578
|
+
}
|
|
28579
|
+
]
|
|
28580
|
+
},
|
|
27375
28581
|
apikeys: initMigration("apikeys", [
|
|
27376
28582
|
accessTokensTable,
|
|
27377
28583
|
apiClientsTable,
|
|
@@ -27893,6 +29099,38 @@ var auth = async ({
|
|
|
27893
29099
|
onRevocationError,
|
|
27894
29100
|
onSessionCleanup
|
|
27895
29101
|
}) => {
|
|
29102
|
+
if (agentAuth?.agentRegistration !== undefined) {
|
|
29103
|
+
if (oidc === undefined) {
|
|
29104
|
+
throw new Error("agentAuth.agentRegistration requires the OIDC provider");
|
|
29105
|
+
}
|
|
29106
|
+
if (agentAuth.authorizationServer !== oidc.issuer) {
|
|
29107
|
+
throw new Error("agentAuth.authorizationServer must equal oidc.issuer");
|
|
29108
|
+
}
|
|
29109
|
+
const registration2 = agentAuth.agentRegistration;
|
|
29110
|
+
const unknownScopes = [
|
|
29111
|
+
...registration2.preClaimScopes ?? [],
|
|
29112
|
+
...registration2.postClaimScopes
|
|
29113
|
+
].filter((scope) => !agentAuth.scopes.includes(scope));
|
|
29114
|
+
if (unknownScopes.length > 0) {
|
|
29115
|
+
throw new Error(`Agent registration uses undeclared scopes: ${[...new Set(unknownScopes)].join(", ")}`);
|
|
29116
|
+
}
|
|
29117
|
+
if (registration2.allowAnonymous === true && registration2.revokeAccessTokens === undefined) {
|
|
29118
|
+
throw new Error("Anonymous agent registration requires revokeAccessTokens");
|
|
29119
|
+
}
|
|
29120
|
+
for (const [name2, value] of Object.entries({
|
|
29121
|
+
assertionTtlMs: registration2.assertionTtlMs,
|
|
29122
|
+
attemptTtlMs: registration2.attemptTtlMs,
|
|
29123
|
+
claimTtlMs: registration2.claimTtlMs,
|
|
29124
|
+
maxAuthenticationAgeMs: registration2.maxAuthenticationAgeMs,
|
|
29125
|
+
maxCodeAttempts: registration2.maxCodeAttempts,
|
|
29126
|
+
pollIntervalSeconds: registration2.pollIntervalSeconds,
|
|
29127
|
+
tokenTtlMs: registration2.tokenTtlMs
|
|
29128
|
+
})) {
|
|
29129
|
+
if (value !== undefined && (!Number.isFinite(value) || value <= 0)) {
|
|
29130
|
+
throw new Error(`agentAuth.agentRegistration.${name2} must be positive`);
|
|
29131
|
+
}
|
|
29132
|
+
}
|
|
29133
|
+
}
|
|
27896
29134
|
if (tracing !== undefined)
|
|
27897
29135
|
await initTracing(tracing);
|
|
27898
29136
|
const clientProviders = await buildClientProviders(providersConfiguration, createOAuth2Client, customProviders);
|
|
@@ -27906,8 +29144,34 @@ var auth = async ({
|
|
|
27906
29144
|
}
|
|
27907
29145
|
}) : undefined;
|
|
27908
29146
|
const lockoutGuard = lockout ? createLockoutGuard(lockout) : undefined;
|
|
29147
|
+
const resolvedAgentAuth = agentAuth === undefined ? undefined : {
|
|
29148
|
+
...agentAuth,
|
|
29149
|
+
oidcRoute: agentAuth.oidcRoute ?? oidc?.oidcRoute
|
|
29150
|
+
};
|
|
27909
29151
|
const oidcConfig = oidc ? {
|
|
27910
29152
|
...oidc,
|
|
29153
|
+
additionalDiscoveryMetadata: {
|
|
29154
|
+
...oidc.additionalDiscoveryMetadata,
|
|
29155
|
+
...resolvedAgentAuth?.agentRegistration === undefined ? {} : {
|
|
29156
|
+
agent_auth: agentRegistrationDiscoveryMetadata(resolvedAgentAuth)
|
|
29157
|
+
}
|
|
29158
|
+
},
|
|
29159
|
+
publicTokenGrantTypes: [
|
|
29160
|
+
...oidc.publicTokenGrantTypes ?? [],
|
|
29161
|
+
...resolvedAgentAuth?.agentRegistration === undefined ? [] : [
|
|
29162
|
+
AGENT_CLAIM_GRANT_TYPE,
|
|
29163
|
+
AGENT_IDENTITY_ASSERTION_GRANT_TYPE
|
|
29164
|
+
]
|
|
29165
|
+
],
|
|
29166
|
+
handlePublicTokenGrant: async (context) => {
|
|
29167
|
+
const consumerResult = await oidc.handlePublicTokenGrant?.(context);
|
|
29168
|
+
if (consumerResult !== undefined)
|
|
29169
|
+
return consumerResult;
|
|
29170
|
+
if (resolvedAgentAuth?.agentRegistration === undefined) {
|
|
29171
|
+
return;
|
|
29172
|
+
}
|
|
29173
|
+
return handleAgentTokenGrant(resolvedAgentAuth, context.body);
|
|
29174
|
+
},
|
|
27911
29175
|
onClientRegistered: async (context) => {
|
|
27912
29176
|
await oidc.onClientRegistered?.(context);
|
|
27913
29177
|
if (agentAuth?.registerDynamicClients !== true)
|
|
@@ -27932,28 +29196,28 @@ var auth = async ({
|
|
|
27932
29196
|
await oidc.onDeviceAuthorizationApproved?.(context);
|
|
27933
29197
|
if (agentAuth === undefined)
|
|
27934
29198
|
return;
|
|
27935
|
-
const
|
|
27936
|
-
if (
|
|
29199
|
+
const registration2 = await agentAuth.registrationStore.findByClientId(context.clientId);
|
|
29200
|
+
if (registration2 === undefined || registration2.status !== "active") {
|
|
27937
29201
|
return;
|
|
27938
29202
|
}
|
|
27939
29203
|
const now = Date.now();
|
|
27940
29204
|
const existing = await agentAuth.delegationStore.findActiveDelegation({
|
|
27941
|
-
agentId:
|
|
29205
|
+
agentId: registration2.agentId,
|
|
27942
29206
|
now,
|
|
27943
29207
|
userId: context.userSub
|
|
27944
29208
|
});
|
|
27945
29209
|
await agentAuth.delegationStore.saveDelegation({
|
|
27946
|
-
agentId:
|
|
29210
|
+
agentId: registration2.agentId,
|
|
27947
29211
|
createdAt: existing?.createdAt ?? now,
|
|
27948
29212
|
delegationId: existing?.delegationId ?? `agd_${crypto.randomUUID()}`,
|
|
27949
|
-
scopes: context.scopes.filter((scope) =>
|
|
29213
|
+
scopes: context.scopes.filter((scope) => registration2.allowedScopes.includes(scope) && agentAuth.scopes.includes(scope)),
|
|
27950
29214
|
status: "active",
|
|
27951
29215
|
updatedAt: now,
|
|
27952
29216
|
userId: context.userSub
|
|
27953
29217
|
});
|
|
27954
29218
|
await auditEmit?.({
|
|
27955
29219
|
at: now,
|
|
27956
|
-
metadata: { agentId:
|
|
29220
|
+
metadata: { agentId: registration2.agentId },
|
|
27957
29221
|
type: "agent_delegated",
|
|
27958
29222
|
userId: context.userSub
|
|
27959
29223
|
});
|
|
@@ -28066,7 +29330,7 @@ var auth = async ({
|
|
|
28066
29330
|
...htmx,
|
|
28067
29331
|
authSessionStore
|
|
28068
29332
|
}) : new Elysia42);
|
|
28069
|
-
const authWithAgent = composedAuth.use(agentAuthPlugin(
|
|
29333
|
+
const authWithAgent = composedAuth.use(agentAuthPlugin(resolvedAgentAuth));
|
|
28070
29334
|
return authWithAgent;
|
|
28071
29335
|
};
|
|
28072
29336
|
export {
|
|
@@ -28118,6 +29382,7 @@ export {
|
|
|
28118
29382
|
stepUpPlugin,
|
|
28119
29383
|
statusListRoutes,
|
|
28120
29384
|
startImpersonation,
|
|
29385
|
+
startAgentRegistration,
|
|
28121
29386
|
ssoDiscoveryRoute,
|
|
28122
29387
|
ssoConnectionsTable,
|
|
28123
29388
|
signWebhook,
|
|
@@ -28144,6 +29409,7 @@ export {
|
|
|
28144
29409
|
roleRoutes,
|
|
28145
29410
|
revokeUserSessions,
|
|
28146
29411
|
revokeRefreshToken,
|
|
29412
|
+
revokeAgentIdentityRegistration,
|
|
28147
29413
|
revocableProviderOptions,
|
|
28148
29414
|
resolveSetupSession,
|
|
28149
29415
|
resolveScimOrganization,
|
|
@@ -28223,6 +29489,8 @@ export {
|
|
|
28223
29489
|
issueDeviceAuthorization,
|
|
28224
29490
|
issueCredential,
|
|
28225
29491
|
issueBackchannelAuth,
|
|
29492
|
+
issueAgentServiceAssertion,
|
|
29493
|
+
issueAgentIdentityAssertion,
|
|
28226
29494
|
isValidUser,
|
|
28227
29495
|
isValidProviderOption,
|
|
28228
29496
|
isUserSessionId,
|
|
@@ -28251,6 +29519,7 @@ export {
|
|
|
28251
29519
|
hashAuditEvent,
|
|
28252
29520
|
hasScopes,
|
|
28253
29521
|
hasOrganizationScope,
|
|
29522
|
+
handleAgentTokenGrant,
|
|
28254
29523
|
getUserSessionId,
|
|
28255
29524
|
getStatus,
|
|
28256
29525
|
getRegisteredClient,
|
|
@@ -28262,6 +29531,7 @@ export {
|
|
|
28262
29531
|
generateSecureToken,
|
|
28263
29532
|
generateEncryptionKey,
|
|
28264
29533
|
generateBackupCodes,
|
|
29534
|
+
generateAgentRegistrationGuide,
|
|
28265
29535
|
fromBase64Url2 as fromBase64Url,
|
|
28266
29536
|
fingerprintDevice,
|
|
28267
29537
|
fetchUserInfo,
|
|
@@ -28279,6 +29549,7 @@ export {
|
|
|
28279
29549
|
endImpersonation,
|
|
28280
29550
|
encryptTotpSecret,
|
|
28281
29551
|
encryptSecret,
|
|
29552
|
+
discoverAgentRegistration,
|
|
28282
29553
|
diffScimGroupMembers,
|
|
28283
29554
|
denyDeviceAuthorization,
|
|
28284
29555
|
denyBackchannelAuth,
|
|
@@ -28349,6 +29620,7 @@ export {
|
|
|
28349
29620
|
createPostgresApiKeyStore,
|
|
28350
29621
|
createPostgresApiClientStore,
|
|
28351
29622
|
createPostgresAgentRegistrationStore,
|
|
29623
|
+
createPostgresAgentIdentityRegistrationStore,
|
|
28352
29624
|
createPostgresAgentDelegationStore,
|
|
28353
29625
|
createPostgresAccessTokenStore,
|
|
28354
29626
|
createOrganization,
|
|
@@ -28391,6 +29663,7 @@ export {
|
|
|
28391
29663
|
createNeonApiKeyStore,
|
|
28392
29664
|
createNeonApiClientStore,
|
|
28393
29665
|
createNeonAgentRegistrationStore,
|
|
29666
|
+
createNeonAgentIdentityRegistrationStore,
|
|
28394
29667
|
createNeonAgentDelegationStore,
|
|
28395
29668
|
createNeonAccessTokenStore,
|
|
28396
29669
|
createMfaGate,
|
|
@@ -28433,6 +29706,8 @@ export {
|
|
|
28433
29706
|
createInMemoryApiKeyStore,
|
|
28434
29707
|
createInMemoryApiClientStore,
|
|
28435
29708
|
createInMemoryAgentRegistrationStore,
|
|
29709
|
+
createInMemoryAgentIdentityRegistrationStore,
|
|
29710
|
+
createInMemoryAgentIdentityAssertionJtiStore,
|
|
28436
29711
|
createInMemoryAgentDelegationStore,
|
|
28437
29712
|
createInMemoryAccessTokenStore,
|
|
28438
29713
|
createFgaEngine,
|
|
@@ -28446,6 +29721,9 @@ export {
|
|
|
28446
29721
|
createApiKey,
|
|
28447
29722
|
createApiClient,
|
|
28448
29723
|
createAnonymousSession,
|
|
29724
|
+
createAgentRegistrationCredentialVerifier,
|
|
29725
|
+
createAgentRegistrationClient,
|
|
29726
|
+
createAgentIdentityAssertionVerifier,
|
|
28449
29727
|
createActionPipeline,
|
|
28450
29728
|
createAbuseGuard,
|
|
28451
29729
|
consumePushedRequest,
|
|
@@ -28453,6 +29731,7 @@ export {
|
|
|
28453
29731
|
constantTimeEqual,
|
|
28454
29732
|
computeCertThumbprint,
|
|
28455
29733
|
complianceRoutes,
|
|
29734
|
+
completeAgentClaim,
|
|
28456
29735
|
clientIdMetadataToOAuthClient,
|
|
28457
29736
|
check2 as check,
|
|
28458
29737
|
buildStatusClaim,
|
|
@@ -28460,6 +29739,7 @@ export {
|
|
|
28460
29739
|
buildHolderKeyBindingJwt,
|
|
28461
29740
|
buildClientProviders,
|
|
28462
29741
|
blockMigrations,
|
|
29742
|
+
beginAgentClaim,
|
|
28463
29743
|
base32Encode,
|
|
28464
29744
|
base32Decode,
|
|
28465
29745
|
authProviderOption,
|
|
@@ -28475,7 +29755,10 @@ export {
|
|
|
28475
29755
|
apiKeysRoutes,
|
|
28476
29756
|
apiClientsTable,
|
|
28477
29757
|
agentRegistrationsTable,
|
|
29758
|
+
agentRegistrationEndpoints,
|
|
29759
|
+
agentRegistrationDiscoveryMetadata,
|
|
28478
29760
|
agentProtectedResourceMetadata,
|
|
29761
|
+
agentIdentityRegistrationsTable,
|
|
28479
29762
|
agentHasScopes,
|
|
28480
29763
|
agentDelegationsTable,
|
|
28481
29764
|
agentAuthPlugin,
|
|
@@ -28526,8 +29809,11 @@ export {
|
|
|
28526
29809
|
DEFAULT_AGENT_RESOURCE_METADATA_ROUTE,
|
|
28527
29810
|
CLIENT_ASSERTION_TYPE,
|
|
28528
29811
|
CIBA_GRANT_TYPE,
|
|
28529
|
-
AuthIdentityConflictError
|
|
29812
|
+
AuthIdentityConflictError,
|
|
29813
|
+
AGENT_IDENTITY_ASSERTION_TYPE,
|
|
29814
|
+
AGENT_IDENTITY_ASSERTION_GRANT_TYPE,
|
|
29815
|
+
AGENT_CLAIM_GRANT_TYPE
|
|
28530
29816
|
};
|
|
28531
29817
|
|
|
28532
|
-
//# debugId=
|
|
29818
|
+
//# debugId=8E4D1AD9D7CAB11764756E2164756E21
|
|
28533
29819
|
//# sourceMappingURL=index.js.map
|