@absolutejs/auth 0.54.9 → 0.55.1
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 +26 -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 +1427 -95
- 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 +144 -1
- package/dist/agents/types.d.ts +59 -0
- package/dist/apikeys/routes.d.ts +1 -1
- package/dist/authInstance.d.ts +12 -0
- package/dist/authorization/protectPermission.d.ts +2 -2
- package/dist/cli/migrate.js +57 -8
- package/dist/cli/migrate.js.map +4 -4
- package/dist/index.d.ts +14 -131
- package/dist/index.js +1535 -238
- package/dist/index.js.map +16 -13
- package/dist/manifest.js +12 -6
- package/dist/manifest.js.map +4 -4
- package/dist/manifest.json +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 +8 -6
- package/dist/routes/protectRoute.d.ts +2 -2
- package/dist/server.d.ts +18 -0
- package/dist/server.js +29362 -0
- package/dist/server.js.map +287 -0
- package/dist/utils.d.ts +1 -1
- 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 +14 -6
package/dist/index.js
CHANGED
|
@@ -3112,11 +3112,684 @@ 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
|
|
3783
|
+
var DELETE_CODE_POINT = 127;
|
|
3784
|
+
var HTTP_BAD_REQUEST2 = 400;
|
|
3785
|
+
var HTTP_FORBIDDEN = 403;
|
|
3786
|
+
var HTTP_OK2 = 200;
|
|
3787
|
+
var HTTP_UNAUTHORIZED2 = 401;
|
|
3788
|
+
var MINIMUM_PRINTABLE_CODE_POINT = 32;
|
|
3116
3789
|
var quoteHeaderValue = (value) => {
|
|
3117
3790
|
const printable = [...value].filter((character) => {
|
|
3118
3791
|
const codePoint = character.codePointAt(0) ?? 0;
|
|
3119
|
-
return codePoint >=
|
|
3792
|
+
return codePoint >= MINIMUM_PRINTABLE_CODE_POINT && codePoint !== DELETE_CODE_POINT;
|
|
3120
3793
|
}).join("");
|
|
3121
3794
|
return `"${printable.replace(/[\\"]/g, "\\$&")}"`;
|
|
3122
3795
|
};
|
|
@@ -3149,39 +3822,170 @@ var failureResponse = (config, failure, requiredScopes) => new Response(JSON.str
|
|
|
3149
3822
|
requiredScopes
|
|
3150
3823
|
})
|
|
3151
3824
|
},
|
|
3152
|
-
status: failure.code === "Forbidden" ?
|
|
3825
|
+
status: failure.code === "Forbidden" ? HTTP_FORBIDDEN : HTTP_UNAUTHORIZED2
|
|
3153
3826
|
});
|
|
3154
|
-
var
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3827
|
+
var json = (value, status = HTTP_OK2) => new Response(JSON.stringify(value), {
|
|
3828
|
+
headers: {
|
|
3829
|
+
"cache-control": "no-store",
|
|
3830
|
+
"content-type": "application/json"
|
|
3831
|
+
},
|
|
3832
|
+
status
|
|
3833
|
+
});
|
|
3834
|
+
var recordBody = (body) => {
|
|
3835
|
+
if (typeof body !== "object" || body === null || Array.isArray(body)) {
|
|
3836
|
+
return;
|
|
3837
|
+
}
|
|
3838
|
+
return Object.fromEntries(Object.entries(body));
|
|
3839
|
+
};
|
|
3840
|
+
var registrationResponse = (result) => {
|
|
3841
|
+
const body = {
|
|
3842
|
+
...result.assertionExpires > 0 ? {
|
|
3843
|
+
assertion_expires: new Date(result.assertionExpires).toISOString()
|
|
3844
|
+
} : {},
|
|
3845
|
+
...result.claim === undefined ? {} : { claim: result.claim },
|
|
3846
|
+
...result.claimToken === undefined ? {} : { claim_token: result.claimToken },
|
|
3847
|
+
...result.claimTokenExpires === undefined ? {} : {
|
|
3848
|
+
claim_token_expires: new Date(result.claimTokenExpires).toISOString()
|
|
3849
|
+
},
|
|
3850
|
+
...result.identityAssertion === undefined ? {} : { identity_assertion: result.identityAssertion },
|
|
3851
|
+
...result.preClaimScopes === undefined ? {} : { pre_claim_scopes: result.preClaimScopes },
|
|
3852
|
+
post_claim_scopes: result.postClaimScopes,
|
|
3853
|
+
registration_id: result.registrationId,
|
|
3854
|
+
registration_type: result.registrationType
|
|
3855
|
+
};
|
|
3856
|
+
if (result.registrationType === "identity_assertion" && result.identityAssertion === undefined) {
|
|
3857
|
+
return json({
|
|
3858
|
+
...body,
|
|
3859
|
+
error: "interaction_required",
|
|
3860
|
+
error_description: "Authenticate at the service and confirm the account link."
|
|
3861
|
+
}, HTTP_UNAUTHORIZED2);
|
|
3862
|
+
}
|
|
3863
|
+
return json(body);
|
|
3864
|
+
};
|
|
3865
|
+
var parseRegistrationInput = (value) => {
|
|
3866
|
+
if (value.type === "anonymous") {
|
|
3867
|
+
const input2 = { type: "anonymous" };
|
|
3868
|
+
return input2;
|
|
3869
|
+
}
|
|
3870
|
+
if (value.type === "service_auth" && typeof value.login_hint === "string") {
|
|
3871
|
+
const input2 = {
|
|
3872
|
+
loginHint: value.login_hint,
|
|
3873
|
+
type: "service_auth"
|
|
3874
|
+
};
|
|
3875
|
+
return input2;
|
|
3876
|
+
}
|
|
3877
|
+
if (value.type !== "identity_assertion" || value.assertion_type !== AGENT_IDENTITY_ASSERTION_TYPE || typeof value.assertion !== "string") {
|
|
3878
|
+
return;
|
|
3879
|
+
}
|
|
3880
|
+
const input = {
|
|
3881
|
+
assertion: value.assertion,
|
|
3882
|
+
assertionType: AGENT_IDENTITY_ASSERTION_TYPE,
|
|
3883
|
+
type: "identity_assertion"
|
|
3884
|
+
};
|
|
3885
|
+
return input;
|
|
3886
|
+
};
|
|
3887
|
+
var escapeHtml = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """);
|
|
3888
|
+
var agentAuthContextPlugin = (config) => new Elysia2().derive(({ request }) => ({
|
|
3889
|
+
protectAgent: async (requiredScopes, handleAuth, handleAuthFail) => {
|
|
3890
|
+
if (config === undefined) {
|
|
3891
|
+
const failure = {
|
|
3892
|
+
code: "Unauthorized",
|
|
3893
|
+
message: "Agent is not authenticated"
|
|
3894
|
+
};
|
|
3895
|
+
return await handleAuthFail?.(failure) ?? new Response(failure.message, { status: 401 });
|
|
3180
3896
|
}
|
|
3181
|
-
|
|
3897
|
+
const principal = await resolveAgentPrincipal(request, config);
|
|
3898
|
+
if (principal === undefined) {
|
|
3899
|
+
const failure = {
|
|
3900
|
+
code: "Unauthorized",
|
|
3901
|
+
message: "Agent is not authenticated"
|
|
3902
|
+
};
|
|
3903
|
+
return await handleAuthFail?.(failure) ?? failureResponse(config, failure, requiredScopes);
|
|
3904
|
+
}
|
|
3905
|
+
if (!agentHasScopes(principal, requiredScopes)) {
|
|
3906
|
+
const failure = {
|
|
3907
|
+
code: "Forbidden",
|
|
3908
|
+
message: "Insufficient agent scopes"
|
|
3909
|
+
};
|
|
3910
|
+
return await handleAuthFail?.(failure) ?? failureResponse(config, failure, requiredScopes);
|
|
3911
|
+
}
|
|
3912
|
+
return handleAuth(principal);
|
|
3913
|
+
}
|
|
3914
|
+
}));
|
|
3915
|
+
var agentAuthPlugin = (config) => {
|
|
3916
|
+
const plugin = agentAuthContextPlugin(config);
|
|
3182
3917
|
if (config === undefined)
|
|
3183
3918
|
return plugin.as("global");
|
|
3184
|
-
|
|
3919
|
+
if (config.agentRegistration === undefined) {
|
|
3920
|
+
return plugin.get(config.metadataRoute ?? DEFAULT_AGENT_RESOURCE_METADATA_ROUTE, () => agentProtectedResourceMetadata(config)).as("global");
|
|
3921
|
+
}
|
|
3922
|
+
const registration = config.agentRegistration;
|
|
3923
|
+
const identityRoute = registration.identityRoute ?? "/agent/identity";
|
|
3924
|
+
const claimRoute = registration.claimRoute ?? "/agent/identity/claim";
|
|
3925
|
+
const completeRoute = registration.completeRoute ?? "/agent/identity/claim/complete";
|
|
3926
|
+
const guideRoute = registration.guideRoute ?? "/auth.md";
|
|
3927
|
+
return plugin.get(config.metadataRoute ?? DEFAULT_AGENT_RESOURCE_METADATA_ROUTE, () => agentProtectedResourceMetadata(config)).get(guideRoute, () => new Response(generateAgentRegistrationGuide(config), {
|
|
3928
|
+
headers: {
|
|
3929
|
+
"cache-control": "public, max-age=300",
|
|
3930
|
+
"content-type": "text/markdown; charset=utf-8"
|
|
3931
|
+
}
|
|
3932
|
+
})).get(completeRoute, ({ query }) => {
|
|
3933
|
+
const token = typeof query.claim_attempt_token === "string" ? query.claim_attempt_token : "";
|
|
3934
|
+
if (token.length === 0)
|
|
3935
|
+
return new Response("Invalid claim link", { status: 400 });
|
|
3936
|
+
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>`, {
|
|
3937
|
+
headers: {
|
|
3938
|
+
"cache-control": "no-store",
|
|
3939
|
+
"content-security-policy": "default-src 'none'; form-action 'self'; style-src 'none'; base-uri 'none'; frame-ancestors 'none'",
|
|
3940
|
+
"content-type": "text/html; charset=utf-8",
|
|
3941
|
+
"x-content-type-options": "nosniff"
|
|
3942
|
+
}
|
|
3943
|
+
});
|
|
3944
|
+
}).post(identityRoute, async ({ body }) => {
|
|
3945
|
+
const value = recordBody(body);
|
|
3946
|
+
if (value === undefined || typeof value.type !== "string") {
|
|
3947
|
+
return json({ error: "invalid_request" }, HTTP_BAD_REQUEST2);
|
|
3948
|
+
}
|
|
3949
|
+
const input = parseRegistrationInput(value);
|
|
3950
|
+
if (input === undefined)
|
|
3951
|
+
return json({ error: "invalid_request" }, HTTP_BAD_REQUEST2);
|
|
3952
|
+
const result = await startAgentRegistration(config, input);
|
|
3953
|
+
if ("error" in result) {
|
|
3954
|
+
return json({
|
|
3955
|
+
error: result.error,
|
|
3956
|
+
...result.message === undefined ? {} : { error_description: result.message }
|
|
3957
|
+
}, result.status);
|
|
3958
|
+
}
|
|
3959
|
+
return registrationResponse(result);
|
|
3960
|
+
}).post(claimRoute, async ({ body }) => {
|
|
3961
|
+
const value = recordBody(body);
|
|
3962
|
+
if (value === undefined || typeof value.claim_token !== "string" || typeof value.email !== "string") {
|
|
3963
|
+
return json({ error: "invalid_request" }, HTTP_BAD_REQUEST2);
|
|
3964
|
+
}
|
|
3965
|
+
const result = await beginAgentClaim(config, {
|
|
3966
|
+
claimToken: value.claim_token,
|
|
3967
|
+
email: value.email
|
|
3968
|
+
});
|
|
3969
|
+
if ("error" in result)
|
|
3970
|
+
return json({ error: result.error }, result.status);
|
|
3971
|
+
return json({ claim_attempt: result.claimAttempt });
|
|
3972
|
+
}).post(completeRoute, async ({ body, request }) => {
|
|
3973
|
+
let value = recordBody(body);
|
|
3974
|
+
if (value === undefined && typeof body === "string") {
|
|
3975
|
+
value = Object.fromEntries(new URLSearchParams(body));
|
|
3976
|
+
}
|
|
3977
|
+
if (value === undefined || typeof value.claim_attempt_token !== "string" || typeof value.user_code !== "string") {
|
|
3978
|
+
return json({ error: "invalid_request" }, HTTP_BAD_REQUEST2);
|
|
3979
|
+
}
|
|
3980
|
+
const result = await completeAgentClaim(config, {
|
|
3981
|
+
attemptToken: value.claim_attempt_token,
|
|
3982
|
+
request,
|
|
3983
|
+
userCode: value.user_code
|
|
3984
|
+
});
|
|
3985
|
+
if ("error" in result)
|
|
3986
|
+
return json({ error: result.error }, result.status);
|
|
3987
|
+
return new Response(null, { status: 204 });
|
|
3988
|
+
}).as("global");
|
|
3185
3989
|
};
|
|
3186
3990
|
|
|
3187
3991
|
// src/audit/config.ts
|
|
@@ -3484,6 +4288,11 @@ import { Elysia as Elysia5, t as t4 } from "elysia";
|
|
|
3484
4288
|
|
|
3485
4289
|
// src/utils.ts
|
|
3486
4290
|
init_constants();
|
|
4291
|
+
var MILLISECONDS_IN_A_SECOND2 = 1000;
|
|
4292
|
+
var readOptionalString = (value, property) => {
|
|
4293
|
+
const candidate = Reflect.get(value, property);
|
|
4294
|
+
return typeof candidate === "string" ? candidate : undefined;
|
|
4295
|
+
};
|
|
3487
4296
|
var defineAuthConfig = (configuration) => configuration;
|
|
3488
4297
|
var defineAuthHtmxConfig = (htmxConfig) => htmxConfig;
|
|
3489
4298
|
var defineAuthSettings = (settings) => settings;
|
|
@@ -3579,14 +4388,10 @@ var resolveOAuthAuthorization = async ({
|
|
|
3579
4388
|
let userIdentity;
|
|
3580
4389
|
let accessToken = tokenResponse.access_token;
|
|
3581
4390
|
let refreshToken = tokenResponse.refresh_token;
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
if (typeof nestedAccessToken === "string") {
|
|
3587
|
-
accessToken = nestedAccessToken;
|
|
3588
|
-
}
|
|
3589
|
-
}
|
|
4391
|
+
const withingsBody = Reflect.get(tokenResponse, "body");
|
|
4392
|
+
const withingsAccessToken = authProvider === "withings" && withingsBody !== null && typeof withingsBody === "object" ? readOptionalString(withingsBody, "access_token") : undefined;
|
|
4393
|
+
if (!accessToken && withingsAccessToken !== undefined) {
|
|
4394
|
+
accessToken = withingsAccessToken;
|
|
3590
4395
|
}
|
|
3591
4396
|
if (typeof accessToken !== "string" || accessToken.length === 0) {
|
|
3592
4397
|
throw new Error("OAuth authorization response contains no access_token");
|
|
@@ -3604,9 +4409,12 @@ var resolveOAuthAuthorization = async ({
|
|
|
3604
4409
|
source: "tokenResponse"
|
|
3605
4410
|
});
|
|
3606
4411
|
} else if (authProvider === "withings") {
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
|
|
4412
|
+
if (withingsBody === null || typeof withingsBody !== "object") {
|
|
4413
|
+
throw new Error("Withings OAuth response contains no body");
|
|
4414
|
+
}
|
|
4415
|
+
userIdentity = { userid: Reflect.get(withingsBody, "userid") };
|
|
4416
|
+
accessToken = readOptionalString(withingsBody, "access_token") ?? accessToken;
|
|
4417
|
+
refreshToken = readOptionalString(withingsBody, "refresh_token") ?? refreshToken;
|
|
3610
4418
|
} else {
|
|
3611
4419
|
userIdentity = normalizeProviderIdentity({
|
|
3612
4420
|
identity: await providerInstance.fetchUserProfile(accessToken),
|
|
@@ -3638,7 +4446,7 @@ var resolveOAuthTokenExpiresAt = (tokenResponse, now = Date.now()) => {
|
|
|
3638
4446
|
if (!Number.isFinite(expiresInSeconds) || expiresInSeconds <= 0) {
|
|
3639
4447
|
return;
|
|
3640
4448
|
}
|
|
3641
|
-
return now + expiresInSeconds *
|
|
4449
|
+
return now + expiresInSeconds * MILLISECONDS_IN_A_SECOND2;
|
|
3642
4450
|
};
|
|
3643
4451
|
var validateSession = ({
|
|
3644
4452
|
user_session_id,
|
|
@@ -4381,7 +5189,7 @@ var protectRoutePlugin = ({
|
|
|
4381
5189
|
})).as("global");
|
|
4382
5190
|
|
|
4383
5191
|
// src/htmx/renderers.ts
|
|
4384
|
-
var
|
|
5192
|
+
var escapeHtml2 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """);
|
|
4385
5193
|
var defaultAuthorizationHref = (provider, client) => client ? `/oauth2/${provider}/authorization?client=${client}` : `/oauth2/${provider}/authorization`;
|
|
4386
5194
|
var resolveAuthHtmxRenderers = (config) => {
|
|
4387
5195
|
const { connectorTargets, featuredLoginProviders, providerData } = config;
|
|
@@ -4394,10 +5202,10 @@ var resolveAuthHtmxRenderers = (config) => {
|
|
|
4394
5202
|
return `<div class="grid-2">
|
|
4395
5203
|
<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
5204
|
<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>${
|
|
5205
|
+
<div class="spread"><span class="muted">Subject</span><span>${escapeHtml2(user.sub)}</span></div>
|
|
5206
|
+
<div class="spread"><span class="muted">Name</span><span>${escapeHtml2(fullName || "\u2014")}</span></div>
|
|
5207
|
+
<div class="spread"><span class="muted">Email</span><span>${escapeHtml2(user.email ?? "\u2014")}</span></div>
|
|
5208
|
+
<div class="spread"><span class="muted">Primary identity</span><span>${escapeHtml2(user.primary_auth_identity_id ?? "\u2014")}</span></div>
|
|
4401
5209
|
</div>
|
|
4402
5210
|
</div>`;
|
|
4403
5211
|
};
|
|
@@ -4406,18 +5214,18 @@ var resolveAuthHtmxRenderers = (config) => {
|
|
|
4406
5214
|
return `<a class="btn btn--primary btn--sm" href="/htmx">Sign in</a>`;
|
|
4407
5215
|
}
|
|
4408
5216
|
const label = user.email ?? user.first_name ?? "Account";
|
|
4409
|
-
return `<span class="muted">${
|
|
5217
|
+
return `<span class="muted">${escapeHtml2(label)}</span><a class="btn btn--ghost btn--sm" href="/htmx/signout">Sign out</a>`;
|
|
4410
5218
|
};
|
|
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)}" />${
|
|
5219
|
+
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
5220
|
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">${
|
|
5221
|
+
const scopeList = (scopes) => scopes.map((scope) => `<span class="scope">${escapeHtml2(scope)}</span>`).join("");
|
|
5222
|
+
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>`;
|
|
5223
|
+
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
5224
|
return `<h3 class="provider-heading">External accounts</h3>${bindings}<h3 class="provider-heading">Grants</h3>${grants}`;
|
|
4417
5225
|
};
|
|
4418
5226
|
const identities = (payload, query) => {
|
|
4419
5227
|
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">${
|
|
5228
|
+
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
5229
|
<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
5230
|
<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
5231
|
</div></div>`).join("")}</div></div>`;
|
|
@@ -4426,19 +5234,19 @@ var resolveAuthHtmxRenderers = (config) => {
|
|
|
4426
5234
|
identities: list.filter((identity) => term === "" || providerLabel(provider).toLowerCase().includes(term) || identity.id.toLowerCase().includes(term) || identity.provider_subject.toLowerCase().includes(term)),
|
|
4427
5235
|
provider
|
|
4428
5236
|
})).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)}" />` : ""}${
|
|
5237
|
+
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
5238
|
${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
5239
|
<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
5240
|
</div></div>`).join("")}</div></div>`).join("");
|
|
4433
5241
|
return `${mergesHtml}${groupsHtml}`;
|
|
4434
5242
|
};
|
|
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">${
|
|
5243
|
+
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
5244
|
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">${
|
|
5245
|
+
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
5246
|
if (!includeDropdown) {
|
|
4439
5247
|
return `<div class="oauth-grid">${featured}</div>`;
|
|
4440
5248
|
}
|
|
4441
|
-
const options = providerOptions.map((provider) => `<option value="${provider}">${
|
|
5249
|
+
const options = providerOptions.map((provider) => `<option value="${provider}">${escapeHtml2(providerLabel(provider))}</option>`).join("");
|
|
4442
5250
|
return `<div class="oauth-grid">${featured}
|
|
4443
5251
|
<div class="separator"><span class="separator__line"></span><span class="separator__text">or any provider</span><span class="separator__line"></span></div>
|
|
4444
5252
|
<form class="oauth-grid" action="/htmx/login-redirect" method="get">
|
|
@@ -4454,7 +5262,7 @@ var resolveAuthHtmxRenderers = (config) => {
|
|
|
4454
5262
|
authMenu: overrides.authMenu ?? authMenu,
|
|
4455
5263
|
connectorLinks: overrides.connectorLinks ?? connectorLinks,
|
|
4456
5264
|
connectors: overrides.connectors ?? connectors,
|
|
4457
|
-
escapeHtml,
|
|
5265
|
+
escapeHtml: escapeHtml2,
|
|
4458
5266
|
identities: overrides.identities ?? identities,
|
|
4459
5267
|
protected: overrides.protected ?? protectedView,
|
|
4460
5268
|
providerLogin: overrides.providerLogin ?? providerLogin
|
|
@@ -5071,75 +5879,17 @@ import { Elysia as Elysia18, t as t14 } from "elysia";
|
|
|
5071
5879
|
// src/oidc/config.ts
|
|
5072
5880
|
init_constants();
|
|
5073
5881
|
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
5882
|
var DEFAULT_OIDC_ROUTE = "/oauth2";
|
|
5133
5883
|
var MS_PER_SECOND = 1000;
|
|
5134
|
-
var
|
|
5884
|
+
var TOKEN_BYTES3 = 32;
|
|
5135
5885
|
var REFRESH_TTL_DAYS = 30;
|
|
5136
|
-
var
|
|
5886
|
+
var DEFAULT_ACCESS_TOKEN_TTL_MS3 = MILLISECONDS_IN_AN_HOUR;
|
|
5137
5887
|
var DEFAULT_ID_TOKEN_TTL_MS = MILLISECONDS_IN_AN_HOUR;
|
|
5138
5888
|
var DEFAULT_REFRESH_TOKEN_TTL_MS = MILLISECONDS_IN_A_DAY * REFRESH_TTL_DAYS;
|
|
5139
5889
|
var resolveAccessTtl = (ttl, scopes) => {
|
|
5140
5890
|
if (typeof ttl === "function")
|
|
5141
5891
|
return ttl({ scopes });
|
|
5142
|
-
return ttl ??
|
|
5892
|
+
return ttl ?? DEFAULT_ACCESS_TOKEN_TTL_MS3;
|
|
5143
5893
|
};
|
|
5144
5894
|
var nowSeconds = (milliseconds) => Math.floor(milliseconds / MS_PER_SECOND);
|
|
5145
5895
|
var narrowScopes = (available, requested) => requested === undefined || requested.length === 0 ? available : requested.filter((scope) => available.includes(scope));
|
|
@@ -5280,7 +6030,7 @@ var issueTokenSet = async ({
|
|
|
5280
6030
|
idPayload.nonce = nonce;
|
|
5281
6031
|
if (acr !== undefined)
|
|
5282
6032
|
idPayload.acr = acr;
|
|
5283
|
-
const refreshToken = generateSecureToken(
|
|
6033
|
+
const refreshToken = generateSecureToken(TOKEN_BYTES3);
|
|
5284
6034
|
await config.refreshTokenStore.saveToken({
|
|
5285
6035
|
acr,
|
|
5286
6036
|
claims,
|
|
@@ -6706,15 +7456,15 @@ var updateRegisteredClient = async ({
|
|
|
6706
7456
|
};
|
|
6707
7457
|
|
|
6708
7458
|
// src/oidc/routes.ts
|
|
6709
|
-
var
|
|
7459
|
+
var HTTP_OK3 = 200;
|
|
6710
7460
|
var HTTP_NO_CONTENT = 204;
|
|
6711
7461
|
var HTTP_FOUND = 302;
|
|
6712
|
-
var
|
|
6713
|
-
var
|
|
7462
|
+
var HTTP_BAD_REQUEST3 = 400;
|
|
7463
|
+
var HTTP_UNAUTHORIZED3 = 401;
|
|
6714
7464
|
var HTTP_NOT_IMPLEMENTED = 501;
|
|
6715
7465
|
var CODE_TTL_MINUTES = 10;
|
|
6716
7466
|
var CODE_TTL_MS = MILLISECONDS_IN_A_MINUTE * CODE_TTL_MINUTES;
|
|
6717
|
-
var
|
|
7467
|
+
var TOKEN_BYTES4 = 32;
|
|
6718
7468
|
var BASIC_PREFIX2 = "Basic ";
|
|
6719
7469
|
var jsonResponse = (value, status) => new Response(JSON.stringify(value), {
|
|
6720
7470
|
headers: {
|
|
@@ -6724,7 +7474,7 @@ var jsonResponse = (value, status) => new Response(JSON.stringify(value), {
|
|
|
6724
7474
|
status
|
|
6725
7475
|
});
|
|
6726
7476
|
var oauthError2 = (status, error) => jsonResponse({ error }, status);
|
|
6727
|
-
var tokenResponse = (tokens) => jsonResponse(tokens,
|
|
7477
|
+
var tokenResponse = (tokens) => jsonResponse(tokens, HTTP_OK3);
|
|
6728
7478
|
var canonicalizeRequestUrl = (requestUrl, issuer) => {
|
|
6729
7479
|
try {
|
|
6730
7480
|
const url = new URL(requestUrl);
|
|
@@ -6746,7 +7496,7 @@ var formPostResponse = (redirectUri, params) => {
|
|
|
6746
7496
|
"cache-control": "no-store",
|
|
6747
7497
|
"content-type": "text/html;charset=UTF-8"
|
|
6748
7498
|
},
|
|
6749
|
-
status:
|
|
7499
|
+
status: HTTP_OK3
|
|
6750
7500
|
});
|
|
6751
7501
|
};
|
|
6752
7502
|
var respondToClient = (redirectUri, responseMode, params) => {
|
|
@@ -6797,7 +7547,7 @@ var oidcProviderRoutes = (config) => {
|
|
|
6797
7547
|
const userinfoRoute = `${oidcRoute}/userinfo`;
|
|
6798
7548
|
const registrationBaseUrl = `${issuer}${registrationRoute}`;
|
|
6799
7549
|
const tokenUrl = `${issuer}${oidcRoute}/token`;
|
|
6800
|
-
const resolveClient = async (clientId) => await clientStore.findClient(clientId) ??
|
|
7550
|
+
const resolveClient = async (clientId) => await clientStore.findClient(clientId) ?? config.resolveClientIdMetadata?.(clientId);
|
|
6801
7551
|
const authenticateClient = async (clientId, clientSecret) => {
|
|
6802
7552
|
const client = await resolveClient(clientId);
|
|
6803
7553
|
if (client === undefined)
|
|
@@ -6884,7 +7634,7 @@ var oidcProviderRoutes = (config) => {
|
|
|
6884
7634
|
"dpop-nonce": fresh,
|
|
6885
7635
|
"www-authenticate": 'DPoP error="use_dpop_nonce"'
|
|
6886
7636
|
},
|
|
6887
|
-
status:
|
|
7637
|
+
status: HTTP_UNAUTHORIZED3
|
|
6888
7638
|
});
|
|
6889
7639
|
};
|
|
6890
7640
|
const grantAuthorizationCode = async (client, body, dpop, clientCertThumbprint) => {
|
|
@@ -6894,11 +7644,11 @@ var oidcProviderRoutes = (config) => {
|
|
|
6894
7644
|
redirect_uri: redirectUri
|
|
6895
7645
|
} = body;
|
|
6896
7646
|
if (code === undefined || codeVerifier === undefined || redirectUri === undefined) {
|
|
6897
|
-
return oauthError2(
|
|
7647
|
+
return oauthError2(HTTP_BAD_REQUEST3, "invalid_request");
|
|
6898
7648
|
}
|
|
6899
7649
|
const record = await authorizationCodeStore.consumeCode(await hashToken(code));
|
|
6900
7650
|
if (record === undefined || record.expiresAt < Date.now() || record.clientId !== client.clientId || record.redirectUri !== redirectUri || !await verifyPkce(codeVerifier, record.codeChallenge)) {
|
|
6901
|
-
return oauthError2(
|
|
7651
|
+
return oauthError2(HTTP_BAD_REQUEST3, "invalid_grant");
|
|
6902
7652
|
}
|
|
6903
7653
|
const dpopResult = dpop === undefined ? undefined : await verifyDpopProof({
|
|
6904
7654
|
htm: "POST",
|
|
@@ -6906,7 +7656,7 @@ var oidcProviderRoutes = (config) => {
|
|
|
6906
7656
|
proof: dpop
|
|
6907
7657
|
});
|
|
6908
7658
|
if (dpop !== undefined && dpopResult === undefined) {
|
|
6909
|
-
return oauthError2(
|
|
7659
|
+
return oauthError2(HTTP_BAD_REQUEST3, "invalid_dpop_proof");
|
|
6910
7660
|
}
|
|
6911
7661
|
return tokenResponse(await issueTokenSet({
|
|
6912
7662
|
acr: record.acr,
|
|
@@ -6923,11 +7673,11 @@ var oidcProviderRoutes = (config) => {
|
|
|
6923
7673
|
const grantRefreshToken = async (client, body, dpop, clientCertThumbprint) => {
|
|
6924
7674
|
const presented = body.refresh_token;
|
|
6925
7675
|
if (presented === undefined) {
|
|
6926
|
-
return oauthError2(
|
|
7676
|
+
return oauthError2(HTTP_BAD_REQUEST3, "invalid_request");
|
|
6927
7677
|
}
|
|
6928
7678
|
const record = await refreshTokenStore.consumeToken(await hashToken(presented));
|
|
6929
7679
|
if (record === undefined || record.expiresAt < Date.now() || record.clientId !== client.clientId) {
|
|
6930
|
-
return oauthError2(
|
|
7680
|
+
return oauthError2(HTTP_BAD_REQUEST3, "invalid_grant");
|
|
6931
7681
|
}
|
|
6932
7682
|
if (record.dpopJkt !== undefined) {
|
|
6933
7683
|
const proof = await verifyDpopProof({
|
|
@@ -6936,7 +7686,7 @@ var oidcProviderRoutes = (config) => {
|
|
|
6936
7686
|
proof: dpop
|
|
6937
7687
|
});
|
|
6938
7688
|
if (proof === undefined || proof.jkt !== record.dpopJkt) {
|
|
6939
|
-
return oauthError2(
|
|
7689
|
+
return oauthError2(HTTP_BAD_REQUEST3, "invalid_dpop_proof");
|
|
6940
7690
|
}
|
|
6941
7691
|
}
|
|
6942
7692
|
return tokenResponse(await issueTokenSet({
|
|
@@ -6952,7 +7702,7 @@ var oidcProviderRoutes = (config) => {
|
|
|
6952
7702
|
};
|
|
6953
7703
|
const grantTokenExchange = async (client, body, dpop) => {
|
|
6954
7704
|
if (body.subject_token === undefined) {
|
|
6955
|
-
return oauthError2(
|
|
7705
|
+
return oauthError2(HTTP_BAD_REQUEST3, "invalid_request");
|
|
6956
7706
|
}
|
|
6957
7707
|
const dpopResult = dpop === undefined ? undefined : await verifyDpopProof({
|
|
6958
7708
|
htm: "POST",
|
|
@@ -6960,7 +7710,7 @@ var oidcProviderRoutes = (config) => {
|
|
|
6960
7710
|
proof: dpop
|
|
6961
7711
|
});
|
|
6962
7712
|
if (dpop !== undefined && dpopResult === undefined) {
|
|
6963
|
-
return oauthError2(
|
|
7713
|
+
return oauthError2(HTTP_BAD_REQUEST3, "invalid_dpop_proof");
|
|
6964
7714
|
}
|
|
6965
7715
|
const result = await exchangeToken({
|
|
6966
7716
|
actorClientId: client.clientId,
|
|
@@ -6971,21 +7721,21 @@ var oidcProviderRoutes = (config) => {
|
|
|
6971
7721
|
subjectToken: body.subject_token
|
|
6972
7722
|
});
|
|
6973
7723
|
if (!result.ok)
|
|
6974
|
-
return oauthError2(
|
|
7724
|
+
return oauthError2(HTTP_BAD_REQUEST3, result.error);
|
|
6975
7725
|
return jsonResponse({
|
|
6976
7726
|
access_token: result.accessToken,
|
|
6977
7727
|
expires_in: result.expiresIn,
|
|
6978
7728
|
issued_token_type: "urn:ietf:params:oauth:token-type:access_token",
|
|
6979
7729
|
scope: result.scope,
|
|
6980
7730
|
token_type: dpopResult === undefined ? "Bearer" : "DPoP"
|
|
6981
|
-
},
|
|
7731
|
+
}, HTTP_OK3);
|
|
6982
7732
|
};
|
|
6983
7733
|
const grantBackchannel = async (client, body, dpop, clientCertThumbprint) => {
|
|
6984
7734
|
if (config.backchannelAuthStore === undefined) {
|
|
6985
|
-
return oauthError2(
|
|
7735
|
+
return oauthError2(HTTP_BAD_REQUEST3, "unsupported_grant_type");
|
|
6986
7736
|
}
|
|
6987
7737
|
if (body.auth_req_id === undefined) {
|
|
6988
|
-
return oauthError2(
|
|
7738
|
+
return oauthError2(HTTP_BAD_REQUEST3, "invalid_request");
|
|
6989
7739
|
}
|
|
6990
7740
|
const dpopResult = dpop === undefined ? undefined : await verifyDpopProof({
|
|
6991
7741
|
htm: "POST",
|
|
@@ -6993,7 +7743,7 @@ var oidcProviderRoutes = (config) => {
|
|
|
6993
7743
|
proof: dpop
|
|
6994
7744
|
});
|
|
6995
7745
|
if (dpop !== undefined && dpopResult === undefined) {
|
|
6996
|
-
return oauthError2(
|
|
7746
|
+
return oauthError2(HTTP_BAD_REQUEST3, "invalid_dpop_proof");
|
|
6997
7747
|
}
|
|
6998
7748
|
const result = await exchangeBackchannelAuth({
|
|
6999
7749
|
authReqId: body.auth_req_id,
|
|
@@ -7003,7 +7753,7 @@ var oidcProviderRoutes = (config) => {
|
|
|
7003
7753
|
dpopJkt: dpopResult?.jkt
|
|
7004
7754
|
});
|
|
7005
7755
|
if (!result.ok)
|
|
7006
|
-
return oauthError2(
|
|
7756
|
+
return oauthError2(HTTP_BAD_REQUEST3, result.error);
|
|
7007
7757
|
return jsonResponse({
|
|
7008
7758
|
access_token: result.access_token,
|
|
7009
7759
|
expires_in: result.expires_in,
|
|
@@ -7011,14 +7761,14 @@ var oidcProviderRoutes = (config) => {
|
|
|
7011
7761
|
refresh_token: result.refresh_token,
|
|
7012
7762
|
scope: result.scope,
|
|
7013
7763
|
token_type: dpopResult === undefined ? "Bearer" : "DPoP"
|
|
7014
|
-
},
|
|
7764
|
+
}, HTTP_OK3);
|
|
7015
7765
|
};
|
|
7016
7766
|
const grantDeviceCode = async (client, body, dpop) => {
|
|
7017
7767
|
if (config.deviceAuthorizationStore === undefined) {
|
|
7018
|
-
return oauthError2(
|
|
7768
|
+
return oauthError2(HTTP_BAD_REQUEST3, "unsupported_grant_type");
|
|
7019
7769
|
}
|
|
7020
7770
|
if (body.device_code === undefined) {
|
|
7021
|
-
return oauthError2(
|
|
7771
|
+
return oauthError2(HTTP_BAD_REQUEST3, "invalid_request");
|
|
7022
7772
|
}
|
|
7023
7773
|
const dpopResult = dpop === undefined ? undefined : await verifyDpopProof({
|
|
7024
7774
|
htm: "POST",
|
|
@@ -7026,7 +7776,7 @@ var oidcProviderRoutes = (config) => {
|
|
|
7026
7776
|
proof: dpop
|
|
7027
7777
|
});
|
|
7028
7778
|
if (dpop !== undefined && dpopResult === undefined) {
|
|
7029
|
-
return oauthError2(
|
|
7779
|
+
return oauthError2(HTTP_BAD_REQUEST3, "invalid_dpop_proof");
|
|
7030
7780
|
}
|
|
7031
7781
|
const result = await exchangeDeviceCode({
|
|
7032
7782
|
clientId: client.clientId,
|
|
@@ -7035,7 +7785,7 @@ var oidcProviderRoutes = (config) => {
|
|
|
7035
7785
|
dpopJkt: dpopResult?.jkt
|
|
7036
7786
|
});
|
|
7037
7787
|
if (!result.ok)
|
|
7038
|
-
return oauthError2(
|
|
7788
|
+
return oauthError2(HTTP_BAD_REQUEST3, result.error);
|
|
7039
7789
|
return jsonResponse({
|
|
7040
7790
|
access_token: result.access_token,
|
|
7041
7791
|
expires_in: result.expires_in,
|
|
@@ -7043,7 +7793,7 @@ var oidcProviderRoutes = (config) => {
|
|
|
7043
7793
|
refresh_token: result.refresh_token,
|
|
7044
7794
|
scope: result.scope,
|
|
7045
7795
|
token_type: dpopResult === undefined ? "Bearer" : "DPoP"
|
|
7046
|
-
},
|
|
7796
|
+
}, HTTP_OK3);
|
|
7047
7797
|
};
|
|
7048
7798
|
const grantTypes = [
|
|
7049
7799
|
"authorization_code",
|
|
@@ -7059,13 +7809,17 @@ var oidcProviderRoutes = (config) => {
|
|
|
7059
7809
|
if (config.vciConfig !== undefined) {
|
|
7060
7810
|
grantTypes.push(PRE_AUTHORIZED_CODE_GRANT);
|
|
7061
7811
|
}
|
|
7812
|
+
for (const grantType of config.publicTokenGrantTypes ?? []) {
|
|
7813
|
+
if (!grantTypes.includes(grantType))
|
|
7814
|
+
grantTypes.push(grantType);
|
|
7815
|
+
}
|
|
7062
7816
|
const discovery = {
|
|
7063
7817
|
authorization_endpoint: `${issuer}${authorizeRoute}`,
|
|
7064
7818
|
authorization_response_iss_parameter_supported: true,
|
|
7065
7819
|
backchannel_logout_session_supported: false,
|
|
7066
7820
|
backchannel_logout_supported: true,
|
|
7067
|
-
code_challenge_methods_supported: ["S256"],
|
|
7068
7821
|
client_id_metadata_document_supported: config.resolveClientIdMetadata !== undefined,
|
|
7822
|
+
code_challenge_methods_supported: ["S256"],
|
|
7069
7823
|
dpop_signing_alg_values_supported: ["ES256"],
|
|
7070
7824
|
end_session_endpoint: `${issuer}${endSessionRoute}`,
|
|
7071
7825
|
grant_types_supported: grantTypes,
|
|
@@ -7113,6 +7867,11 @@ var oidcProviderRoutes = (config) => {
|
|
|
7113
7867
|
if (config.acrValuesSupported !== undefined && config.acrValuesSupported.length > 0) {
|
|
7114
7868
|
discovery.acr_values_supported = config.acrValuesSupported;
|
|
7115
7869
|
}
|
|
7870
|
+
const reservedDiscoveryKeys = new Set(Object.keys(discovery));
|
|
7871
|
+
for (const [key, value] of Object.entries(config.additionalDiscoveryMetadata ?? {})) {
|
|
7872
|
+
if (!reservedDiscoveryKeys.has(key))
|
|
7873
|
+
discovery[key] = value;
|
|
7874
|
+
}
|
|
7116
7875
|
const handleEndSession = async ({
|
|
7117
7876
|
cookie,
|
|
7118
7877
|
inMemorySession,
|
|
@@ -7147,7 +7906,7 @@ var oidcProviderRoutes = (config) => {
|
|
|
7147
7906
|
requestedUri: query.post_logout_redirect_uri
|
|
7148
7907
|
});
|
|
7149
7908
|
if (redirectUri === undefined) {
|
|
7150
|
-
return jsonResponse({ ok: true },
|
|
7909
|
+
return jsonResponse({ ok: true }, HTTP_OK3);
|
|
7151
7910
|
}
|
|
7152
7911
|
const url = new URL(redirectUri);
|
|
7153
7912
|
if (query.state !== undefined)
|
|
@@ -7168,11 +7927,11 @@ var oidcProviderRoutes = (config) => {
|
|
|
7168
7927
|
store: config.pushedAuthorizationRequestStore
|
|
7169
7928
|
});
|
|
7170
7929
|
if (pushed === undefined) {
|
|
7171
|
-
return jsonResponse({ error: "invalid_request_uri" },
|
|
7930
|
+
return jsonResponse({ error: "invalid_request_uri" }, HTTP_BAD_REQUEST3);
|
|
7172
7931
|
}
|
|
7173
7932
|
effectiveQuery = pushed;
|
|
7174
7933
|
} else if (query.request_uri !== undefined && query.request_uri.startsWith(REQUEST_URI_PREFIX)) {
|
|
7175
|
-
return jsonResponse({ error: "invalid_request_uri" },
|
|
7934
|
+
return jsonResponse({ error: "invalid_request_uri" }, HTTP_BAD_REQUEST3);
|
|
7176
7935
|
}
|
|
7177
7936
|
const initialClientId = effectiveQuery.client_id;
|
|
7178
7937
|
const initialClient = initialClientId === undefined ? undefined : await resolveClient(initialClientId);
|
|
@@ -7183,7 +7942,7 @@ var oidcProviderRoutes = (config) => {
|
|
|
7183
7942
|
jwt: effectiveQuery.request
|
|
7184
7943
|
});
|
|
7185
7944
|
if (!parsed.ok) {
|
|
7186
|
-
return jsonResponse({ error: parsed.error },
|
|
7945
|
+
return jsonResponse({ error: parsed.error }, HTTP_BAD_REQUEST3);
|
|
7187
7946
|
}
|
|
7188
7947
|
effectiveQuery = {
|
|
7189
7948
|
...parsed.params,
|
|
@@ -7203,11 +7962,11 @@ var oidcProviderRoutes = (config) => {
|
|
|
7203
7962
|
} = effectiveQuery;
|
|
7204
7963
|
const client = initialClient;
|
|
7205
7964
|
if (client === undefined || clientId !== client.clientId || redirectUri === undefined || !client.redirectUris.includes(redirectUri)) {
|
|
7206
|
-
return jsonResponse({ error: "invalid_client" },
|
|
7965
|
+
return jsonResponse({ error: "invalid_client" }, HTTP_BAD_REQUEST3);
|
|
7207
7966
|
}
|
|
7208
7967
|
const responseMode = requestedResponseMode === "form_post" ? "form_post" : "query";
|
|
7209
7968
|
if (requestedResponseMode !== undefined && requestedResponseMode !== "query" && requestedResponseMode !== "form_post") {
|
|
7210
|
-
return jsonResponse({ error: "unsupported_response_mode" },
|
|
7969
|
+
return jsonResponse({ error: "unsupported_response_mode" }, HTTP_BAD_REQUEST3);
|
|
7211
7970
|
}
|
|
7212
7971
|
const errorRedirect = (error) => {
|
|
7213
7972
|
const params2 = {
|
|
@@ -7251,7 +8010,7 @@ var oidcProviderRoutes = (config) => {
|
|
|
7251
8010
|
if (wantsSilent) {
|
|
7252
8011
|
return errorRedirect(userSession === undefined ? "login_required" : "interaction_required");
|
|
7253
8012
|
}
|
|
7254
|
-
return loginUrl === undefined ? jsonResponse({ error: "login_required" },
|
|
8013
|
+
return loginUrl === undefined ? jsonResponse({ error: "login_required" }, HTTP_UNAUTHORIZED3) : redirectTo(`${loginUrl}?return_to=${encodeURIComponent(canonicalizeRequestUrl(request.url, issuer))}`);
|
|
7255
8014
|
}
|
|
7256
8015
|
const requested = scope === undefined || scope.length === 0 ? client.scopes : scope.split(" ").filter((entry) => client.scopes.includes(entry));
|
|
7257
8016
|
if (config.consentUrl !== undefined && await config.needsConsent?.({
|
|
@@ -7287,7 +8046,7 @@ var oidcProviderRoutes = (config) => {
|
|
|
7287
8046
|
if (requestedAcr !== undefined && (userAcr === undefined || !requestedAcr.includes(userAcr))) {
|
|
7288
8047
|
return errorRedirect("insufficient_user_authentication");
|
|
7289
8048
|
}
|
|
7290
|
-
const code = generateSecureToken(
|
|
8049
|
+
const code = generateSecureToken(TOKEN_BYTES4);
|
|
7291
8050
|
await authorizationCodeStore.saveCode({
|
|
7292
8051
|
acr: userAcr,
|
|
7293
8052
|
claims: getClaims?.(userSession.user),
|
|
@@ -7334,7 +8093,7 @@ var oidcProviderRoutes = (config) => {
|
|
|
7334
8093
|
if (body.grant_type === PRE_AUTHORIZED_CODE_GRANT && config.vciConfig !== undefined) {
|
|
7335
8094
|
const preAuthorizedCode = body["pre-authorized_code"];
|
|
7336
8095
|
if (typeof preAuthorizedCode !== "string") {
|
|
7337
|
-
return oauthError2(
|
|
8096
|
+
return oauthError2(HTTP_BAD_REQUEST3, "invalid_request");
|
|
7338
8097
|
}
|
|
7339
8098
|
const result = await exchangePreAuthorizedCode({
|
|
7340
8099
|
config: config.vciConfig,
|
|
@@ -7343,14 +8102,23 @@ var oidcProviderRoutes = (config) => {
|
|
|
7343
8102
|
signingKey: config.vciConfig.signingKey ?? config.signingKey
|
|
7344
8103
|
});
|
|
7345
8104
|
if (!result.ok)
|
|
7346
|
-
return oauthError2(
|
|
8105
|
+
return oauthError2(HTTP_BAD_REQUEST3, result.error);
|
|
7347
8106
|
return jsonResponse({
|
|
7348
8107
|
access_token: result.access_token,
|
|
7349
8108
|
c_nonce: result.c_nonce,
|
|
7350
8109
|
c_nonce_expires_in: result.c_nonce_expires_in,
|
|
7351
8110
|
expires_in: result.expires_in,
|
|
7352
8111
|
token_type: result.token_type
|
|
7353
|
-
},
|
|
8112
|
+
}, HTTP_OK3);
|
|
8113
|
+
}
|
|
8114
|
+
if (typeof body.grant_type === "string" && config.publicTokenGrantTypes?.includes(body.grant_type) === true && config.handlePublicTokenGrant !== undefined) {
|
|
8115
|
+
const result = await config.handlePublicTokenGrant({
|
|
8116
|
+
body,
|
|
8117
|
+
request
|
|
8118
|
+
});
|
|
8119
|
+
if (result !== undefined) {
|
|
8120
|
+
return jsonResponse(result.body, result.status);
|
|
8121
|
+
}
|
|
7354
8122
|
}
|
|
7355
8123
|
const basic = readBasicAuth2(headers.authorization);
|
|
7356
8124
|
const auth = await authenticateTokenClient({
|
|
@@ -7363,7 +8131,7 @@ var oidcProviderRoutes = (config) => {
|
|
|
7363
8131
|
requestHeaders: request.headers
|
|
7364
8132
|
});
|
|
7365
8133
|
if (auth === undefined) {
|
|
7366
|
-
return oauthError2(
|
|
8134
|
+
return oauthError2(HTTP_UNAUTHORIZED3, "invalid_client");
|
|
7367
8135
|
}
|
|
7368
8136
|
const { client, clientCertThumbprint } = auth;
|
|
7369
8137
|
const nonceChallenge = await dpopNonceChallenge(headers.dpop);
|
|
@@ -7384,11 +8152,13 @@ var oidcProviderRoutes = (config) => {
|
|
|
7384
8152
|
if (body.grant_type === CIBA_GRANT_TYPE) {
|
|
7385
8153
|
return grantBackchannel(client, body, headers.dpop, clientCertThumbprint);
|
|
7386
8154
|
}
|
|
7387
|
-
return oauthError2(
|
|
8155
|
+
return oauthError2(HTTP_BAD_REQUEST3, "unsupported_grant_type");
|
|
7388
8156
|
}, {
|
|
7389
8157
|
body: t14.Object({
|
|
8158
|
+
assertion: t14.Optional(t14.String()),
|
|
7390
8159
|
audience: t14.Optional(t14.String()),
|
|
7391
8160
|
auth_req_id: t14.Optional(t14.String()),
|
|
8161
|
+
claim_token: t14.Optional(t14.String()),
|
|
7392
8162
|
client_assertion: t14.Optional(t14.String()),
|
|
7393
8163
|
client_assertion_type: t14.Optional(t14.String()),
|
|
7394
8164
|
client_id: t14.Optional(t14.String()),
|
|
@@ -7420,7 +8190,7 @@ var oidcProviderRoutes = (config) => {
|
|
|
7420
8190
|
requestHeaders: request.headers
|
|
7421
8191
|
});
|
|
7422
8192
|
if (auth === undefined) {
|
|
7423
|
-
return oauthError2(
|
|
8193
|
+
return oauthError2(HTTP_UNAUTHORIZED3, "invalid_client");
|
|
7424
8194
|
}
|
|
7425
8195
|
const { client } = auth;
|
|
7426
8196
|
const isAuthField = (key) => key === "client_assertion" || key === "client_assertion_type" || key === "client_secret";
|
|
@@ -7431,7 +8201,7 @@ var oidcProviderRoutes = (config) => {
|
|
|
7431
8201
|
store: config.pushedAuthorizationRequestStore,
|
|
7432
8202
|
ttlMs: config.pushedAuthorizationRequestTtlMs
|
|
7433
8203
|
});
|
|
7434
|
-
return jsonResponse(result.body, result.ok ?
|
|
8204
|
+
return jsonResponse(result.body, result.ok ? HTTP_OK3 : result.status);
|
|
7435
8205
|
}, {
|
|
7436
8206
|
body: t14.Object({
|
|
7437
8207
|
acr_values: t14.Optional(t14.String()),
|
|
@@ -7458,11 +8228,11 @@ var oidcProviderRoutes = (config) => {
|
|
|
7458
8228
|
const clientId = body.client_id ?? basic.clientId;
|
|
7459
8229
|
const clientSecret = body.client_secret ?? basic.clientSecret;
|
|
7460
8230
|
if (clientId === undefined) {
|
|
7461
|
-
return oauthError2(
|
|
8231
|
+
return oauthError2(HTTP_UNAUTHORIZED3, "invalid_client");
|
|
7462
8232
|
}
|
|
7463
8233
|
const client = await authenticateClient(clientId, clientSecret);
|
|
7464
8234
|
if (client === undefined) {
|
|
7465
|
-
return oauthError2(
|
|
8235
|
+
return oauthError2(HTTP_UNAUTHORIZED3, "invalid_client");
|
|
7466
8236
|
}
|
|
7467
8237
|
const result = await introspectToken({
|
|
7468
8238
|
config,
|
|
@@ -7470,7 +8240,7 @@ var oidcProviderRoutes = (config) => {
|
|
|
7470
8240
|
now: Date.now(),
|
|
7471
8241
|
token: body.token
|
|
7472
8242
|
});
|
|
7473
|
-
return jsonResponse(result,
|
|
8243
|
+
return jsonResponse(result, HTTP_OK3);
|
|
7474
8244
|
}, {
|
|
7475
8245
|
body: t14.Object({
|
|
7476
8246
|
client_id: t14.Optional(t14.String()),
|
|
@@ -7486,16 +8256,16 @@ var oidcProviderRoutes = (config) => {
|
|
|
7486
8256
|
const clientId = body.client_id ?? basic.clientId;
|
|
7487
8257
|
const clientSecret = body.client_secret ?? basic.clientSecret;
|
|
7488
8258
|
if (clientId === undefined) {
|
|
7489
|
-
return oauthError2(
|
|
8259
|
+
return oauthError2(HTTP_UNAUTHORIZED3, "invalid_client");
|
|
7490
8260
|
}
|
|
7491
8261
|
const client = await authenticateClient(clientId, clientSecret);
|
|
7492
8262
|
if (client === undefined) {
|
|
7493
|
-
return oauthError2(
|
|
8263
|
+
return oauthError2(HTTP_UNAUTHORIZED3, "invalid_client");
|
|
7494
8264
|
}
|
|
7495
8265
|
if (body.token_type_hint !== "access_token") {
|
|
7496
8266
|
await revokeRefreshToken(config, body.token);
|
|
7497
8267
|
}
|
|
7498
|
-
return new Response(null, { status:
|
|
8268
|
+
return new Response(null, { status: HTTP_OK3 });
|
|
7499
8269
|
}, {
|
|
7500
8270
|
body: t14.Object({
|
|
7501
8271
|
client_id: t14.Optional(t14.String()),
|
|
@@ -7508,20 +8278,20 @@ var oidcProviderRoutes = (config) => {
|
|
|
7508
8278
|
})
|
|
7509
8279
|
}).post(backchannelAuthorizationRoute, async ({ body, headers }) => {
|
|
7510
8280
|
if (config.backchannelAuthStore === undefined) {
|
|
7511
|
-
return oauthError2(
|
|
8281
|
+
return oauthError2(HTTP_BAD_REQUEST3, "unsupported_grant_type");
|
|
7512
8282
|
}
|
|
7513
8283
|
const basic = readBasicAuth2(headers.authorization);
|
|
7514
8284
|
const clientId = body.client_id ?? basic.clientId;
|
|
7515
8285
|
const clientSecret = body.client_secret ?? basic.clientSecret;
|
|
7516
8286
|
if (clientId === undefined) {
|
|
7517
|
-
return oauthError2(
|
|
8287
|
+
return oauthError2(HTTP_UNAUTHORIZED3, "invalid_client");
|
|
7518
8288
|
}
|
|
7519
8289
|
const client = await authenticateClient(clientId, clientSecret);
|
|
7520
8290
|
if (client === undefined) {
|
|
7521
|
-
return oauthError2(
|
|
8291
|
+
return oauthError2(HTTP_UNAUTHORIZED3, "invalid_client");
|
|
7522
8292
|
}
|
|
7523
8293
|
if (body.login_hint === undefined) {
|
|
7524
|
-
return oauthError2(
|
|
8294
|
+
return oauthError2(HTTP_BAD_REQUEST3, "invalid_request");
|
|
7525
8295
|
}
|
|
7526
8296
|
const requested = body.scope === undefined || body.scope.length === 0 ? client.scopes : body.scope.split(" ").filter((entry) => client.scopes.includes(entry));
|
|
7527
8297
|
const result = await issueBackchannelAuth({
|
|
@@ -7533,12 +8303,12 @@ var oidcProviderRoutes = (config) => {
|
|
|
7533
8303
|
requestedScopes: requested
|
|
7534
8304
|
});
|
|
7535
8305
|
if (!result.ok)
|
|
7536
|
-
return oauthError2(
|
|
8306
|
+
return oauthError2(HTTP_BAD_REQUEST3, result.error);
|
|
7537
8307
|
return jsonResponse({
|
|
7538
8308
|
auth_req_id: result.auth_req_id,
|
|
7539
8309
|
expires_in: result.expires_in,
|
|
7540
8310
|
interval: result.interval
|
|
7541
|
-
},
|
|
8311
|
+
}, HTTP_OK3);
|
|
7542
8312
|
}, {
|
|
7543
8313
|
body: t14.Object({
|
|
7544
8314
|
binding_message: t14.Optional(t14.String()),
|
|
@@ -7552,17 +8322,17 @@ var oidcProviderRoutes = (config) => {
|
|
|
7552
8322
|
})
|
|
7553
8323
|
}).post(deviceAuthorizationRoute, async ({ body, headers }) => {
|
|
7554
8324
|
if (config.deviceAuthorizationStore === undefined) {
|
|
7555
|
-
return oauthError2(
|
|
8325
|
+
return oauthError2(HTTP_BAD_REQUEST3, "unsupported_grant_type");
|
|
7556
8326
|
}
|
|
7557
8327
|
const basic = readBasicAuth2(headers.authorization);
|
|
7558
8328
|
const clientId = body.client_id ?? basic.clientId;
|
|
7559
8329
|
const clientSecret = body.client_secret ?? basic.clientSecret;
|
|
7560
8330
|
if (clientId === undefined) {
|
|
7561
|
-
return oauthError2(
|
|
8331
|
+
return oauthError2(HTTP_UNAUTHORIZED3, "invalid_client");
|
|
7562
8332
|
}
|
|
7563
8333
|
const client = await authenticateClient(clientId, clientSecret);
|
|
7564
8334
|
if (client === undefined) {
|
|
7565
|
-
return oauthError2(
|
|
8335
|
+
return oauthError2(HTTP_UNAUTHORIZED3, "invalid_client");
|
|
7566
8336
|
}
|
|
7567
8337
|
const requested = body.scope === undefined || body.scope.length === 0 ? client.scopes : body.scope.split(" ").filter((entry) => client.scopes.includes(entry));
|
|
7568
8338
|
const response = await issueDeviceAuthorization({
|
|
@@ -7571,7 +8341,7 @@ var oidcProviderRoutes = (config) => {
|
|
|
7571
8341
|
now: Date.now(),
|
|
7572
8342
|
requestedScopes: requested
|
|
7573
8343
|
});
|
|
7574
|
-
return jsonResponse(response,
|
|
8344
|
+
return jsonResponse(response, HTTP_OK3);
|
|
7575
8345
|
}, {
|
|
7576
8346
|
body: t14.Object({
|
|
7577
8347
|
client_id: t14.Optional(t14.String()),
|
|
@@ -7583,7 +8353,7 @@ var oidcProviderRoutes = (config) => {
|
|
|
7583
8353
|
})
|
|
7584
8354
|
}).post(deviceApproveRoute, async ({ body, cookie: { user_session_id }, store }) => {
|
|
7585
8355
|
if (config.deviceAuthorizationStore === undefined) {
|
|
7586
|
-
return oauthError2(
|
|
8356
|
+
return oauthError2(HTTP_BAD_REQUEST3, "unsupported_grant_type");
|
|
7587
8357
|
}
|
|
7588
8358
|
const userSession = await loadSessionFromSource({
|
|
7589
8359
|
authSessionStore,
|
|
@@ -7591,7 +8361,7 @@ var oidcProviderRoutes = (config) => {
|
|
|
7591
8361
|
userSessionId: user_session_id.value
|
|
7592
8362
|
});
|
|
7593
8363
|
if (userSession === undefined) {
|
|
7594
|
-
return oauthError2(
|
|
8364
|
+
return oauthError2(HTTP_UNAUTHORIZED3, "login_required");
|
|
7595
8365
|
}
|
|
7596
8366
|
const result = body.action === "deny" ? await denyDeviceAuthorization({
|
|
7597
8367
|
config,
|
|
@@ -7602,8 +8372,8 @@ var oidcProviderRoutes = (config) => {
|
|
|
7602
8372
|
userSub: getUserId(userSession.user)
|
|
7603
8373
|
});
|
|
7604
8374
|
if (!result.ok)
|
|
7605
|
-
return oauthError2(
|
|
7606
|
-
return jsonResponse({ ok: true },
|
|
8375
|
+
return oauthError2(HTTP_BAD_REQUEST3, result.error);
|
|
8376
|
+
return jsonResponse({ ok: true }, HTTP_OK3);
|
|
7607
8377
|
}, {
|
|
7608
8378
|
body: t14.Object({
|
|
7609
8379
|
action: t14.Optional(t14.Union([t14.Literal("approve"), t14.Literal("deny")])),
|
|
@@ -7655,7 +8425,7 @@ var oidcProviderRoutes = (config) => {
|
|
|
7655
8425
|
registrationBaseUrl,
|
|
7656
8426
|
registrationTokenStore: config.clientRegistrationTokenStore
|
|
7657
8427
|
});
|
|
7658
|
-
return jsonResponse(result.body, result.ok ?
|
|
8428
|
+
return jsonResponse(result.body, result.ok ? HTTP_OK3 : result.status);
|
|
7659
8429
|
}, {
|
|
7660
8430
|
body: t14.Object({
|
|
7661
8431
|
backchannel_logout_uri: t14.Optional(t14.String()),
|
|
@@ -7742,10 +8512,10 @@ var oidcProviderRoutes = (config) => {
|
|
|
7742
8512
|
"content-type": "application/json",
|
|
7743
8513
|
"www-authenticate": userInfoChallengeHeader(result.error)
|
|
7744
8514
|
},
|
|
7745
|
-
status:
|
|
8515
|
+
status: HTTP_UNAUTHORIZED3
|
|
7746
8516
|
});
|
|
7747
8517
|
}
|
|
7748
|
-
return jsonResponse(result.body,
|
|
8518
|
+
return jsonResponse(result.body, HTTP_OK3);
|
|
7749
8519
|
}, {
|
|
7750
8520
|
headers: t14.Object({
|
|
7751
8521
|
authorization: t14.Optional(t14.String())
|
|
@@ -7759,10 +8529,10 @@ var oidcProviderRoutes = (config) => {
|
|
|
7759
8529
|
"content-type": "application/json",
|
|
7760
8530
|
"www-authenticate": userInfoChallengeHeader(result.error)
|
|
7761
8531
|
},
|
|
7762
|
-
status:
|
|
8532
|
+
status: HTTP_UNAUTHORIZED3
|
|
7763
8533
|
});
|
|
7764
8534
|
}
|
|
7765
|
-
return jsonResponse(result.body,
|
|
8535
|
+
return jsonResponse(result.body, HTTP_OK3);
|
|
7766
8536
|
}, {
|
|
7767
8537
|
body: t14.Object({
|
|
7768
8538
|
access_token: t14.Optional(t14.String())
|
|
@@ -13742,7 +14512,7 @@ var PgJson = class extends PgColumn {
|
|
|
13742
14512
|
return "json";
|
|
13743
14513
|
}
|
|
13744
14514
|
};
|
|
13745
|
-
function
|
|
14515
|
+
function json2(name2) {
|
|
13746
14516
|
return new PgJsonBuilder(name2 ?? "");
|
|
13747
14517
|
}
|
|
13748
14518
|
|
|
@@ -14279,7 +15049,7 @@ function getPgColumnBuilders() {
|
|
|
14279
15049
|
inet,
|
|
14280
15050
|
integer,
|
|
14281
15051
|
interval,
|
|
14282
|
-
json,
|
|
15052
|
+
json: json2,
|
|
14283
15053
|
jsonb,
|
|
14284
15054
|
line,
|
|
14285
15055
|
macaddr,
|
|
@@ -14496,6 +15266,9 @@ var Index = class {
|
|
|
14496
15266
|
function index(name2) {
|
|
14497
15267
|
return new IndexBuilderOn(false, name2);
|
|
14498
15268
|
}
|
|
15269
|
+
function uniqueIndex(name2) {
|
|
15270
|
+
return new IndexBuilderOn(true, name2);
|
|
15271
|
+
}
|
|
14499
15272
|
// node_modules/drizzle-orm/pg-core/checks.js
|
|
14500
15273
|
var CheckBuilder = class {
|
|
14501
15274
|
static [entityKind] = "PgCheckBuilder";
|
|
@@ -24277,9 +25050,9 @@ var createInMemoryCredentialOfferStore = () => {
|
|
|
24277
25050
|
};
|
|
24278
25051
|
// src/oidc/vciRoutes.ts
|
|
24279
25052
|
import { Elysia as Elysia38, t as t33 } from "elysia";
|
|
24280
|
-
var
|
|
24281
|
-
var
|
|
24282
|
-
var
|
|
25053
|
+
var HTTP_OK4 = 200;
|
|
25054
|
+
var HTTP_BAD_REQUEST4 = 400;
|
|
25055
|
+
var HTTP_UNAUTHORIZED4 = 401;
|
|
24283
25056
|
var BEARER_PREFIX5 = "Bearer ";
|
|
24284
25057
|
var errorBody = (error, status) => new Response(JSON.stringify({ error }), {
|
|
24285
25058
|
headers: { "content-type": "application/json" },
|
|
@@ -24308,7 +25081,7 @@ var vciRoutes = ({
|
|
|
24308
25081
|
}))).post(credentialRoute, async ({ body, headers }) => {
|
|
24309
25082
|
const accessToken = extractBearer(headers.authorization);
|
|
24310
25083
|
if (accessToken === undefined) {
|
|
24311
|
-
return errorBody("invalid_token",
|
|
25084
|
+
return errorBody("invalid_token", HTTP_UNAUTHORIZED4);
|
|
24312
25085
|
}
|
|
24313
25086
|
const result = await issueCredential({
|
|
24314
25087
|
config: vciConfig,
|
|
@@ -24321,8 +25094,8 @@ var vciRoutes = ({
|
|
|
24321
25094
|
signingKey: vciSigningKey
|
|
24322
25095
|
});
|
|
24323
25096
|
if (!result.ok)
|
|
24324
|
-
return errorBody(result.error,
|
|
24325
|
-
return Response.json({ credential: result.credential, format: result.format }, { status:
|
|
25097
|
+
return errorBody(result.error, HTTP_BAD_REQUEST4);
|
|
25098
|
+
return Response.json({ credential: result.credential, format: result.format }, { status: HTTP_OK4 });
|
|
24326
25099
|
}, {
|
|
24327
25100
|
body: t33.Object({
|
|
24328
25101
|
format: t33.Optional(t33.Union([t33.Literal("vc+sd-jwt")])),
|
|
@@ -24333,7 +25106,7 @@ var vciRoutes = ({
|
|
|
24333
25106
|
})
|
|
24334
25107
|
}).post(nonceRoute, async () => {
|
|
24335
25108
|
if (vciConfig.credentialNonceStore === undefined) {
|
|
24336
|
-
return errorBody("not_supported",
|
|
25109
|
+
return errorBody("not_supported", HTTP_BAD_REQUEST4);
|
|
24337
25110
|
}
|
|
24338
25111
|
const { generateSecureToken: generateSecureToken2, hashToken: hashToken2 } = await Promise.resolve().then(() => (init_crypto(), exports_crypto));
|
|
24339
25112
|
const nonceBytes = 16;
|
|
@@ -24347,7 +25120,7 @@ var vciRoutes = ({
|
|
|
24347
25120
|
return Response.json({
|
|
24348
25121
|
c_nonce: nonce,
|
|
24349
25122
|
c_nonce_expires_in: Math.floor(ttlMs / msPerSecond)
|
|
24350
|
-
}, { status:
|
|
25123
|
+
}, { status: HTTP_OK4 });
|
|
24351
25124
|
});
|
|
24352
25125
|
};
|
|
24353
25126
|
// src/vc/statusList.ts
|
|
@@ -24451,7 +25224,7 @@ var verifyStatusListJwt = async ({
|
|
|
24451
25224
|
};
|
|
24452
25225
|
// src/vc/statusListRoutes.ts
|
|
24453
25226
|
import { Elysia as Elysia39, t as t34 } from "elysia";
|
|
24454
|
-
var
|
|
25227
|
+
var HTTP_OK5 = 200;
|
|
24455
25228
|
var HTTP_NOT_FOUND = 404;
|
|
24456
25229
|
var DEFAULT_STATUS_ROUTE = "/vc/status";
|
|
24457
25230
|
var statusListRoutes = ({
|
|
@@ -24476,7 +25249,7 @@ var statusListRoutes = ({
|
|
|
24476
25249
|
});
|
|
24477
25250
|
return new Response(jwt, {
|
|
24478
25251
|
headers: { "content-type": STATUS_LIST_SUB_TYP },
|
|
24479
|
-
status:
|
|
25252
|
+
status: HTTP_OK5
|
|
24480
25253
|
});
|
|
24481
25254
|
}, { params: t34.Object({ listId: t34.String() }) });
|
|
24482
25255
|
};
|
|
@@ -24684,8 +25457,8 @@ var createInMemoryPresentationRequestStore = () => {
|
|
|
24684
25457
|
};
|
|
24685
25458
|
// src/vc/vpRoutes.ts
|
|
24686
25459
|
import { Elysia as Elysia40, t as t35 } from "elysia";
|
|
24687
|
-
var
|
|
24688
|
-
var
|
|
25460
|
+
var HTTP_OK6 = 200;
|
|
25461
|
+
var HTTP_BAD_REQUEST5 = 400;
|
|
24689
25462
|
var HTTP_NOT_FOUND2 = 404;
|
|
24690
25463
|
var errorBody2 = (error, status) => new Response(JSON.stringify({ error }), {
|
|
24691
25464
|
headers: { "content-type": "application/json" },
|
|
@@ -24718,7 +25491,7 @@ var vpRoutes = ({
|
|
|
24718
25491
|
nonce: result.nonce,
|
|
24719
25492
|
request_uri: result.requestUri,
|
|
24720
25493
|
requestId: result.request.requestId
|
|
24721
|
-
}, { status:
|
|
25494
|
+
}, { status: HTTP_OK6 });
|
|
24722
25495
|
}, {
|
|
24723
25496
|
body: t35.Object({
|
|
24724
25497
|
client_id: t35.Optional(t35.String()),
|
|
@@ -24747,19 +25520,19 @@ var vpRoutes = ({
|
|
|
24747
25520
|
headers: {
|
|
24748
25521
|
"content-type": "application/oauth-authz-req+jwt"
|
|
24749
25522
|
},
|
|
24750
|
-
status:
|
|
25523
|
+
status: HTTP_OK6
|
|
24751
25524
|
});
|
|
24752
25525
|
}, { params: t35.Object({ id: t35.String() }) }).post(responseRoute, async ({ body }) => {
|
|
24753
25526
|
const requestId = body.state;
|
|
24754
25527
|
if (requestId === undefined) {
|
|
24755
|
-
return errorBody2("missing_state",
|
|
25528
|
+
return errorBody2("missing_state", HTTP_BAD_REQUEST5);
|
|
24756
25529
|
}
|
|
24757
25530
|
const result = await verifyPresentationResponse({
|
|
24758
25531
|
config: vpConfig,
|
|
24759
25532
|
input: { requestId, vpToken: body.vp_token }
|
|
24760
25533
|
});
|
|
24761
25534
|
if (!result.ok)
|
|
24762
|
-
return errorBody2(result.error,
|
|
25535
|
+
return errorBody2(result.error, HTTP_BAD_REQUEST5);
|
|
24763
25536
|
if (onVerifiedPresentation !== undefined) {
|
|
24764
25537
|
await onVerifiedPresentation({ verified: result.verified });
|
|
24765
25538
|
}
|
|
@@ -24768,7 +25541,7 @@ var vpRoutes = ({
|
|
|
24768
25541
|
holder_jwk: result.verified.holderJwk,
|
|
24769
25542
|
protected_claims: result.verified.protectedClaims,
|
|
24770
25543
|
verified: true
|
|
24771
|
-
}, { status:
|
|
25544
|
+
}, { status: HTTP_OK6 });
|
|
24772
25545
|
}, {
|
|
24773
25546
|
body: t35.Object({
|
|
24774
25547
|
presentation_submission: t35.Optional(t35.Unknown()),
|
|
@@ -24966,6 +25739,284 @@ var createPostgresScimTokenStore = (db) => ({
|
|
|
24966
25739
|
});
|
|
24967
25740
|
}
|
|
24968
25741
|
});
|
|
25742
|
+
// src/agents/registrationClient.ts
|
|
25743
|
+
var isObject2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
25744
|
+
var secureUrl = (value, allowLocalhost) => {
|
|
25745
|
+
if (typeof value !== "string")
|
|
25746
|
+
return;
|
|
25747
|
+
try {
|
|
25748
|
+
const url = new URL(value);
|
|
25749
|
+
if (url.protocol === "https:")
|
|
25750
|
+
return url.toString();
|
|
25751
|
+
if (allowLocalhost && url.protocol === "http:" && (url.hostname === "localhost" || url.hostname === "127.0.0.1")) {
|
|
25752
|
+
return url.toString();
|
|
25753
|
+
}
|
|
25754
|
+
} catch {
|
|
25755
|
+
return;
|
|
25756
|
+
}
|
|
25757
|
+
return;
|
|
25758
|
+
};
|
|
25759
|
+
var readBoundedJson = async (response, maxBytes) => {
|
|
25760
|
+
const length = Number(response.headers.get("content-length"));
|
|
25761
|
+
if (Number.isFinite(length) && length > maxBytes) {
|
|
25762
|
+
throw new Error("Agent registration metadata exceeds the response limit");
|
|
25763
|
+
}
|
|
25764
|
+
const bytes = new Uint8Array(await response.arrayBuffer());
|
|
25765
|
+
if (bytes.byteLength > maxBytes) {
|
|
25766
|
+
throw new Error("Agent registration metadata exceeds the response limit");
|
|
25767
|
+
}
|
|
25768
|
+
const parsed = JSON.parse(new TextDecoder().decode(bytes));
|
|
25769
|
+
return parsed;
|
|
25770
|
+
};
|
|
25771
|
+
var stringArray = (value) => Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : [];
|
|
25772
|
+
var requestJson = async (request, url, init, maxBytes) => {
|
|
25773
|
+
const response = await request(url, {
|
|
25774
|
+
...init,
|
|
25775
|
+
headers: {
|
|
25776
|
+
accept: "application/json",
|
|
25777
|
+
...init.headers
|
|
25778
|
+
},
|
|
25779
|
+
redirect: "error"
|
|
25780
|
+
});
|
|
25781
|
+
const body = await readBoundedJson(response, maxBytes);
|
|
25782
|
+
if (!isObject2(body))
|
|
25783
|
+
throw new Error("Expected a JSON object");
|
|
25784
|
+
return { body, response };
|
|
25785
|
+
};
|
|
25786
|
+
var createAgentRegistrationClient = (discovery, options = {}) => {
|
|
25787
|
+
const request = options.request ?? fetch;
|
|
25788
|
+
const maxBytes = options.maxResponseBytes ?? 256 * 1024;
|
|
25789
|
+
const post = (url, body, form = false) => requestJson(request, url, {
|
|
25790
|
+
body: form ? new URLSearchParams(Object.fromEntries(Object.entries(body).map(([key, value]) => [
|
|
25791
|
+
key,
|
|
25792
|
+
String(value)
|
|
25793
|
+
]))) : JSON.stringify(body),
|
|
25794
|
+
headers: {
|
|
25795
|
+
"content-type": form ? "application/x-www-form-urlencoded" : "application/json"
|
|
25796
|
+
},
|
|
25797
|
+
method: "POST"
|
|
25798
|
+
}, maxBytes);
|
|
25799
|
+
return {
|
|
25800
|
+
beginAnonymous: () => post(discovery.agentAuth.identityEndpoint, { type: "anonymous" }),
|
|
25801
|
+
beginServiceAuth: (loginHint) => post(discovery.agentAuth.identityEndpoint, {
|
|
25802
|
+
login_hint: loginHint,
|
|
25803
|
+
type: "service_auth"
|
|
25804
|
+
}),
|
|
25805
|
+
beginVerified: (assertion) => {
|
|
25806
|
+
if (!discovery.agentAuth.identityAssertionTypes.includes(AGENT_IDENTITY_ASSERTION_TYPE)) {
|
|
25807
|
+
throw new Error("Service does not accept ID-JAG assertions");
|
|
25808
|
+
}
|
|
25809
|
+
return post(discovery.agentAuth.identityEndpoint, {
|
|
25810
|
+
assertion,
|
|
25811
|
+
assertion_type: AGENT_IDENTITY_ASSERTION_TYPE,
|
|
25812
|
+
type: "identity_assertion"
|
|
25813
|
+
});
|
|
25814
|
+
},
|
|
25815
|
+
claim: (claimToken, email) => post(discovery.agentAuth.claimEndpoint, {
|
|
25816
|
+
claim_token: claimToken,
|
|
25817
|
+
email
|
|
25818
|
+
}),
|
|
25819
|
+
exchangeAssertion: (assertion) => post(discovery.tokenEndpoint, {
|
|
25820
|
+
assertion,
|
|
25821
|
+
grant_type: AGENT_IDENTITY_ASSERTION_GRANT_TYPE,
|
|
25822
|
+
resource: discovery.resource
|
|
25823
|
+
}, true),
|
|
25824
|
+
pollClaim: (claimToken) => post(discovery.tokenEndpoint, {
|
|
25825
|
+
claim_token: claimToken,
|
|
25826
|
+
grant_type: AGENT_CLAIM_GRANT_TYPE
|
|
25827
|
+
}, true)
|
|
25828
|
+
};
|
|
25829
|
+
};
|
|
25830
|
+
var discoverAgentRegistration = async (resource, options = {}) => {
|
|
25831
|
+
const request = options.request ?? fetch;
|
|
25832
|
+
const maxBytes = options.maxResponseBytes ?? 256 * 1024;
|
|
25833
|
+
const allowLocalhost = options.allowInsecureLocalhost === true;
|
|
25834
|
+
const resourceUrl = secureUrl(resource, allowLocalhost);
|
|
25835
|
+
if (resourceUrl === undefined)
|
|
25836
|
+
throw new Error("Resource URL must use HTTPS");
|
|
25837
|
+
const resourceMetadataUrl = new URL("/.well-known/oauth-protected-resource", resourceUrl).toString();
|
|
25838
|
+
const prm = await requestJson(request, resourceMetadataUrl, {}, maxBytes);
|
|
25839
|
+
const advertisedResource = secureUrl(prm.body.resource, allowLocalhost);
|
|
25840
|
+
if (advertisedResource === undefined || advertisedResource !== resourceUrl) {
|
|
25841
|
+
throw new Error("Protected resource metadata identity mismatch");
|
|
25842
|
+
}
|
|
25843
|
+
const authorizationServer = secureUrl(stringArray(prm.body.authorization_servers)[0], allowLocalhost);
|
|
25844
|
+
if (authorizationServer === undefined) {
|
|
25845
|
+
throw new Error("No secure authorization server is advertised");
|
|
25846
|
+
}
|
|
25847
|
+
const asUrl = new URL("/.well-known/oauth-authorization-server", authorizationServer).toString();
|
|
25848
|
+
const metadata = await requestJson(request, asUrl, {}, maxBytes);
|
|
25849
|
+
if (secureUrl(metadata.body.issuer, allowLocalhost) !== authorizationServer) {
|
|
25850
|
+
throw new Error("Authorization server issuer mismatch");
|
|
25851
|
+
}
|
|
25852
|
+
const tokenEndpoint = secureUrl(metadata.body.token_endpoint, allowLocalhost);
|
|
25853
|
+
const agentAuth = metadata.body.agent_auth;
|
|
25854
|
+
if (tokenEndpoint === undefined || !isObject2(agentAuth)) {
|
|
25855
|
+
throw new Error("Authorization server does not advertise agent registration");
|
|
25856
|
+
}
|
|
25857
|
+
const identityEndpoint = secureUrl(agentAuth.identity_endpoint, allowLocalhost);
|
|
25858
|
+
const claimEndpoint = secureUrl(agentAuth.claim_endpoint, allowLocalhost);
|
|
25859
|
+
const skill = secureUrl(agentAuth.skill, allowLocalhost);
|
|
25860
|
+
const assertionMetadata = agentAuth.identity_assertion;
|
|
25861
|
+
if (identityEndpoint === undefined || claimEndpoint === undefined || skill === undefined || !isObject2(assertionMetadata)) {
|
|
25862
|
+
throw new Error("Agent registration metadata is incomplete");
|
|
25863
|
+
}
|
|
25864
|
+
return {
|
|
25865
|
+
agentAuth: {
|
|
25866
|
+
claimEndpoint,
|
|
25867
|
+
identityAssertionTypes: stringArray(assertionMetadata.assertion_types_supported),
|
|
25868
|
+
identityEndpoint,
|
|
25869
|
+
identityTypes: stringArray(agentAuth.identity_types_supported),
|
|
25870
|
+
skill
|
|
25871
|
+
},
|
|
25872
|
+
authorizationServer,
|
|
25873
|
+
resource: resourceUrl,
|
|
25874
|
+
resourceMetadataUrl,
|
|
25875
|
+
scopes: stringArray(prm.body.scopes_supported),
|
|
25876
|
+
tokenEndpoint
|
|
25877
|
+
};
|
|
25878
|
+
};
|
|
25879
|
+
// src/agents/idJag.ts
|
|
25880
|
+
init_constants();
|
|
25881
|
+
var createInMemoryAgentIdentityAssertionJtiStore = () => {
|
|
25882
|
+
const entries = new Map;
|
|
25883
|
+
return {
|
|
25884
|
+
recordIfFresh: async (issuer, jti, expiresAt) => {
|
|
25885
|
+
const now = Date.now();
|
|
25886
|
+
for (const [key2, expiry] of entries) {
|
|
25887
|
+
if (expiry <= now)
|
|
25888
|
+
entries.delete(key2);
|
|
25889
|
+
}
|
|
25890
|
+
const key = `${issuer}\x00${jti}`;
|
|
25891
|
+
if (entries.has(key))
|
|
25892
|
+
return false;
|
|
25893
|
+
entries.set(key, expiresAt);
|
|
25894
|
+
return true;
|
|
25895
|
+
}
|
|
25896
|
+
};
|
|
25897
|
+
};
|
|
25898
|
+
var issueAgentIdentityAssertion = async ({
|
|
25899
|
+
agentContextId,
|
|
25900
|
+
agentPlatform,
|
|
25901
|
+
audience,
|
|
25902
|
+
clientId,
|
|
25903
|
+
issuer,
|
|
25904
|
+
now = Date.now(),
|
|
25905
|
+
resource,
|
|
25906
|
+
signingKey,
|
|
25907
|
+
ttlMs = 5 * 60 * MILLISECONDS_IN_A_SECOND,
|
|
25908
|
+
user
|
|
25909
|
+
}) => {
|
|
25910
|
+
if (user.emailVerified !== true && user.phoneNumberVerified !== true) {
|
|
25911
|
+
throw new Error("ID-JAG issuance requires a verified email or phone number");
|
|
25912
|
+
}
|
|
25913
|
+
const expiresAt = now + ttlMs;
|
|
25914
|
+
const payload = {
|
|
25915
|
+
aud: audience,
|
|
25916
|
+
auth_time: Math.floor(user.authenticatedAt / MILLISECONDS_IN_A_SECOND),
|
|
25917
|
+
client_id: clientId,
|
|
25918
|
+
exp: Math.floor(expiresAt / MILLISECONDS_IN_A_SECOND),
|
|
25919
|
+
iat: Math.floor(now / MILLISECONDS_IN_A_SECOND),
|
|
25920
|
+
iss: issuer,
|
|
25921
|
+
jti: crypto.randomUUID(),
|
|
25922
|
+
sub: user.subject
|
|
25923
|
+
};
|
|
25924
|
+
if (user.email !== undefined)
|
|
25925
|
+
payload.email = user.email;
|
|
25926
|
+
if (user.emailVerified !== undefined)
|
|
25927
|
+
payload.email_verified = user.emailVerified;
|
|
25928
|
+
if (user.name !== undefined)
|
|
25929
|
+
payload.name = user.name;
|
|
25930
|
+
if (user.phoneNumber !== undefined)
|
|
25931
|
+
payload.phone_number = user.phoneNumber;
|
|
25932
|
+
if (user.phoneNumberVerified !== undefined)
|
|
25933
|
+
payload.phone_number_verified = user.phoneNumberVerified;
|
|
25934
|
+
if (user.methods !== undefined)
|
|
25935
|
+
payload.amr = user.methods;
|
|
25936
|
+
if (resource !== undefined)
|
|
25937
|
+
payload.resource = resource;
|
|
25938
|
+
if (agentPlatform !== undefined)
|
|
25939
|
+
payload.agent_platform = agentPlatform;
|
|
25940
|
+
if (agentContextId !== undefined)
|
|
25941
|
+
payload.agent_context_id = agentContextId;
|
|
25942
|
+
return {
|
|
25943
|
+
assertion: await signJwt(payload, signingKey, "oauth-id-jag+jwt"),
|
|
25944
|
+
assertionType: AGENT_IDENTITY_ASSERTION_TYPE,
|
|
25945
|
+
expiresAt
|
|
25946
|
+
};
|
|
25947
|
+
};
|
|
25948
|
+
var numberClaim2 = (value) => typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
25949
|
+
var stringClaim2 = (value) => typeof value === "string" && value.length > 0 ? value : undefined;
|
|
25950
|
+
var createAgentIdentityAssertionVerifier = ({
|
|
25951
|
+
audience,
|
|
25952
|
+
clockSkewMs = MILLISECONDS_IN_A_SECOND * 60,
|
|
25953
|
+
jtiStore,
|
|
25954
|
+
maxAssertionLifetimeMs = MILLISECONDS_IN_A_SECOND * 60 * 60,
|
|
25955
|
+
maxAuthenticationAgeMs = MILLISECONDS_IN_A_SECOND * 60 * 60,
|
|
25956
|
+
resolveIssuer
|
|
25957
|
+
}) => async (assertion, now = Date.now()) => {
|
|
25958
|
+
const segments = assertion.split(".");
|
|
25959
|
+
if (segments.length !== 3 || segments[1] === undefined)
|
|
25960
|
+
return;
|
|
25961
|
+
let decoded;
|
|
25962
|
+
try {
|
|
25963
|
+
decoded = JSON.parse(Buffer.from(segments[1], "base64url").toString("utf8"));
|
|
25964
|
+
} catch {
|
|
25965
|
+
return;
|
|
25966
|
+
}
|
|
25967
|
+
if (typeof decoded !== "object" || decoded === null || Array.isArray(decoded)) {
|
|
25968
|
+
return;
|
|
25969
|
+
}
|
|
25970
|
+
const unverified = Object.fromEntries(Object.entries(decoded));
|
|
25971
|
+
const issuer = stringClaim2(unverified.iss);
|
|
25972
|
+
if (issuer === undefined)
|
|
25973
|
+
return;
|
|
25974
|
+
const trusted = await resolveIssuer(issuer);
|
|
25975
|
+
if (trusted === undefined)
|
|
25976
|
+
return;
|
|
25977
|
+
const verified = await verifyJwt(assertion, trusted.publicJwk);
|
|
25978
|
+
if (verified?.header?.typ !== "oauth-id-jag+jwt")
|
|
25979
|
+
return;
|
|
25980
|
+
const { payload } = verified;
|
|
25981
|
+
const subject = stringClaim2(payload.sub);
|
|
25982
|
+
const jti = stringClaim2(payload.jti);
|
|
25983
|
+
const clientId = stringClaim2(payload.client_id);
|
|
25984
|
+
const expiresAtSeconds = numberClaim2(payload.exp);
|
|
25985
|
+
const issuedAtSeconds = numberClaim2(payload.iat);
|
|
25986
|
+
const authenticatedAtSeconds = numberClaim2(payload.auth_time);
|
|
25987
|
+
if (payload.iss !== issuer || payload.aud !== audience || subject === undefined || jti === undefined || clientId === undefined || expiresAtSeconds === undefined || issuedAtSeconds === undefined || authenticatedAtSeconds === undefined) {
|
|
25988
|
+
return;
|
|
25989
|
+
}
|
|
25990
|
+
const expiresAt = expiresAtSeconds * MILLISECONDS_IN_A_SECOND;
|
|
25991
|
+
const issuedAt = issuedAtSeconds * MILLISECONDS_IN_A_SECOND;
|
|
25992
|
+
const authenticatedAt = authenticatedAtSeconds * MILLISECONDS_IN_A_SECOND;
|
|
25993
|
+
if (expiresAt <= now - clockSkewMs || issuedAt > now + clockSkewMs || expiresAt <= issuedAt || expiresAt - issuedAt > maxAssertionLifetimeMs + clockSkewMs || authenticatedAt > now + clockSkewMs || authenticatedAt > issuedAt + clockSkewMs || now - authenticatedAt > maxAuthenticationAgeMs + clockSkewMs) {
|
|
25994
|
+
return;
|
|
25995
|
+
}
|
|
25996
|
+
if (trusted.allowedClientIds !== undefined && !trusted.allowedClientIds.includes(clientId)) {
|
|
25997
|
+
return;
|
|
25998
|
+
}
|
|
25999
|
+
const email = stringClaim2(payload.email);
|
|
26000
|
+
const phoneNumber = stringClaim2(payload.phone_number);
|
|
26001
|
+
const emailVerified = payload.email_verified === true;
|
|
26002
|
+
const phoneNumberVerified = payload.phone_number_verified === true;
|
|
26003
|
+
if (!emailVerified && !phoneNumberVerified)
|
|
26004
|
+
return;
|
|
26005
|
+
if (!await jtiStore.recordIfFresh(issuer, jti, expiresAt)) {
|
|
26006
|
+
return;
|
|
26007
|
+
}
|
|
26008
|
+
return {
|
|
26009
|
+
authenticatedAt,
|
|
26010
|
+
clientId,
|
|
26011
|
+
email,
|
|
26012
|
+
emailVerified,
|
|
26013
|
+
issuer,
|
|
26014
|
+
name: stringClaim2(payload.name),
|
|
26015
|
+
phoneNumber,
|
|
26016
|
+
phoneNumberVerified,
|
|
26017
|
+
subject
|
|
26018
|
+
};
|
|
26019
|
+
};
|
|
24969
26020
|
// src/agents/oidcAdapter.ts
|
|
24970
26021
|
var BEARER_PREFIX6 = "Bearer ";
|
|
24971
26022
|
var MS_PER_SECOND7 = 1000;
|
|
@@ -25027,6 +26078,11 @@ var createOidcAgentCredentialVerifier = ({
|
|
|
25027
26078
|
return verifier;
|
|
25028
26079
|
};
|
|
25029
26080
|
// src/agents/inMemoryStores.ts
|
|
26081
|
+
var cloneIdentityRegistration = (value) => ({
|
|
26082
|
+
...value,
|
|
26083
|
+
claimAttempt: value.claimAttempt === undefined ? undefined : { ...value.claimAttempt },
|
|
26084
|
+
upstream: value.upstream === undefined ? undefined : { ...value.upstream }
|
|
26085
|
+
});
|
|
25030
26086
|
var cloneRegistration = (value) => ({
|
|
25031
26087
|
...value,
|
|
25032
26088
|
allowedScopes: [...value.allowedScopes],
|
|
@@ -25061,6 +26117,48 @@ var createInMemoryAgentDelegationStore = () => {
|
|
|
25061
26117
|
}
|
|
25062
26118
|
};
|
|
25063
26119
|
};
|
|
26120
|
+
var createInMemoryAgentIdentityRegistrationStore = () => {
|
|
26121
|
+
const registrations = new Map;
|
|
26122
|
+
return {
|
|
26123
|
+
create: async (registration) => {
|
|
26124
|
+
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);
|
|
26125
|
+
if (conflicts)
|
|
26126
|
+
return false;
|
|
26127
|
+
registrations.set(registration.registrationId, cloneIdentityRegistration(registration));
|
|
26128
|
+
return true;
|
|
26129
|
+
},
|
|
26130
|
+
findByAgentId: async (agentId) => {
|
|
26131
|
+
const value = [...registrations.values()].find((registration) => registration.agentId === agentId);
|
|
26132
|
+
return value === undefined ? undefined : cloneIdentityRegistration(value);
|
|
26133
|
+
},
|
|
26134
|
+
findByAttemptTokenHash: async (attemptTokenHash) => {
|
|
26135
|
+
const value = [...registrations.values()].find((registration) => registration.claimAttempt?.tokenHash === attemptTokenHash);
|
|
26136
|
+
return value === undefined ? undefined : cloneIdentityRegistration(value);
|
|
26137
|
+
},
|
|
26138
|
+
findByClaimTokenHash: async (claimTokenHash) => {
|
|
26139
|
+
const value = [...registrations.values()].find((registration) => registration.claimTokenHash === claimTokenHash);
|
|
26140
|
+
return value === undefined ? undefined : cloneIdentityRegistration(value);
|
|
26141
|
+
},
|
|
26142
|
+
findByRegistrationId: async (registrationId) => {
|
|
26143
|
+
const value = registrations.get(registrationId);
|
|
26144
|
+
return value === undefined ? undefined : cloneIdentityRegistration(value);
|
|
26145
|
+
},
|
|
26146
|
+
findByUpstreamIdentity: async ({ clientId, issuer, subject }) => {
|
|
26147
|
+
const value = [...registrations.values()].find((registration) => registration.upstream?.clientId === clientId && registration.upstream?.issuer === issuer && registration.upstream.subject === subject);
|
|
26148
|
+
return value === undefined ? undefined : cloneIdentityRegistration(value);
|
|
26149
|
+
},
|
|
26150
|
+
replace: async (registration, expectedVersion) => {
|
|
26151
|
+
const current = registrations.get(registration.registrationId);
|
|
26152
|
+
if (current?.version !== expectedVersion)
|
|
26153
|
+
return false;
|
|
26154
|
+
registrations.set(registration.registrationId, cloneIdentityRegistration({
|
|
26155
|
+
...registration,
|
|
26156
|
+
version: expectedVersion + 1
|
|
26157
|
+
}));
|
|
26158
|
+
return true;
|
|
26159
|
+
}
|
|
26160
|
+
};
|
|
26161
|
+
};
|
|
25064
26162
|
var createInMemoryAgentRegistrationStore = () => {
|
|
25065
26163
|
const registrations = new Map;
|
|
25066
26164
|
return {
|
|
@@ -25096,6 +26194,38 @@ var agentDelegationsTable = pgTable("auth_agent_delegations", {
|
|
|
25096
26194
|
updated_at_ms: bigint("updated_at_ms", { mode: "number" }).notNull(),
|
|
25097
26195
|
user_id: varchar("user_id", { length: ID_LENGTH7 }).notNull()
|
|
25098
26196
|
});
|
|
26197
|
+
var agentIdentityRegistrationsTable = pgTable("auth_agent_identity_registrations", {
|
|
26198
|
+
agent_id: varchar("agent_id", { length: ID_LENGTH7 }).notNull().unique(),
|
|
26199
|
+
claim_attempt: jsonb("claim_attempt").$type(),
|
|
26200
|
+
claim_attempt_token_hash: varchar("claim_attempt_token_hash", {
|
|
26201
|
+
length: ID_LENGTH7
|
|
26202
|
+
}).unique(),
|
|
26203
|
+
claim_expires_at_ms: bigint("claim_expires_at_ms", {
|
|
26204
|
+
mode: "number"
|
|
26205
|
+
}).notNull(),
|
|
26206
|
+
claim_token_hash: varchar("claim_token_hash", {
|
|
26207
|
+
length: ID_LENGTH7
|
|
26208
|
+
}).notNull().unique(),
|
|
26209
|
+
created_at_ms: bigint("created_at_ms", { mode: "number" }).notNull(),
|
|
26210
|
+
expires_at_ms: bigint("expires_at_ms", { mode: "number" }).notNull(),
|
|
26211
|
+
kind: varchar("kind", { length: 32 }).$type().notNull(),
|
|
26212
|
+
last_polled_at_ms: bigint("last_polled_at_ms", { mode: "number" }),
|
|
26213
|
+
login_hint: varchar("login_hint", { length: ID_LENGTH7 }),
|
|
26214
|
+
registration_id: varchar("registration_id", {
|
|
26215
|
+
length: ID_LENGTH7
|
|
26216
|
+
}).primaryKey(),
|
|
26217
|
+
status: varchar("status", { length: STATUS_LENGTH2 }).$type().notNull(),
|
|
26218
|
+
updated_at_ms: bigint("updated_at_ms", { mode: "number" }).notNull(),
|
|
26219
|
+
upstream_client_id: varchar("upstream_client_id", {
|
|
26220
|
+
length: ID_LENGTH7
|
|
26221
|
+
}),
|
|
26222
|
+
upstream_issuer: varchar("upstream_issuer", { length: ID_LENGTH7 }),
|
|
26223
|
+
upstream_subject: varchar("upstream_subject", { length: ID_LENGTH7 }),
|
|
26224
|
+
user_id: varchar("user_id", { length: ID_LENGTH7 }),
|
|
26225
|
+
version: integer("version").notNull()
|
|
26226
|
+
}, (table) => [
|
|
26227
|
+
uniqueIndex("auth_agent_identity_upstream_unique").on(table.upstream_issuer, table.upstream_subject, table.upstream_client_id)
|
|
26228
|
+
]);
|
|
25099
26229
|
var agentRegistrationsTable = pgTable("auth_agent_registrations", {
|
|
25100
26230
|
agent_id: varchar("agent_id", { length: ID_LENGTH7 }).primaryKey(),
|
|
25101
26231
|
allowed_scopes: jsonb("allowed_scopes").$type().notNull().default([]),
|
|
@@ -25128,7 +26258,49 @@ var toDelegation = (row) => ({
|
|
|
25128
26258
|
updatedAt: row.updated_at_ms,
|
|
25129
26259
|
userId: row.user_id
|
|
25130
26260
|
});
|
|
26261
|
+
var toIdentityRegistration = (row) => ({
|
|
26262
|
+
agentId: row.agent_id,
|
|
26263
|
+
claimAttempt: row.claim_attempt ?? undefined,
|
|
26264
|
+
claimExpiresAt: row.claim_expires_at_ms,
|
|
26265
|
+
claimTokenHash: row.claim_token_hash,
|
|
26266
|
+
createdAt: row.created_at_ms,
|
|
26267
|
+
expiresAt: row.expires_at_ms,
|
|
26268
|
+
kind: row.kind,
|
|
26269
|
+
lastPolledAt: row.last_polled_at_ms ?? undefined,
|
|
26270
|
+
loginHint: row.login_hint ?? undefined,
|
|
26271
|
+
registrationId: row.registration_id,
|
|
26272
|
+
status: row.status,
|
|
26273
|
+
updatedAt: row.updated_at_ms,
|
|
26274
|
+
upstream: row.upstream_client_id === null || row.upstream_issuer === null || row.upstream_subject === null ? undefined : {
|
|
26275
|
+
clientId: row.upstream_client_id,
|
|
26276
|
+
issuer: row.upstream_issuer,
|
|
26277
|
+
subject: row.upstream_subject
|
|
26278
|
+
},
|
|
26279
|
+
userId: row.user_id ?? undefined,
|
|
26280
|
+
version: row.version
|
|
26281
|
+
});
|
|
26282
|
+
var identityRegistrationValues = (registration) => ({
|
|
26283
|
+
agent_id: registration.agentId,
|
|
26284
|
+
claim_attempt: registration.claimAttempt ?? null,
|
|
26285
|
+
claim_attempt_token_hash: registration.claimAttempt?.tokenHash ?? null,
|
|
26286
|
+
claim_expires_at_ms: registration.claimExpiresAt,
|
|
26287
|
+
claim_token_hash: registration.claimTokenHash,
|
|
26288
|
+
created_at_ms: registration.createdAt,
|
|
26289
|
+
expires_at_ms: registration.expiresAt,
|
|
26290
|
+
kind: registration.kind,
|
|
26291
|
+
last_polled_at_ms: registration.lastPolledAt ?? null,
|
|
26292
|
+
login_hint: registration.loginHint ?? null,
|
|
26293
|
+
registration_id: registration.registrationId,
|
|
26294
|
+
status: registration.status,
|
|
26295
|
+
updated_at_ms: registration.updatedAt,
|
|
26296
|
+
upstream_client_id: registration.upstream?.clientId ?? null,
|
|
26297
|
+
upstream_issuer: registration.upstream?.issuer ?? null,
|
|
26298
|
+
upstream_subject: registration.upstream?.subject ?? null,
|
|
26299
|
+
user_id: registration.userId ?? null,
|
|
26300
|
+
version: registration.version
|
|
26301
|
+
});
|
|
25131
26302
|
var createNeonAgentDelegationStore = (databaseUrl) => createPostgresAgentDelegationStore(createNeonDatabase(databaseUrl));
|
|
26303
|
+
var createNeonAgentIdentityRegistrationStore = (databaseUrl) => createPostgresAgentIdentityRegistrationStore(createNeonDatabase(databaseUrl));
|
|
25132
26304
|
var createNeonAgentRegistrationStore = (databaseUrl) => createPostgresAgentRegistrationStore(createNeonDatabase(databaseUrl));
|
|
25133
26305
|
var createPostgresAgentDelegationStore = (db) => ({
|
|
25134
26306
|
findActiveDelegation: async ({
|
|
@@ -25169,6 +26341,41 @@ var createPostgresAgentDelegationStore = (db) => ({
|
|
|
25169
26341
|
});
|
|
25170
26342
|
}
|
|
25171
26343
|
});
|
|
26344
|
+
var createPostgresAgentIdentityRegistrationStore = (db) => ({
|
|
26345
|
+
create: async (registration) => {
|
|
26346
|
+
const rows = await db.insert(agentIdentityRegistrationsTable).values(identityRegistrationValues(registration)).onConflictDoNothing().returning({ id: agentIdentityRegistrationsTable.registration_id });
|
|
26347
|
+
return rows.length === 1;
|
|
26348
|
+
},
|
|
26349
|
+
findByAgentId: async (agentId) => {
|
|
26350
|
+
const [row] = await db.select().from(agentIdentityRegistrationsTable).where(eq(agentIdentityRegistrationsTable.agent_id, agentId)).limit(1);
|
|
26351
|
+
return row === undefined ? undefined : toIdentityRegistration(row);
|
|
26352
|
+
},
|
|
26353
|
+
findByAttemptTokenHash: async (attemptTokenHash) => {
|
|
26354
|
+
const [row] = await db.select().from(agentIdentityRegistrationsTable).where(eq(agentIdentityRegistrationsTable.claim_attempt_token_hash, attemptTokenHash)).limit(1);
|
|
26355
|
+
return row === undefined ? undefined : toIdentityRegistration(row);
|
|
26356
|
+
},
|
|
26357
|
+
findByClaimTokenHash: async (claimTokenHash) => {
|
|
26358
|
+
const [row] = await db.select().from(agentIdentityRegistrationsTable).where(eq(agentIdentityRegistrationsTable.claim_token_hash, claimTokenHash)).limit(1);
|
|
26359
|
+
return row === undefined ? undefined : toIdentityRegistration(row);
|
|
26360
|
+
},
|
|
26361
|
+
findByRegistrationId: async (registrationId) => {
|
|
26362
|
+
const [row] = await db.select().from(agentIdentityRegistrationsTable).where(eq(agentIdentityRegistrationsTable.registration_id, registrationId)).limit(1);
|
|
26363
|
+
return row === undefined ? undefined : toIdentityRegistration(row);
|
|
26364
|
+
},
|
|
26365
|
+
findByUpstreamIdentity: async ({ clientId, issuer, subject }) => {
|
|
26366
|
+
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);
|
|
26367
|
+
return row === undefined ? undefined : toIdentityRegistration(row);
|
|
26368
|
+
},
|
|
26369
|
+
replace: async (registration, expectedVersion) => {
|
|
26370
|
+
const next = {
|
|
26371
|
+
...registration,
|
|
26372
|
+
version: expectedVersion + 1
|
|
26373
|
+
};
|
|
26374
|
+
const values = identityRegistrationValues(next);
|
|
26375
|
+
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 });
|
|
26376
|
+
return rows.length === 1;
|
|
26377
|
+
}
|
|
26378
|
+
});
|
|
25172
26379
|
var createPostgresAgentRegistrationStore = (db) => ({
|
|
25173
26380
|
findByAgentId: async (agentId) => {
|
|
25174
26381
|
const [row] = await db.select().from(agentRegistrationsTable).where(eq(agentRegistrationsTable.agent_id, agentId)).limit(1);
|
|
@@ -25402,7 +26609,7 @@ var createPostgresApiKeyStore = (db) => ({
|
|
|
25402
26609
|
}
|
|
25403
26610
|
});
|
|
25404
26611
|
// src/oidc/clientIdMetadata.ts
|
|
25405
|
-
var
|
|
26612
|
+
var secureUrl2 = (value) => {
|
|
25406
26613
|
try {
|
|
25407
26614
|
return new URL(value).protocol === "https:";
|
|
25408
26615
|
} catch {
|
|
@@ -25413,7 +26620,7 @@ var validateClientIdMetadataDocument = (document, expectedClientId) => {
|
|
|
25413
26620
|
const errors = [];
|
|
25414
26621
|
if (document.client_id !== expectedClientId)
|
|
25415
26622
|
errors.push("client_id does not match the metadata document URL");
|
|
25416
|
-
if (!
|
|
26623
|
+
if (!secureUrl2(document.client_id))
|
|
25417
26624
|
errors.push("client_id must use HTTPS");
|
|
25418
26625
|
if (!Array.isArray(document.redirect_uris) || document.redirect_uris.length === 0)
|
|
25419
26626
|
errors.push("redirect_uris is required");
|
|
@@ -25433,7 +26640,7 @@ var validateClientIdMetadataDocument = (document, expectedClientId) => {
|
|
|
25433
26640
|
["tos_uri", document.tos_uri],
|
|
25434
26641
|
["jwks_uri", document.jwks_uri]
|
|
25435
26642
|
]) {
|
|
25436
|
-
if (value !== undefined && !
|
|
26643
|
+
if (value !== undefined && !secureUrl2(value))
|
|
25437
26644
|
errors.push(`${name2} must use HTTPS`);
|
|
25438
26645
|
}
|
|
25439
26646
|
return errors;
|
|
@@ -25456,7 +26663,7 @@ var createClientIdMetadataResolver = ({
|
|
|
25456
26663
|
}) => {
|
|
25457
26664
|
const cache = new Map;
|
|
25458
26665
|
return async (clientId) => {
|
|
25459
|
-
if (!
|
|
26666
|
+
if (!secureUrl2(clientId) || !await allow(clientId))
|
|
25460
26667
|
return;
|
|
25461
26668
|
const cached = cache.get(clientId);
|
|
25462
26669
|
if (cached !== undefined && cached.expiresAt > now())
|
|
@@ -27368,10 +28575,20 @@ var mfaTotpLockoutMigration = {
|
|
|
27368
28575
|
};
|
|
27369
28576
|
var blockMigrations = {
|
|
27370
28577
|
adaptive: initMigration("adaptive", [knownDevicesTable, loginHistoryTable]),
|
|
27371
|
-
agents:
|
|
27372
|
-
|
|
27373
|
-
|
|
27374
|
-
|
|
28578
|
+
agents: {
|
|
28579
|
+
block: "agents",
|
|
28580
|
+
migrations: [
|
|
28581
|
+
...initMigration("agents", [
|
|
28582
|
+
agentRegistrationsTable,
|
|
28583
|
+
agentDelegationsTable,
|
|
28584
|
+
agentIdentityRegistrationsTable
|
|
28585
|
+
]).migrations,
|
|
28586
|
+
{
|
|
28587
|
+
id: "0002_identity_registration",
|
|
28588
|
+
sql: tablesToInitSql([agentIdentityRegistrationsTable])
|
|
28589
|
+
}
|
|
28590
|
+
]
|
|
28591
|
+
},
|
|
27375
28592
|
apikeys: initMigration("apikeys", [
|
|
27376
28593
|
accessTokensTable,
|
|
27377
28594
|
apiClientsTable,
|
|
@@ -27434,17 +28651,17 @@ var blockMigrations = {
|
|
|
27434
28651
|
};
|
|
27435
28652
|
// src/sso/samlIdpRoutes.ts
|
|
27436
28653
|
import { Elysia as Elysia41, t as t36 } from "elysia";
|
|
27437
|
-
var
|
|
27438
|
-
var
|
|
28654
|
+
var HTTP_BAD_REQUEST6 = 400;
|
|
28655
|
+
var HTTP_UNAUTHORIZED5 = 401;
|
|
27439
28656
|
var HTTP_FOUND2 = 302;
|
|
27440
|
-
var
|
|
28657
|
+
var HTTP_OK7 = 200;
|
|
27441
28658
|
var xmlResponse = (body) => new Response(body, {
|
|
27442
28659
|
headers: { "content-type": "application/samlmetadata+xml" },
|
|
27443
|
-
status:
|
|
28660
|
+
status: HTTP_OK7
|
|
27444
28661
|
});
|
|
27445
28662
|
var htmlResponse = (body) => new Response(body, {
|
|
27446
28663
|
headers: { "content-type": "text/html; charset=utf-8" },
|
|
27447
|
-
status:
|
|
28664
|
+
status: HTTP_OK7
|
|
27448
28665
|
});
|
|
27449
28666
|
var redirectTo2 = (url) => new Response(null, { headers: { location: url }, status: HTTP_FOUND2 });
|
|
27450
28667
|
var errorJson = (status, error) => new Response(JSON.stringify({ error }), {
|
|
@@ -27496,7 +28713,7 @@ var samlIdpRoutes = ({
|
|
|
27496
28713
|
userSessionIdValue
|
|
27497
28714
|
}) => {
|
|
27498
28715
|
if (body.SAMLRequest === undefined) {
|
|
27499
|
-
return errorJson(
|
|
28716
|
+
return errorJson(HTTP_BAD_REQUEST6, "missing_saml_request");
|
|
27500
28717
|
}
|
|
27501
28718
|
let firstPass;
|
|
27502
28719
|
try {
|
|
@@ -27505,11 +28722,11 @@ var samlIdpRoutes = ({
|
|
|
27505
28722
|
samlRequest: body.SAMLRequest
|
|
27506
28723
|
});
|
|
27507
28724
|
} catch {
|
|
27508
|
-
return errorJson(
|
|
28725
|
+
return errorJson(HTTP_BAD_REQUEST6, "invalid_authn_request");
|
|
27509
28726
|
}
|
|
27510
28727
|
const serviceProvider = await samlServiceProviderStore.findServiceProvider(firstPass.issuer);
|
|
27511
28728
|
if (serviceProvider === undefined) {
|
|
27512
|
-
return errorJson(
|
|
28729
|
+
return errorJson(HTTP_BAD_REQUEST6, "unknown_service_provider");
|
|
27513
28730
|
}
|
|
27514
28731
|
let parsed;
|
|
27515
28732
|
try {
|
|
@@ -27522,7 +28739,7 @@ var samlIdpRoutes = ({
|
|
|
27522
28739
|
signedQueryString: binding === "Redirect" ? new URL(request.url).search.slice(1) : undefined
|
|
27523
28740
|
});
|
|
27524
28741
|
} catch {
|
|
27525
|
-
return errorJson(
|
|
28742
|
+
return errorJson(HTTP_BAD_REQUEST6, "invalid_authn_request");
|
|
27526
28743
|
}
|
|
27527
28744
|
const userSession = await loadSessionFromSource({
|
|
27528
28745
|
authSessionStore,
|
|
@@ -27531,7 +28748,7 @@ var samlIdpRoutes = ({
|
|
|
27531
28748
|
});
|
|
27532
28749
|
if (userSession === undefined || parsed.forceAuthn === true) {
|
|
27533
28750
|
if (loginUrl === undefined) {
|
|
27534
|
-
return errorJson(
|
|
28751
|
+
return errorJson(HTTP_UNAUTHORIZED5, "login_required");
|
|
27535
28752
|
}
|
|
27536
28753
|
return redirectTo2(`${loginUrl}?return_to=${encodeURIComponent(request.url)}`);
|
|
27537
28754
|
}
|
|
@@ -27580,11 +28797,11 @@ var samlIdpRoutes = ({
|
|
|
27580
28797
|
store
|
|
27581
28798
|
}) => {
|
|
27582
28799
|
if (serviceProviderEntityId === undefined) {
|
|
27583
|
-
return errorJson(
|
|
28800
|
+
return errorJson(HTTP_BAD_REQUEST6, "missing_sp");
|
|
27584
28801
|
}
|
|
27585
28802
|
const serviceProvider = await samlServiceProviderStore.findServiceProvider(serviceProviderEntityId);
|
|
27586
28803
|
if (serviceProvider === undefined) {
|
|
27587
|
-
return errorJson(
|
|
28804
|
+
return errorJson(HTTP_BAD_REQUEST6, "unknown_service_provider");
|
|
27588
28805
|
}
|
|
27589
28806
|
const userSession = authSessionStore === undefined ? await loadSessionFromSource({
|
|
27590
28807
|
session: store.session,
|
|
@@ -27596,7 +28813,7 @@ var samlIdpRoutes = ({
|
|
|
27596
28813
|
});
|
|
27597
28814
|
if (userSession === undefined) {
|
|
27598
28815
|
if (loginUrl === undefined) {
|
|
27599
|
-
return errorJson(
|
|
28816
|
+
return errorJson(HTTP_UNAUTHORIZED5, "login_required");
|
|
27600
28817
|
}
|
|
27601
28818
|
return redirectTo2(`${loginUrl}?return_to=${encodeURIComponent(request.url)}`);
|
|
27602
28819
|
}
|
|
@@ -27893,6 +29110,38 @@ var auth = async ({
|
|
|
27893
29110
|
onRevocationError,
|
|
27894
29111
|
onSessionCleanup
|
|
27895
29112
|
}) => {
|
|
29113
|
+
if (agentAuth?.agentRegistration !== undefined) {
|
|
29114
|
+
if (oidc === undefined) {
|
|
29115
|
+
throw new Error("agentAuth.agentRegistration requires the OIDC provider");
|
|
29116
|
+
}
|
|
29117
|
+
if (agentAuth.authorizationServer !== oidc.issuer) {
|
|
29118
|
+
throw new Error("agentAuth.authorizationServer must equal oidc.issuer");
|
|
29119
|
+
}
|
|
29120
|
+
const registration2 = agentAuth.agentRegistration;
|
|
29121
|
+
const unknownScopes = [
|
|
29122
|
+
...registration2.preClaimScopes ?? [],
|
|
29123
|
+
...registration2.postClaimScopes
|
|
29124
|
+
].filter((scope) => !agentAuth.scopes.includes(scope));
|
|
29125
|
+
if (unknownScopes.length > 0) {
|
|
29126
|
+
throw new Error(`Agent registration uses undeclared scopes: ${[...new Set(unknownScopes)].join(", ")}`);
|
|
29127
|
+
}
|
|
29128
|
+
if (registration2.allowAnonymous === true && registration2.revokeAccessTokens === undefined) {
|
|
29129
|
+
throw new Error("Anonymous agent registration requires revokeAccessTokens");
|
|
29130
|
+
}
|
|
29131
|
+
for (const [name2, value] of Object.entries({
|
|
29132
|
+
assertionTtlMs: registration2.assertionTtlMs,
|
|
29133
|
+
attemptTtlMs: registration2.attemptTtlMs,
|
|
29134
|
+
claimTtlMs: registration2.claimTtlMs,
|
|
29135
|
+
maxAuthenticationAgeMs: registration2.maxAuthenticationAgeMs,
|
|
29136
|
+
maxCodeAttempts: registration2.maxCodeAttempts,
|
|
29137
|
+
pollIntervalSeconds: registration2.pollIntervalSeconds,
|
|
29138
|
+
tokenTtlMs: registration2.tokenTtlMs
|
|
29139
|
+
})) {
|
|
29140
|
+
if (value !== undefined && (!Number.isFinite(value) || value <= 0)) {
|
|
29141
|
+
throw new Error(`agentAuth.agentRegistration.${name2} must be positive`);
|
|
29142
|
+
}
|
|
29143
|
+
}
|
|
29144
|
+
}
|
|
27896
29145
|
if (tracing !== undefined)
|
|
27897
29146
|
await initTracing(tracing);
|
|
27898
29147
|
const clientProviders = await buildClientProviders(providersConfiguration, createOAuth2Client, customProviders);
|
|
@@ -27906,8 +29155,34 @@ var auth = async ({
|
|
|
27906
29155
|
}
|
|
27907
29156
|
}) : undefined;
|
|
27908
29157
|
const lockoutGuard = lockout ? createLockoutGuard(lockout) : undefined;
|
|
29158
|
+
const resolvedAgentAuth = agentAuth === undefined ? undefined : {
|
|
29159
|
+
...agentAuth,
|
|
29160
|
+
oidcRoute: agentAuth.oidcRoute ?? oidc?.oidcRoute
|
|
29161
|
+
};
|
|
27909
29162
|
const oidcConfig = oidc ? {
|
|
27910
29163
|
...oidc,
|
|
29164
|
+
additionalDiscoveryMetadata: {
|
|
29165
|
+
...oidc.additionalDiscoveryMetadata,
|
|
29166
|
+
...resolvedAgentAuth?.agentRegistration === undefined ? {} : {
|
|
29167
|
+
agent_auth: agentRegistrationDiscoveryMetadata(resolvedAgentAuth)
|
|
29168
|
+
}
|
|
29169
|
+
},
|
|
29170
|
+
publicTokenGrantTypes: [
|
|
29171
|
+
...oidc.publicTokenGrantTypes ?? [],
|
|
29172
|
+
...resolvedAgentAuth?.agentRegistration === undefined ? [] : [
|
|
29173
|
+
AGENT_CLAIM_GRANT_TYPE,
|
|
29174
|
+
AGENT_IDENTITY_ASSERTION_GRANT_TYPE
|
|
29175
|
+
]
|
|
29176
|
+
],
|
|
29177
|
+
handlePublicTokenGrant: async (context) => {
|
|
29178
|
+
const consumerResult = await oidc.handlePublicTokenGrant?.(context);
|
|
29179
|
+
if (consumerResult !== undefined)
|
|
29180
|
+
return consumerResult;
|
|
29181
|
+
if (resolvedAgentAuth?.agentRegistration === undefined) {
|
|
29182
|
+
return;
|
|
29183
|
+
}
|
|
29184
|
+
return handleAgentTokenGrant(resolvedAgentAuth, context.body);
|
|
29185
|
+
},
|
|
27911
29186
|
onClientRegistered: async (context) => {
|
|
27912
29187
|
await oidc.onClientRegistered?.(context);
|
|
27913
29188
|
if (agentAuth?.registerDynamicClients !== true)
|
|
@@ -27932,28 +29207,28 @@ var auth = async ({
|
|
|
27932
29207
|
await oidc.onDeviceAuthorizationApproved?.(context);
|
|
27933
29208
|
if (agentAuth === undefined)
|
|
27934
29209
|
return;
|
|
27935
|
-
const
|
|
27936
|
-
if (
|
|
29210
|
+
const registration2 = await agentAuth.registrationStore.findByClientId(context.clientId);
|
|
29211
|
+
if (registration2 === undefined || registration2.status !== "active") {
|
|
27937
29212
|
return;
|
|
27938
29213
|
}
|
|
27939
29214
|
const now = Date.now();
|
|
27940
29215
|
const existing = await agentAuth.delegationStore.findActiveDelegation({
|
|
27941
|
-
agentId:
|
|
29216
|
+
agentId: registration2.agentId,
|
|
27942
29217
|
now,
|
|
27943
29218
|
userId: context.userSub
|
|
27944
29219
|
});
|
|
27945
29220
|
await agentAuth.delegationStore.saveDelegation({
|
|
27946
|
-
agentId:
|
|
29221
|
+
agentId: registration2.agentId,
|
|
27947
29222
|
createdAt: existing?.createdAt ?? now,
|
|
27948
29223
|
delegationId: existing?.delegationId ?? `agd_${crypto.randomUUID()}`,
|
|
27949
|
-
scopes: context.scopes.filter((scope) =>
|
|
29224
|
+
scopes: context.scopes.filter((scope) => registration2.allowedScopes.includes(scope) && agentAuth.scopes.includes(scope)),
|
|
27950
29225
|
status: "active",
|
|
27951
29226
|
updatedAt: now,
|
|
27952
29227
|
userId: context.userSub
|
|
27953
29228
|
});
|
|
27954
29229
|
await auditEmit?.({
|
|
27955
29230
|
at: now,
|
|
27956
|
-
metadata: { agentId:
|
|
29231
|
+
metadata: { agentId: registration2.agentId },
|
|
27957
29232
|
type: "agent_delegated",
|
|
27958
29233
|
userId: context.userSub
|
|
27959
29234
|
});
|
|
@@ -28066,7 +29341,7 @@ var auth = async ({
|
|
|
28066
29341
|
...htmx,
|
|
28067
29342
|
authSessionStore
|
|
28068
29343
|
}) : new Elysia42);
|
|
28069
|
-
const authWithAgent = composedAuth.use(agentAuthPlugin(
|
|
29344
|
+
const authWithAgent = composedAuth.use(agentAuthPlugin(resolvedAgentAuth));
|
|
28070
29345
|
return authWithAgent;
|
|
28071
29346
|
};
|
|
28072
29347
|
export {
|
|
@@ -28118,6 +29393,7 @@ export {
|
|
|
28118
29393
|
stepUpPlugin,
|
|
28119
29394
|
statusListRoutes,
|
|
28120
29395
|
startImpersonation,
|
|
29396
|
+
startAgentRegistration,
|
|
28121
29397
|
ssoDiscoveryRoute,
|
|
28122
29398
|
ssoConnectionsTable,
|
|
28123
29399
|
signWebhook,
|
|
@@ -28144,6 +29420,7 @@ export {
|
|
|
28144
29420
|
roleRoutes,
|
|
28145
29421
|
revokeUserSessions,
|
|
28146
29422
|
revokeRefreshToken,
|
|
29423
|
+
revokeAgentIdentityRegistration,
|
|
28147
29424
|
revocableProviderOptions,
|
|
28148
29425
|
resolveSetupSession,
|
|
28149
29426
|
resolveScimOrganization,
|
|
@@ -28223,6 +29500,8 @@ export {
|
|
|
28223
29500
|
issueDeviceAuthorization,
|
|
28224
29501
|
issueCredential,
|
|
28225
29502
|
issueBackchannelAuth,
|
|
29503
|
+
issueAgentServiceAssertion,
|
|
29504
|
+
issueAgentIdentityAssertion,
|
|
28226
29505
|
isValidUser,
|
|
28227
29506
|
isValidProviderOption,
|
|
28228
29507
|
isUserSessionId,
|
|
@@ -28251,6 +29530,7 @@ export {
|
|
|
28251
29530
|
hashAuditEvent,
|
|
28252
29531
|
hasScopes,
|
|
28253
29532
|
hasOrganizationScope,
|
|
29533
|
+
handleAgentTokenGrant,
|
|
28254
29534
|
getUserSessionId,
|
|
28255
29535
|
getStatus,
|
|
28256
29536
|
getRegisteredClient,
|
|
@@ -28262,6 +29542,7 @@ export {
|
|
|
28262
29542
|
generateSecureToken,
|
|
28263
29543
|
generateEncryptionKey,
|
|
28264
29544
|
generateBackupCodes,
|
|
29545
|
+
generateAgentRegistrationGuide,
|
|
28265
29546
|
fromBase64Url2 as fromBase64Url,
|
|
28266
29547
|
fingerprintDevice,
|
|
28267
29548
|
fetchUserInfo,
|
|
@@ -28279,6 +29560,7 @@ export {
|
|
|
28279
29560
|
endImpersonation,
|
|
28280
29561
|
encryptTotpSecret,
|
|
28281
29562
|
encryptSecret,
|
|
29563
|
+
discoverAgentRegistration,
|
|
28282
29564
|
diffScimGroupMembers,
|
|
28283
29565
|
denyDeviceAuthorization,
|
|
28284
29566
|
denyBackchannelAuth,
|
|
@@ -28349,6 +29631,7 @@ export {
|
|
|
28349
29631
|
createPostgresApiKeyStore,
|
|
28350
29632
|
createPostgresApiClientStore,
|
|
28351
29633
|
createPostgresAgentRegistrationStore,
|
|
29634
|
+
createPostgresAgentIdentityRegistrationStore,
|
|
28352
29635
|
createPostgresAgentDelegationStore,
|
|
28353
29636
|
createPostgresAccessTokenStore,
|
|
28354
29637
|
createOrganization,
|
|
@@ -28391,6 +29674,7 @@ export {
|
|
|
28391
29674
|
createNeonApiKeyStore,
|
|
28392
29675
|
createNeonApiClientStore,
|
|
28393
29676
|
createNeonAgentRegistrationStore,
|
|
29677
|
+
createNeonAgentIdentityRegistrationStore,
|
|
28394
29678
|
createNeonAgentDelegationStore,
|
|
28395
29679
|
createNeonAccessTokenStore,
|
|
28396
29680
|
createMfaGate,
|
|
@@ -28433,6 +29717,8 @@ export {
|
|
|
28433
29717
|
createInMemoryApiKeyStore,
|
|
28434
29718
|
createInMemoryApiClientStore,
|
|
28435
29719
|
createInMemoryAgentRegistrationStore,
|
|
29720
|
+
createInMemoryAgentIdentityRegistrationStore,
|
|
29721
|
+
createInMemoryAgentIdentityAssertionJtiStore,
|
|
28436
29722
|
createInMemoryAgentDelegationStore,
|
|
28437
29723
|
createInMemoryAccessTokenStore,
|
|
28438
29724
|
createFgaEngine,
|
|
@@ -28446,6 +29732,9 @@ export {
|
|
|
28446
29732
|
createApiKey,
|
|
28447
29733
|
createApiClient,
|
|
28448
29734
|
createAnonymousSession,
|
|
29735
|
+
createAgentRegistrationCredentialVerifier,
|
|
29736
|
+
createAgentRegistrationClient,
|
|
29737
|
+
createAgentIdentityAssertionVerifier,
|
|
28449
29738
|
createActionPipeline,
|
|
28450
29739
|
createAbuseGuard,
|
|
28451
29740
|
consumePushedRequest,
|
|
@@ -28453,6 +29742,7 @@ export {
|
|
|
28453
29742
|
constantTimeEqual,
|
|
28454
29743
|
computeCertThumbprint,
|
|
28455
29744
|
complianceRoutes,
|
|
29745
|
+
completeAgentClaim,
|
|
28456
29746
|
clientIdMetadataToOAuthClient,
|
|
28457
29747
|
check2 as check,
|
|
28458
29748
|
buildStatusClaim,
|
|
@@ -28460,6 +29750,7 @@ export {
|
|
|
28460
29750
|
buildHolderKeyBindingJwt,
|
|
28461
29751
|
buildClientProviders,
|
|
28462
29752
|
blockMigrations,
|
|
29753
|
+
beginAgentClaim,
|
|
28463
29754
|
base32Encode,
|
|
28464
29755
|
base32Decode,
|
|
28465
29756
|
authProviderOption,
|
|
@@ -28475,7 +29766,10 @@ export {
|
|
|
28475
29766
|
apiKeysRoutes,
|
|
28476
29767
|
apiClientsTable,
|
|
28477
29768
|
agentRegistrationsTable,
|
|
29769
|
+
agentRegistrationEndpoints,
|
|
29770
|
+
agentRegistrationDiscoveryMetadata,
|
|
28478
29771
|
agentProtectedResourceMetadata,
|
|
29772
|
+
agentIdentityRegistrationsTable,
|
|
28479
29773
|
agentHasScopes,
|
|
28480
29774
|
agentDelegationsTable,
|
|
28481
29775
|
agentAuthPlugin,
|
|
@@ -28526,8 +29820,11 @@ export {
|
|
|
28526
29820
|
DEFAULT_AGENT_RESOURCE_METADATA_ROUTE,
|
|
28527
29821
|
CLIENT_ASSERTION_TYPE,
|
|
28528
29822
|
CIBA_GRANT_TYPE,
|
|
28529
|
-
AuthIdentityConflictError
|
|
29823
|
+
AuthIdentityConflictError,
|
|
29824
|
+
AGENT_IDENTITY_ASSERTION_TYPE,
|
|
29825
|
+
AGENT_IDENTITY_ASSERTION_GRANT_TYPE,
|
|
29826
|
+
AGENT_CLAIM_GRANT_TYPE
|
|
28530
29827
|
};
|
|
28531
29828
|
|
|
28532
|
-
//# debugId=
|
|
29829
|
+
//# debugId=A2D2B9523667A9C564756E2164756E21
|
|
28533
29830
|
//# sourceMappingURL=index.js.map
|