@absolutejs/auth 0.78.0 → 0.80.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -0
- package/README.md +48 -0
- package/changelog.json +44 -0
- package/dist/apikeys/config.d.ts +1 -1
- package/dist/apikeys/tokenRoutes.d.ts +7 -0
- package/dist/cli/migrate.js +46 -1
- package/dist/cli/migrate.js.map +6 -5
- package/dist/cli/setup.d.ts +12 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +665 -605
- package/dist/index.js.map +13 -11
- package/dist/linkedProviders/credentialError.d.ts +7 -0
- package/dist/linkedProviders/index.d.ts +1 -0
- package/dist/linkedProviders/index.js +57 -10
- package/dist/linkedProviders/index.js.map +6 -5
- package/dist/manifest.js +13 -17
- package/dist/manifest.js.map +3 -3
- package/dist/manifest.json +9 -14
- package/dist/server.js +664 -605
- package/dist/server.js.map +13 -11
- package/dist/session/neonStore.d.ts +4 -0
- package/dist/types.d.ts +1 -1
- package/package.json +11 -7
package/dist/server.js
CHANGED
|
@@ -4023,7 +4023,7 @@ import { Elysia, t } from "elysia";
|
|
|
4023
4023
|
// src/apikeys/config.ts
|
|
4024
4024
|
init_constants();
|
|
4025
4025
|
init_crypto();
|
|
4026
|
-
var DEFAULT_TOKEN_ROUTE = "/
|
|
4026
|
+
var DEFAULT_TOKEN_ROUTE = "/auth/api/token";
|
|
4027
4027
|
var ACCESS_TOKEN_PREFIX = "at_";
|
|
4028
4028
|
var API_KEY_PREFIX = "sk_";
|
|
4029
4029
|
var BEARER_PREFIX = "Bearer ";
|
|
@@ -4235,6 +4235,602 @@ var apiKeysRoutes = ({
|
|
|
4235
4235
|
});
|
|
4236
4236
|
};
|
|
4237
4237
|
|
|
4238
|
+
// src/oidc/config.ts
|
|
4239
|
+
init_constants();
|
|
4240
|
+
init_crypto();
|
|
4241
|
+
|
|
4242
|
+
// src/oidc/keys.ts
|
|
4243
|
+
var ENCODER = new TextEncoder;
|
|
4244
|
+
var ES256 = { hash: "SHA-256", name: "ECDSA" };
|
|
4245
|
+
var KEY_PARAMS = { name: "ECDSA", namedCurve: "P-256" };
|
|
4246
|
+
var ES256_JOSE_SIGNATURE_BYTES = 64;
|
|
4247
|
+
var toBase64Url = (bytes) => Buffer.from(bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes)).toString("base64url");
|
|
4248
|
+
var fromBase64Url = (value) => new Uint8Array(Buffer.from(value, "base64url"));
|
|
4249
|
+
var encodeSegment = (value) => Buffer.from(JSON.stringify(value)).toString("base64url");
|
|
4250
|
+
var decodeSegment = (segment) => {
|
|
4251
|
+
try {
|
|
4252
|
+
const value = JSON.parse(Buffer.from(segment, "base64url").toString("utf8"));
|
|
4253
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
4254
|
+
return;
|
|
4255
|
+
}
|
|
4256
|
+
return Object.fromEntries(Object.entries(value));
|
|
4257
|
+
} catch {
|
|
4258
|
+
return;
|
|
4259
|
+
}
|
|
4260
|
+
};
|
|
4261
|
+
var generateSigningKey = async () => {
|
|
4262
|
+
const pair = await crypto.subtle.generateKey(KEY_PARAMS, true, [
|
|
4263
|
+
"sign",
|
|
4264
|
+
"verify"
|
|
4265
|
+
]);
|
|
4266
|
+
const privateJwk = await crypto.subtle.exportKey("jwk", pair.privateKey);
|
|
4267
|
+
const publicJwk = await crypto.subtle.exportKey("jwk", pair.publicKey);
|
|
4268
|
+
return { kid: await jwkThumbprint(publicJwk), privateJwk, publicJwk };
|
|
4269
|
+
};
|
|
4270
|
+
var jwkThumbprint = async (jwk) => {
|
|
4271
|
+
const canonical = JSON.stringify({
|
|
4272
|
+
crv: jwk.crv,
|
|
4273
|
+
kty: jwk.kty,
|
|
4274
|
+
x: jwk.x,
|
|
4275
|
+
y: jwk.y
|
|
4276
|
+
});
|
|
4277
|
+
return toBase64Url(await crypto.subtle.digest("SHA-256", ENCODER.encode(canonical)));
|
|
4278
|
+
};
|
|
4279
|
+
var signJwt = async (payload, signing, typ = "JWT") => {
|
|
4280
|
+
const input = `${encodeSegment({ alg: "ES256", kid: signing.kid, typ })}.${encodeSegment(payload)}`;
|
|
4281
|
+
const encoded = ENCODER.encode(input);
|
|
4282
|
+
let signature;
|
|
4283
|
+
if (signing.sign !== undefined) {
|
|
4284
|
+
signature = await signing.sign(encoded);
|
|
4285
|
+
} else {
|
|
4286
|
+
const key = await crypto.subtle.importKey("jwk", signing.privateJwk, KEY_PARAMS, false, ["sign"]);
|
|
4287
|
+
signature = await crypto.subtle.sign(ES256, key, encoded);
|
|
4288
|
+
}
|
|
4289
|
+
if (signature.byteLength !== ES256_JOSE_SIGNATURE_BYTES) {
|
|
4290
|
+
throw new Error("ES256 signer must return a 64-byte JOSE signature");
|
|
4291
|
+
}
|
|
4292
|
+
return `${input}.${toBase64Url(signature)}`;
|
|
4293
|
+
};
|
|
4294
|
+
var toPublicJwk = (key) => ({
|
|
4295
|
+
alg: "ES256",
|
|
4296
|
+
crv: key.publicJwk.crv,
|
|
4297
|
+
kid: key.kid,
|
|
4298
|
+
kty: key.publicJwk.kty,
|
|
4299
|
+
use: "sig",
|
|
4300
|
+
x: key.publicJwk.x,
|
|
4301
|
+
y: key.publicJwk.y
|
|
4302
|
+
});
|
|
4303
|
+
var verifyJwt = async (token, publicJwk) => {
|
|
4304
|
+
const [headerSegment, payloadSegment, signatureSegment] = token.split(".");
|
|
4305
|
+
if (headerSegment === undefined || payloadSegment === undefined || signatureSegment === undefined) {
|
|
4306
|
+
return;
|
|
4307
|
+
}
|
|
4308
|
+
const key = await crypto.subtle.importKey("jwk", publicJwk, KEY_PARAMS, false, ["verify"]);
|
|
4309
|
+
const valid = await crypto.subtle.verify(ES256, key, fromBase64Url(signatureSegment), ENCODER.encode(`${headerSegment}.${payloadSegment}`));
|
|
4310
|
+
if (!valid)
|
|
4311
|
+
return;
|
|
4312
|
+
const header = decodeSegment(headerSegment);
|
|
4313
|
+
const payload = decodeSegment(payloadSegment);
|
|
4314
|
+
if (header === undefined || payload === undefined)
|
|
4315
|
+
return;
|
|
4316
|
+
return {
|
|
4317
|
+
header,
|
|
4318
|
+
payload
|
|
4319
|
+
};
|
|
4320
|
+
};
|
|
4321
|
+
var signingVerificationKeys = (active, previous = []) => {
|
|
4322
|
+
const keys = [active, ...previous];
|
|
4323
|
+
const keyIds = new Set(keys.map(({ kid }) => kid));
|
|
4324
|
+
if (keyIds.size !== keys.length)
|
|
4325
|
+
throw new Error("OIDC signing key IDs must be unique");
|
|
4326
|
+
return keys;
|
|
4327
|
+
};
|
|
4328
|
+
var verifyJwtWithKeys = async (token, keys) => {
|
|
4329
|
+
const [headerSegment] = token.split(".");
|
|
4330
|
+
if (!headerSegment)
|
|
4331
|
+
return;
|
|
4332
|
+
const header = decodeSegment(headerSegment);
|
|
4333
|
+
const kid = header?.kid;
|
|
4334
|
+
if (typeof kid !== "string" || kid.length === 0)
|
|
4335
|
+
return;
|
|
4336
|
+
const key = keys.find((candidate) => candidate.kid === kid);
|
|
4337
|
+
if (!key)
|
|
4338
|
+
return;
|
|
4339
|
+
return verifyJwt(token, key.publicJwk);
|
|
4340
|
+
};
|
|
4341
|
+
|
|
4342
|
+
// src/oidc/config.ts
|
|
4343
|
+
var DEFAULT_OIDC_ROUTE = "/oauth2";
|
|
4344
|
+
var MS_PER_SECOND = 1000;
|
|
4345
|
+
var TOKEN_BYTES2 = 32;
|
|
4346
|
+
var REFRESH_TTL_DAYS = 30;
|
|
4347
|
+
var DEFAULT_ACCESS_TOKEN_TTL_MS2 = MILLISECONDS_IN_AN_HOUR;
|
|
4348
|
+
var DEFAULT_ID_TOKEN_TTL_MS = MILLISECONDS_IN_AN_HOUR;
|
|
4349
|
+
var DEFAULT_REFRESH_TOKEN_TTL_MS = MILLISECONDS_IN_A_DAY * REFRESH_TTL_DAYS;
|
|
4350
|
+
var resolveAccessTtl = (ttl, scopes) => {
|
|
4351
|
+
if (typeof ttl === "function")
|
|
4352
|
+
return ttl({ scopes });
|
|
4353
|
+
return ttl ?? DEFAULT_ACCESS_TOKEN_TTL_MS2;
|
|
4354
|
+
};
|
|
4355
|
+
var nowSeconds = (milliseconds) => Math.floor(milliseconds / MS_PER_SECOND);
|
|
4356
|
+
var narrowScopes = (available, requested) => requested === undefined || requested.length === 0 ? available : requested.filter((scope) => available.includes(scope));
|
|
4357
|
+
var RESERVED_ACCESS_CLAIMS = new Set([
|
|
4358
|
+
"act",
|
|
4359
|
+
"aud",
|
|
4360
|
+
"client_id",
|
|
4361
|
+
"cnf",
|
|
4362
|
+
"exp",
|
|
4363
|
+
"iat",
|
|
4364
|
+
"iss",
|
|
4365
|
+
"jti",
|
|
4366
|
+
"scope",
|
|
4367
|
+
"sub",
|
|
4368
|
+
"token_use"
|
|
4369
|
+
]);
|
|
4370
|
+
var buildAccessClaims = ({
|
|
4371
|
+
act,
|
|
4372
|
+
audience,
|
|
4373
|
+
clientCertThumbprint,
|
|
4374
|
+
clientId,
|
|
4375
|
+
dpopJkt,
|
|
4376
|
+
extraClaims,
|
|
4377
|
+
issuer,
|
|
4378
|
+
now,
|
|
4379
|
+
scopes,
|
|
4380
|
+
sub,
|
|
4381
|
+
ttl
|
|
4382
|
+
}) => {
|
|
4383
|
+
const safeExtra = extraClaims === undefined ? {} : Object.fromEntries(Object.entries(extraClaims).filter(([key]) => !RESERVED_ACCESS_CLAIMS.has(key)));
|
|
4384
|
+
const claims = {
|
|
4385
|
+
...safeExtra,
|
|
4386
|
+
aud: audience ?? clientId,
|
|
4387
|
+
client_id: clientId,
|
|
4388
|
+
exp: nowSeconds(now + ttl),
|
|
4389
|
+
iat: nowSeconds(now),
|
|
4390
|
+
iss: issuer,
|
|
4391
|
+
jti: crypto.randomUUID(),
|
|
4392
|
+
scope: scopes.join(" "),
|
|
4393
|
+
sub,
|
|
4394
|
+
token_use: "access"
|
|
4395
|
+
};
|
|
4396
|
+
if (act !== undefined)
|
|
4397
|
+
claims.act = act;
|
|
4398
|
+
const cnf = {
|
|
4399
|
+
...dpopJkt === undefined ? {} : { jkt: dpopJkt },
|
|
4400
|
+
...clientCertThumbprint === undefined ? {} : { "x5t#S256": clientCertThumbprint }
|
|
4401
|
+
};
|
|
4402
|
+
if (Object.keys(cnf).length > 0) {
|
|
4403
|
+
claims.cnf = cnf;
|
|
4404
|
+
}
|
|
4405
|
+
return claims;
|
|
4406
|
+
};
|
|
4407
|
+
var exchangeToken = async ({
|
|
4408
|
+
actorClientId,
|
|
4409
|
+
audience,
|
|
4410
|
+
config,
|
|
4411
|
+
dpopJkt,
|
|
4412
|
+
now = Date.now(),
|
|
4413
|
+
requestedScopes,
|
|
4414
|
+
subjectToken
|
|
4415
|
+
}) => {
|
|
4416
|
+
const verified = await verifyJwtWithKeys(subjectToken, signingVerificationKeys(config.signingKey, config.previousSigningKeys));
|
|
4417
|
+
const payload = verified?.payload;
|
|
4418
|
+
if (payload === undefined || typeof payload.sub !== "string" || typeof payload.exp !== "number" || payload.exp <= nowSeconds(now)) {
|
|
4419
|
+
return { error: "invalid_grant", ok: false };
|
|
4420
|
+
}
|
|
4421
|
+
const available = typeof payload.scope === "string" ? payload.scope.split(" ") : [];
|
|
4422
|
+
if (requestedScopes?.some((scope) => !available.includes(scope)) === true) {
|
|
4423
|
+
return { error: "invalid_scope", ok: false };
|
|
4424
|
+
}
|
|
4425
|
+
const scopes = narrowScopes(available, requestedScopes);
|
|
4426
|
+
const ttl = resolveAccessTtl(config.accessTokenTtlMs, scopes);
|
|
4427
|
+
const extraClaims = await config.getAccessTokenClaims?.({
|
|
4428
|
+
audience,
|
|
4429
|
+
clientId: actorClientId,
|
|
4430
|
+
scopes,
|
|
4431
|
+
sub: payload.sub
|
|
4432
|
+
});
|
|
4433
|
+
return {
|
|
4434
|
+
accessToken: await signJwt(buildAccessClaims({
|
|
4435
|
+
act: { sub: actorClientId },
|
|
4436
|
+
audience,
|
|
4437
|
+
clientId: actorClientId,
|
|
4438
|
+
dpopJkt,
|
|
4439
|
+
extraClaims,
|
|
4440
|
+
issuer: config.issuer,
|
|
4441
|
+
now,
|
|
4442
|
+
scopes,
|
|
4443
|
+
sub: payload.sub,
|
|
4444
|
+
ttl
|
|
4445
|
+
}), config.signingKey),
|
|
4446
|
+
expiresIn: Math.floor(ttl / MS_PER_SECOND),
|
|
4447
|
+
ok: true,
|
|
4448
|
+
scope: scopes.join(" ")
|
|
4449
|
+
};
|
|
4450
|
+
};
|
|
4451
|
+
var issueTokenSet = async ({
|
|
4452
|
+
acr,
|
|
4453
|
+
audience,
|
|
4454
|
+
claims,
|
|
4455
|
+
clientCertThumbprint,
|
|
4456
|
+
clientId,
|
|
4457
|
+
config,
|
|
4458
|
+
dpopJkt,
|
|
4459
|
+
familyId,
|
|
4460
|
+
nonce,
|
|
4461
|
+
now = Date.now(),
|
|
4462
|
+
persistRefreshToken,
|
|
4463
|
+
scopes,
|
|
4464
|
+
sub
|
|
4465
|
+
}) => {
|
|
4466
|
+
const accessTtl = resolveAccessTtl(config.accessTokenTtlMs, scopes);
|
|
4467
|
+
const idTtl = config.idTokenTtlMs ?? DEFAULT_ID_TOKEN_TTL_MS;
|
|
4468
|
+
const refreshTtl = config.refreshTokenTtlMs ?? DEFAULT_REFRESH_TOKEN_TTL_MS;
|
|
4469
|
+
const accessExtra = await config.getAccessTokenClaims?.({
|
|
4470
|
+
audience,
|
|
4471
|
+
clientId,
|
|
4472
|
+
scopes,
|
|
4473
|
+
sub
|
|
4474
|
+
});
|
|
4475
|
+
const accessPayload = buildAccessClaims({
|
|
4476
|
+
audience,
|
|
4477
|
+
clientCertThumbprint,
|
|
4478
|
+
clientId,
|
|
4479
|
+
dpopJkt,
|
|
4480
|
+
extraClaims: { ...accessExtra, ...acr === undefined ? {} : { acr } },
|
|
4481
|
+
issuer: config.issuer,
|
|
4482
|
+
now,
|
|
4483
|
+
scopes,
|
|
4484
|
+
sub,
|
|
4485
|
+
ttl: accessTtl
|
|
4486
|
+
});
|
|
4487
|
+
const idPayload = {
|
|
4488
|
+
...claims,
|
|
4489
|
+
aud: clientId,
|
|
4490
|
+
exp: nowSeconds(now + idTtl),
|
|
4491
|
+
iat: nowSeconds(now),
|
|
4492
|
+
iss: config.issuer,
|
|
4493
|
+
sub
|
|
4494
|
+
};
|
|
4495
|
+
if (nonce !== undefined)
|
|
4496
|
+
idPayload.nonce = nonce;
|
|
4497
|
+
if (acr !== undefined)
|
|
4498
|
+
idPayload.acr = acr;
|
|
4499
|
+
const refreshToken = generateSecureToken(TOKEN_BYTES2);
|
|
4500
|
+
const refreshRecord = {
|
|
4501
|
+
acr,
|
|
4502
|
+
audience,
|
|
4503
|
+
claims,
|
|
4504
|
+
clientId,
|
|
4505
|
+
createdAt: now,
|
|
4506
|
+
dpopJkt,
|
|
4507
|
+
expiresAt: now + refreshTtl,
|
|
4508
|
+
familyId: familyId ?? crypto.randomUUID(),
|
|
4509
|
+
scopes,
|
|
4510
|
+
tokenHash: await hashToken(refreshToken),
|
|
4511
|
+
userId: sub
|
|
4512
|
+
};
|
|
4513
|
+
if (persistRefreshToken)
|
|
4514
|
+
await persistRefreshToken(refreshRecord);
|
|
4515
|
+
else
|
|
4516
|
+
await config.refreshTokenStore.saveToken(refreshRecord);
|
|
4517
|
+
return {
|
|
4518
|
+
access_token: await signJwt(accessPayload, config.signingKey),
|
|
4519
|
+
expires_in: Math.floor(accessTtl / MS_PER_SECOND),
|
|
4520
|
+
id_token: await signJwt(idPayload, config.signingKey),
|
|
4521
|
+
refresh_token: refreshToken,
|
|
4522
|
+
scope: scopes.join(" "),
|
|
4523
|
+
token_type: dpopJkt === undefined ? "Bearer" : "DPoP"
|
|
4524
|
+
};
|
|
4525
|
+
};
|
|
4526
|
+
var mcpProtectedResourceMetadata = ({
|
|
4527
|
+
issuer,
|
|
4528
|
+
resource,
|
|
4529
|
+
scopes
|
|
4530
|
+
}) => ({
|
|
4531
|
+
authorization_servers: [issuer],
|
|
4532
|
+
resource,
|
|
4533
|
+
scopes_supported: scopes ?? []
|
|
4534
|
+
});
|
|
4535
|
+
var verifyPkce = async (codeVerifier, codeChallenge) => await hashToken(codeVerifier) === codeChallenge;
|
|
4536
|
+
var inactive = { active: false };
|
|
4537
|
+
var introspectToken = async ({
|
|
4538
|
+
config,
|
|
4539
|
+
hint,
|
|
4540
|
+
now = Date.now(),
|
|
4541
|
+
token
|
|
4542
|
+
}) => {
|
|
4543
|
+
if (hint !== "refresh_token") {
|
|
4544
|
+
const verified = await verifyJwtWithKeys(token, signingVerificationKeys(config.signingKey, config.previousSigningKeys));
|
|
4545
|
+
const payload = verified?.payload;
|
|
4546
|
+
if (payload !== undefined && typeof payload.sub === "string" && typeof payload.exp === "number" && payload.exp > nowSeconds(now)) {
|
|
4547
|
+
return {
|
|
4548
|
+
active: true,
|
|
4549
|
+
client_id: typeof payload.client_id === "string" ? payload.client_id : "",
|
|
4550
|
+
exp: payload.exp,
|
|
4551
|
+
iat: typeof payload.iat === "number" ? payload.iat : 0,
|
|
4552
|
+
scope: typeof payload.scope === "string" ? payload.scope : "",
|
|
4553
|
+
sub: payload.sub,
|
|
4554
|
+
token_type: "access_token"
|
|
4555
|
+
};
|
|
4556
|
+
}
|
|
4557
|
+
}
|
|
4558
|
+
if (hint !== "access_token") {
|
|
4559
|
+
const refresh = await config.refreshTokenStore.getToken(await hashToken(token));
|
|
4560
|
+
if (refresh && refresh.expiresAt > now) {
|
|
4561
|
+
return {
|
|
4562
|
+
active: true,
|
|
4563
|
+
client_id: refresh.clientId,
|
|
4564
|
+
exp: nowSeconds(refresh.expiresAt),
|
|
4565
|
+
iat: nowSeconds(refresh.createdAt),
|
|
4566
|
+
scope: refresh.scopes.join(" "),
|
|
4567
|
+
sub: refresh.userId,
|
|
4568
|
+
token_type: "refresh_token"
|
|
4569
|
+
};
|
|
4570
|
+
}
|
|
4571
|
+
}
|
|
4572
|
+
return inactive;
|
|
4573
|
+
};
|
|
4574
|
+
var revokeRefreshToken = async (config, token) => {
|
|
4575
|
+
const consumed = await config.refreshTokenStore.consumeToken(await hashToken(token));
|
|
4576
|
+
return consumed !== undefined;
|
|
4577
|
+
};
|
|
4578
|
+
var DEVICE_CODE_BYTES = 32;
|
|
4579
|
+
var USER_CODE_HALF_LENGTH = 4;
|
|
4580
|
+
var USER_CODE_ALPHABET = "BCDFGHJKLMNPQRSTVWXZ23456789";
|
|
4581
|
+
var DEFAULT_DEVICE_CODE_TTL_MINUTES = 15;
|
|
4582
|
+
var DEFAULT_DEVICE_CODE_TTL_MS = MILLISECONDS_IN_A_MINUTE * DEFAULT_DEVICE_CODE_TTL_MINUTES;
|
|
4583
|
+
var DEFAULT_DEVICE_POLL_INTERVAL_SECONDS = 5;
|
|
4584
|
+
var generateUserCode = () => {
|
|
4585
|
+
const length = USER_CODE_HALF_LENGTH * 2;
|
|
4586
|
+
const random = crypto.getRandomValues(new Uint8Array(length));
|
|
4587
|
+
let code = "";
|
|
4588
|
+
for (const byte of random) {
|
|
4589
|
+
code += USER_CODE_ALPHABET[byte % USER_CODE_ALPHABET.length];
|
|
4590
|
+
}
|
|
4591
|
+
return `${code.slice(0, USER_CODE_HALF_LENGTH)}-${code.slice(USER_CODE_HALF_LENGTH)}`;
|
|
4592
|
+
};
|
|
4593
|
+
var issueDeviceAuthorization = async ({
|
|
4594
|
+
audience,
|
|
4595
|
+
clientId,
|
|
4596
|
+
config,
|
|
4597
|
+
now = Date.now(),
|
|
4598
|
+
requestedScopes
|
|
4599
|
+
}) => {
|
|
4600
|
+
if (!config.deviceAuthorizationStore) {
|
|
4601
|
+
throw new Error("oidc.deviceAuthorizationStore is not configured \u2014 cannot start a device flow");
|
|
4602
|
+
}
|
|
4603
|
+
const deviceCode = generateSecureToken(DEVICE_CODE_BYTES);
|
|
4604
|
+
const userCode = generateUserCode();
|
|
4605
|
+
const ttl = config.deviceCodeTtlMs ?? DEFAULT_DEVICE_CODE_TTL_MS;
|
|
4606
|
+
const interval = config.devicePollIntervalSeconds ?? DEFAULT_DEVICE_POLL_INTERVAL_SECONDS;
|
|
4607
|
+
await config.deviceAuthorizationStore.saveDeviceAuthorization({
|
|
4608
|
+
audience,
|
|
4609
|
+
clientId,
|
|
4610
|
+
createdAt: now,
|
|
4611
|
+
deviceCodeHash: await hashToken(deviceCode),
|
|
4612
|
+
expiresAt: now + ttl,
|
|
4613
|
+
intervalSeconds: interval,
|
|
4614
|
+
scopes: requestedScopes,
|
|
4615
|
+
status: "pending",
|
|
4616
|
+
userCode
|
|
4617
|
+
});
|
|
4618
|
+
const verificationUri = `${config.issuer}${config.oidcRoute ?? DEFAULT_OIDC_ROUTE}/device`;
|
|
4619
|
+
return {
|
|
4620
|
+
device_code: deviceCode,
|
|
4621
|
+
expires_in: Math.floor(ttl / MS_PER_SECOND),
|
|
4622
|
+
interval,
|
|
4623
|
+
user_code: userCode,
|
|
4624
|
+
verification_uri: verificationUri,
|
|
4625
|
+
verification_uri_complete: `${verificationUri}?user_code=${encodeURIComponent(userCode)}`
|
|
4626
|
+
};
|
|
4627
|
+
};
|
|
4628
|
+
var decideDeviceAuthorization = async (config, userCode, approval) => {
|
|
4629
|
+
if (!config.deviceAuthorizationStore) {
|
|
4630
|
+
return { error: "not_configured", ok: false };
|
|
4631
|
+
}
|
|
4632
|
+
const record = await config.deviceAuthorizationStore.findByUserCode(userCode);
|
|
4633
|
+
if (!record)
|
|
4634
|
+
return { error: "invalid_user_code", ok: false };
|
|
4635
|
+
if (record.expiresAt < Date.now()) {
|
|
4636
|
+
return { error: "expired_token", ok: false };
|
|
4637
|
+
}
|
|
4638
|
+
if (record.status !== "pending") {
|
|
4639
|
+
return { error: "already_decided", ok: false };
|
|
4640
|
+
}
|
|
4641
|
+
await config.deviceAuthorizationStore.updateStatus(record.deviceCodeHash, approval.status, approval.userSub);
|
|
4642
|
+
if (approval.status === "approved" && approval.userSub !== undefined) {
|
|
4643
|
+
await config.onDeviceAuthorizationApproved?.({
|
|
4644
|
+
clientId: record.clientId,
|
|
4645
|
+
scopes: record.scopes,
|
|
4646
|
+
userSub: approval.userSub
|
|
4647
|
+
});
|
|
4648
|
+
}
|
|
4649
|
+
return { ok: true };
|
|
4650
|
+
};
|
|
4651
|
+
var approveDeviceAuthorization = async ({
|
|
4652
|
+
config,
|
|
4653
|
+
userCode,
|
|
4654
|
+
userSub
|
|
4655
|
+
}) => decideDeviceAuthorization(config, userCode, {
|
|
4656
|
+
status: "approved",
|
|
4657
|
+
userSub
|
|
4658
|
+
});
|
|
4659
|
+
var denyDeviceAuthorization = async ({
|
|
4660
|
+
config,
|
|
4661
|
+
userCode
|
|
4662
|
+
}) => decideDeviceAuthorization(config, userCode, { status: "denied" });
|
|
4663
|
+
var exchangeDeviceCode = async ({
|
|
4664
|
+
clientId,
|
|
4665
|
+
config,
|
|
4666
|
+
deviceCode,
|
|
4667
|
+
dpopJkt,
|
|
4668
|
+
now = Date.now()
|
|
4669
|
+
}) => {
|
|
4670
|
+
if (!config.deviceAuthorizationStore) {
|
|
4671
|
+
return { error: "invalid_grant", ok: false };
|
|
4672
|
+
}
|
|
4673
|
+
const deviceCodeHash = await hashToken(deviceCode);
|
|
4674
|
+
const record = await config.deviceAuthorizationStore.findByDeviceCodeHash(deviceCodeHash);
|
|
4675
|
+
if (!record || record.clientId !== clientId) {
|
|
4676
|
+
return { error: "invalid_grant", ok: false };
|
|
4677
|
+
}
|
|
4678
|
+
if (record.expiresAt < now) {
|
|
4679
|
+
await config.deviceAuthorizationStore.deleteByDeviceCodeHash(deviceCodeHash);
|
|
4680
|
+
return { error: "expired_token", ok: false };
|
|
4681
|
+
}
|
|
4682
|
+
if (record.status === "pending") {
|
|
4683
|
+
return { error: "authorization_pending", ok: false };
|
|
4684
|
+
}
|
|
4685
|
+
if (record.status === "denied" || record.userSub === undefined) {
|
|
4686
|
+
return { error: "access_denied", ok: false };
|
|
4687
|
+
}
|
|
4688
|
+
await config.deviceAuthorizationStore.deleteByDeviceCodeHash(deviceCodeHash);
|
|
4689
|
+
const tokenSet = await issueTokenSet({
|
|
4690
|
+
audience: record.audience,
|
|
4691
|
+
clientId,
|
|
4692
|
+
config,
|
|
4693
|
+
dpopJkt,
|
|
4694
|
+
now,
|
|
4695
|
+
scopes: record.scopes,
|
|
4696
|
+
sub: record.userSub
|
|
4697
|
+
});
|
|
4698
|
+
return { ...tokenSet, ok: true };
|
|
4699
|
+
};
|
|
4700
|
+
var AUTH_REQ_ID_BYTES = 32;
|
|
4701
|
+
var DEFAULT_BACKCHANNEL_TTL_MINUTES = 10;
|
|
4702
|
+
var DEFAULT_BACKCHANNEL_TTL_MS = MILLISECONDS_IN_A_MINUTE * DEFAULT_BACKCHANNEL_TTL_MINUTES;
|
|
4703
|
+
var DEFAULT_BACKCHANNEL_POLL_INTERVAL_SECONDS = 5;
|
|
4704
|
+
var CIBA_GRANT_TYPE = "urn:openid:params:grant-type:ciba";
|
|
4705
|
+
var issueBackchannelAuth = async ({
|
|
4706
|
+
clientId,
|
|
4707
|
+
config,
|
|
4708
|
+
loginHint,
|
|
4709
|
+
bindingMessage,
|
|
4710
|
+
now = Date.now(),
|
|
4711
|
+
requestedScopes
|
|
4712
|
+
}) => {
|
|
4713
|
+
if (!config.backchannelAuthStore || !config.resolveBackchannelUser) {
|
|
4714
|
+
return { error: "invalid_request", ok: false };
|
|
4715
|
+
}
|
|
4716
|
+
const client = await config.clientStore.findClient(clientId) ?? await config.resolveClientIdMetadata?.(clientId);
|
|
4717
|
+
if (!client)
|
|
4718
|
+
return { error: "invalid_client", ok: false };
|
|
4719
|
+
const resolved = await config.resolveBackchannelUser({
|
|
4720
|
+
client,
|
|
4721
|
+
loginHint
|
|
4722
|
+
});
|
|
4723
|
+
if (!resolved)
|
|
4724
|
+
return { error: "unknown_user_id", ok: false };
|
|
4725
|
+
const authReqId = generateSecureToken(AUTH_REQ_ID_BYTES);
|
|
4726
|
+
const ttl = config.backchannelAuthTtlMs ?? DEFAULT_BACKCHANNEL_TTL_MS;
|
|
4727
|
+
const interval = config.backchannelPollIntervalSeconds ?? DEFAULT_BACKCHANNEL_POLL_INTERVAL_SECONDS;
|
|
4728
|
+
await config.backchannelAuthStore.saveBackchannelAuth({
|
|
4729
|
+
authReqId,
|
|
4730
|
+
bindingMessage,
|
|
4731
|
+
clientId,
|
|
4732
|
+
createdAt: now,
|
|
4733
|
+
expiresAt: now + ttl,
|
|
4734
|
+
intervalSeconds: interval,
|
|
4735
|
+
scopes: requestedScopes,
|
|
4736
|
+
status: "pending",
|
|
4737
|
+
userSub: resolved.sub
|
|
4738
|
+
});
|
|
4739
|
+
await config.onBackchannelAuthRequest?.({
|
|
4740
|
+
authReqId,
|
|
4741
|
+
bindingMessage,
|
|
4742
|
+
clientId,
|
|
4743
|
+
scopes: requestedScopes,
|
|
4744
|
+
userSub: resolved.sub
|
|
4745
|
+
});
|
|
4746
|
+
return {
|
|
4747
|
+
auth_req_id: authReqId,
|
|
4748
|
+
expires_in: Math.floor(ttl / MS_PER_SECOND),
|
|
4749
|
+
interval,
|
|
4750
|
+
ok: true
|
|
4751
|
+
};
|
|
4752
|
+
};
|
|
4753
|
+
var decideBackchannel = async (config, authReqId, approval) => {
|
|
4754
|
+
if (!config.backchannelAuthStore) {
|
|
4755
|
+
return { error: "not_configured", ok: false };
|
|
4756
|
+
}
|
|
4757
|
+
const record = await config.backchannelAuthStore.findByAuthReqId(authReqId);
|
|
4758
|
+
if (!record)
|
|
4759
|
+
return { error: "invalid_auth_req_id", ok: false };
|
|
4760
|
+
if (record.expiresAt < Date.now()) {
|
|
4761
|
+
return { error: "expired_token", ok: false };
|
|
4762
|
+
}
|
|
4763
|
+
if (record.status !== "pending") {
|
|
4764
|
+
return { error: "already_decided", ok: false };
|
|
4765
|
+
}
|
|
4766
|
+
await config.backchannelAuthStore.updateStatus(authReqId, approval.status, approval.userSub ?? record.userSub);
|
|
4767
|
+
return { ok: true };
|
|
4768
|
+
};
|
|
4769
|
+
var approveBackchannelAuth = async ({
|
|
4770
|
+
authReqId,
|
|
4771
|
+
config,
|
|
4772
|
+
userSub
|
|
4773
|
+
}) => decideBackchannel(config, authReqId, {
|
|
4774
|
+
status: "approved",
|
|
4775
|
+
userSub
|
|
4776
|
+
});
|
|
4777
|
+
var denyBackchannelAuth = async ({
|
|
4778
|
+
authReqId,
|
|
4779
|
+
config
|
|
4780
|
+
}) => decideBackchannel(config, authReqId, { status: "denied" });
|
|
4781
|
+
var exchangeBackchannelAuth = async ({
|
|
4782
|
+
authReqId,
|
|
4783
|
+
clientCertThumbprint,
|
|
4784
|
+
clientId,
|
|
4785
|
+
config,
|
|
4786
|
+
dpopJkt,
|
|
4787
|
+
now = Date.now()
|
|
4788
|
+
}) => {
|
|
4789
|
+
if (!config.backchannelAuthStore) {
|
|
4790
|
+
return { error: "invalid_grant", ok: false };
|
|
4791
|
+
}
|
|
4792
|
+
const record = await config.backchannelAuthStore.findByAuthReqId(authReqId);
|
|
4793
|
+
if (!record || record.clientId !== clientId) {
|
|
4794
|
+
return { error: "invalid_grant", ok: false };
|
|
4795
|
+
}
|
|
4796
|
+
if (record.expiresAt < now) {
|
|
4797
|
+
await config.backchannelAuthStore.deleteByAuthReqId(authReqId);
|
|
4798
|
+
return { error: "expired_token", ok: false };
|
|
4799
|
+
}
|
|
4800
|
+
if (record.lastPolledAt !== undefined && now - record.lastPolledAt < record.intervalSeconds * MS_PER_SECOND) {
|
|
4801
|
+
return { error: "slow_down", ok: false };
|
|
4802
|
+
}
|
|
4803
|
+
await config.backchannelAuthStore.recordPoll(authReqId, now);
|
|
4804
|
+
if (record.status === "pending") {
|
|
4805
|
+
return { error: "authorization_pending", ok: false };
|
|
4806
|
+
}
|
|
4807
|
+
if (record.status === "denied" || record.userSub === undefined) {
|
|
4808
|
+
return { error: "access_denied", ok: false };
|
|
4809
|
+
}
|
|
4810
|
+
await config.backchannelAuthStore.deleteByAuthReqId(authReqId);
|
|
4811
|
+
const tokenSet = await issueTokenSet({
|
|
4812
|
+
clientCertThumbprint,
|
|
4813
|
+
clientId,
|
|
4814
|
+
config,
|
|
4815
|
+
dpopJkt,
|
|
4816
|
+
now,
|
|
4817
|
+
scopes: record.scopes,
|
|
4818
|
+
sub: record.userSub
|
|
4819
|
+
});
|
|
4820
|
+
return { ...tokenSet, ok: true };
|
|
4821
|
+
};
|
|
4822
|
+
|
|
4823
|
+
// src/apikeys/tokenRoutes.ts
|
|
4824
|
+
var assertTokenRouteConfiguration = (apikeys, oidc) => {
|
|
4825
|
+
if (apikeys?.apiClientStore === undefined || apikeys.accessTokenStore === undefined || oidc === undefined)
|
|
4826
|
+
return;
|
|
4827
|
+
const apiTokenRoute = apikeys.tokenRoute ?? DEFAULT_TOKEN_ROUTE;
|
|
4828
|
+
const oidcTokenRoute = `${oidc.oidcRoute ?? DEFAULT_OIDC_ROUTE}/token`;
|
|
4829
|
+
if (apiTokenRoute.replace(/\/$/u, "") === oidcTokenRoute) {
|
|
4830
|
+
throw new Error(`Conflicting auth token routes: POST ${apiTokenRoute} is configured for both API client credentials and OIDC. Set apikeys.tokenRoute to a separate path (default: ${DEFAULT_TOKEN_ROUTE}).`);
|
|
4831
|
+
}
|
|
4832
|
+
};
|
|
4833
|
+
|
|
4238
4834
|
// src/agents/routes.ts
|
|
4239
4835
|
import { Elysia as Elysia3 } from "elysia";
|
|
4240
4836
|
|
|
@@ -4491,108 +5087,6 @@ one resource or transport is not authority for another.
|
|
|
4491
5087
|
// src/agents/registration.ts
|
|
4492
5088
|
init_constants();
|
|
4493
5089
|
init_crypto();
|
|
4494
|
-
|
|
4495
|
-
// src/oidc/keys.ts
|
|
4496
|
-
var ENCODER = new TextEncoder;
|
|
4497
|
-
var ES256 = { hash: "SHA-256", name: "ECDSA" };
|
|
4498
|
-
var KEY_PARAMS = { name: "ECDSA", namedCurve: "P-256" };
|
|
4499
|
-
var ES256_JOSE_SIGNATURE_BYTES = 64;
|
|
4500
|
-
var toBase64Url = (bytes) => Buffer.from(bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes)).toString("base64url");
|
|
4501
|
-
var fromBase64Url = (value) => new Uint8Array(Buffer.from(value, "base64url"));
|
|
4502
|
-
var encodeSegment = (value) => Buffer.from(JSON.stringify(value)).toString("base64url");
|
|
4503
|
-
var decodeSegment = (segment) => {
|
|
4504
|
-
try {
|
|
4505
|
-
const value = JSON.parse(Buffer.from(segment, "base64url").toString("utf8"));
|
|
4506
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
4507
|
-
return;
|
|
4508
|
-
}
|
|
4509
|
-
return Object.fromEntries(Object.entries(value));
|
|
4510
|
-
} catch {
|
|
4511
|
-
return;
|
|
4512
|
-
}
|
|
4513
|
-
};
|
|
4514
|
-
var generateSigningKey = async () => {
|
|
4515
|
-
const pair = await crypto.subtle.generateKey(KEY_PARAMS, true, [
|
|
4516
|
-
"sign",
|
|
4517
|
-
"verify"
|
|
4518
|
-
]);
|
|
4519
|
-
const privateJwk = await crypto.subtle.exportKey("jwk", pair.privateKey);
|
|
4520
|
-
const publicJwk = await crypto.subtle.exportKey("jwk", pair.publicKey);
|
|
4521
|
-
return { kid: await jwkThumbprint(publicJwk), privateJwk, publicJwk };
|
|
4522
|
-
};
|
|
4523
|
-
var jwkThumbprint = async (jwk) => {
|
|
4524
|
-
const canonical = JSON.stringify({
|
|
4525
|
-
crv: jwk.crv,
|
|
4526
|
-
kty: jwk.kty,
|
|
4527
|
-
x: jwk.x,
|
|
4528
|
-
y: jwk.y
|
|
4529
|
-
});
|
|
4530
|
-
return toBase64Url(await crypto.subtle.digest("SHA-256", ENCODER.encode(canonical)));
|
|
4531
|
-
};
|
|
4532
|
-
var signJwt = async (payload, signing, typ = "JWT") => {
|
|
4533
|
-
const input = `${encodeSegment({ alg: "ES256", kid: signing.kid, typ })}.${encodeSegment(payload)}`;
|
|
4534
|
-
const encoded = ENCODER.encode(input);
|
|
4535
|
-
let signature;
|
|
4536
|
-
if (signing.sign !== undefined) {
|
|
4537
|
-
signature = await signing.sign(encoded);
|
|
4538
|
-
} else {
|
|
4539
|
-
const key = await crypto.subtle.importKey("jwk", signing.privateJwk, KEY_PARAMS, false, ["sign"]);
|
|
4540
|
-
signature = await crypto.subtle.sign(ES256, key, encoded);
|
|
4541
|
-
}
|
|
4542
|
-
if (signature.byteLength !== ES256_JOSE_SIGNATURE_BYTES) {
|
|
4543
|
-
throw new Error("ES256 signer must return a 64-byte JOSE signature");
|
|
4544
|
-
}
|
|
4545
|
-
return `${input}.${toBase64Url(signature)}`;
|
|
4546
|
-
};
|
|
4547
|
-
var toPublicJwk = (key) => ({
|
|
4548
|
-
alg: "ES256",
|
|
4549
|
-
crv: key.publicJwk.crv,
|
|
4550
|
-
kid: key.kid,
|
|
4551
|
-
kty: key.publicJwk.kty,
|
|
4552
|
-
use: "sig",
|
|
4553
|
-
x: key.publicJwk.x,
|
|
4554
|
-
y: key.publicJwk.y
|
|
4555
|
-
});
|
|
4556
|
-
var verifyJwt = async (token, publicJwk) => {
|
|
4557
|
-
const [headerSegment, payloadSegment, signatureSegment] = token.split(".");
|
|
4558
|
-
if (headerSegment === undefined || payloadSegment === undefined || signatureSegment === undefined) {
|
|
4559
|
-
return;
|
|
4560
|
-
}
|
|
4561
|
-
const key = await crypto.subtle.importKey("jwk", publicJwk, KEY_PARAMS, false, ["verify"]);
|
|
4562
|
-
const valid = await crypto.subtle.verify(ES256, key, fromBase64Url(signatureSegment), ENCODER.encode(`${headerSegment}.${payloadSegment}`));
|
|
4563
|
-
if (!valid)
|
|
4564
|
-
return;
|
|
4565
|
-
const header = decodeSegment(headerSegment);
|
|
4566
|
-
const payload = decodeSegment(payloadSegment);
|
|
4567
|
-
if (header === undefined || payload === undefined)
|
|
4568
|
-
return;
|
|
4569
|
-
return {
|
|
4570
|
-
header,
|
|
4571
|
-
payload
|
|
4572
|
-
};
|
|
4573
|
-
};
|
|
4574
|
-
var signingVerificationKeys = (active, previous = []) => {
|
|
4575
|
-
const keys = [active, ...previous];
|
|
4576
|
-
const keyIds = new Set(keys.map(({ kid }) => kid));
|
|
4577
|
-
if (keyIds.size !== keys.length)
|
|
4578
|
-
throw new Error("OIDC signing key IDs must be unique");
|
|
4579
|
-
return keys;
|
|
4580
|
-
};
|
|
4581
|
-
var verifyJwtWithKeys = async (token, keys) => {
|
|
4582
|
-
const [headerSegment] = token.split(".");
|
|
4583
|
-
if (!headerSegment)
|
|
4584
|
-
return;
|
|
4585
|
-
const header = decodeSegment(headerSegment);
|
|
4586
|
-
const kid = header?.kid;
|
|
4587
|
-
if (typeof kid !== "string" || kid.length === 0)
|
|
4588
|
-
return;
|
|
4589
|
-
const key = keys.find((candidate) => candidate.kid === kid);
|
|
4590
|
-
if (!key)
|
|
4591
|
-
return;
|
|
4592
|
-
return verifyJwt(token, key.publicJwk);
|
|
4593
|
-
};
|
|
4594
|
-
|
|
4595
|
-
// src/agents/registration.ts
|
|
4596
5090
|
var AGENT_CLAIM_GRANT_TYPE = "urn:workos:agent-auth:grant-type:claim";
|
|
4597
5091
|
var AGENT_IDENTITY_ASSERTION_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:jwt-bearer";
|
|
4598
5092
|
var AGENT_IDENTITY_ASSERTION_TYPE = "urn:ietf:params:oauth:token-type:id-jag";
|
|
@@ -4603,12 +5097,12 @@ var DEFAULT_GUIDE_ROUTE2 = "/auth.md";
|
|
|
4603
5097
|
var DEFAULT_CLAIM_TTL_MS = 24 * 60 * MILLISECONDS_IN_A_MINUTE;
|
|
4604
5098
|
var DEFAULT_ATTEMPT_TTL_MS = 10 * MILLISECONDS_IN_A_MINUTE;
|
|
4605
5099
|
var DEFAULT_ASSERTION_TTL_MS = 60 * MILLISECONDS_IN_A_MINUTE;
|
|
4606
|
-
var
|
|
5100
|
+
var DEFAULT_ACCESS_TOKEN_TTL_MS3 = 15 * MILLISECONDS_IN_A_MINUTE;
|
|
4607
5101
|
var DEFAULT_MAX_AUTH_AGE_MS = 60 * MILLISECONDS_IN_A_MINUTE;
|
|
4608
5102
|
var DEFAULT_POLL_INTERVAL_SECONDS = 5;
|
|
4609
5103
|
var DEFAULT_MAX_CODE_ATTEMPTS = 5;
|
|
4610
5104
|
var MAX_CONCURRENT_UPDATE_RETRIES = 5;
|
|
4611
|
-
var
|
|
5105
|
+
var TOKEN_BYTES3 = 32;
|
|
4612
5106
|
var agentRegistrationDiscoveryMetadata = (config) => {
|
|
4613
5107
|
const registration = requiredRegistration(config);
|
|
4614
5108
|
const endpoints = agentRegistrationEndpoints(config);
|
|
@@ -4724,7 +5218,7 @@ var randomCode = () => {
|
|
|
4724
5218
|
const value = new DataView(bytes.buffer).getUint32(0) % 1e6;
|
|
4725
5219
|
return value.toString().padStart(6, "0");
|
|
4726
5220
|
};
|
|
4727
|
-
var makeSecret = (prefix) => `${prefix}_${generateSecureToken(
|
|
5221
|
+
var makeSecret = (prefix) => `${prefix}_${generateSecureToken(TOKEN_BYTES3)}`;
|
|
4728
5222
|
var makeAttempt = async ({
|
|
4729
5223
|
email,
|
|
4730
5224
|
now,
|
|
@@ -5053,9 +5547,9 @@ var completeAgentClaim = async (config, input, now = Date.now()) => {
|
|
|
5053
5547
|
};
|
|
5054
5548
|
var issueAgentAccessToken = async (config, flow, now) => {
|
|
5055
5549
|
const registration = requiredRegistration(config);
|
|
5056
|
-
const accessToken = `at_${generateSecureToken(
|
|
5550
|
+
const accessToken = `at_${generateSecureToken(TOKEN_BYTES3)}`;
|
|
5057
5551
|
const scopes = (flow.status === "claimed" ? registration.postClaimScopes : registration.preClaimScopes ?? []).filter((scope) => config.scopes.includes(scope));
|
|
5058
|
-
const expiresAt = now + (registration.tokenTtlMs ??
|
|
5552
|
+
const expiresAt = now + (registration.tokenTtlMs ?? DEFAULT_ACCESS_TOKEN_TTL_MS3);
|
|
5059
5553
|
await registration.accessTokenStore.saveToken({
|
|
5060
5554
|
clientId: flow.agentId,
|
|
5061
5555
|
createdAt: now,
|
|
@@ -8287,489 +8781,6 @@ init_constants();
|
|
|
8287
8781
|
init_crypto();
|
|
8288
8782
|
import { Elysia as Elysia24, t as t17 } from "elysia";
|
|
8289
8783
|
|
|
8290
|
-
// src/oidc/config.ts
|
|
8291
|
-
init_constants();
|
|
8292
|
-
init_crypto();
|
|
8293
|
-
var DEFAULT_OIDC_ROUTE = "/oauth2";
|
|
8294
|
-
var MS_PER_SECOND = 1000;
|
|
8295
|
-
var TOKEN_BYTES3 = 32;
|
|
8296
|
-
var REFRESH_TTL_DAYS = 30;
|
|
8297
|
-
var DEFAULT_ACCESS_TOKEN_TTL_MS3 = MILLISECONDS_IN_AN_HOUR;
|
|
8298
|
-
var DEFAULT_ID_TOKEN_TTL_MS = MILLISECONDS_IN_AN_HOUR;
|
|
8299
|
-
var DEFAULT_REFRESH_TOKEN_TTL_MS = MILLISECONDS_IN_A_DAY * REFRESH_TTL_DAYS;
|
|
8300
|
-
var resolveAccessTtl = (ttl, scopes) => {
|
|
8301
|
-
if (typeof ttl === "function")
|
|
8302
|
-
return ttl({ scopes });
|
|
8303
|
-
return ttl ?? DEFAULT_ACCESS_TOKEN_TTL_MS3;
|
|
8304
|
-
};
|
|
8305
|
-
var nowSeconds = (milliseconds) => Math.floor(milliseconds / MS_PER_SECOND);
|
|
8306
|
-
var narrowScopes = (available, requested) => requested === undefined || requested.length === 0 ? available : requested.filter((scope) => available.includes(scope));
|
|
8307
|
-
var RESERVED_ACCESS_CLAIMS = new Set([
|
|
8308
|
-
"act",
|
|
8309
|
-
"aud",
|
|
8310
|
-
"client_id",
|
|
8311
|
-
"cnf",
|
|
8312
|
-
"exp",
|
|
8313
|
-
"iat",
|
|
8314
|
-
"iss",
|
|
8315
|
-
"jti",
|
|
8316
|
-
"scope",
|
|
8317
|
-
"sub",
|
|
8318
|
-
"token_use"
|
|
8319
|
-
]);
|
|
8320
|
-
var buildAccessClaims = ({
|
|
8321
|
-
act,
|
|
8322
|
-
audience,
|
|
8323
|
-
clientCertThumbprint,
|
|
8324
|
-
clientId,
|
|
8325
|
-
dpopJkt,
|
|
8326
|
-
extraClaims,
|
|
8327
|
-
issuer,
|
|
8328
|
-
now,
|
|
8329
|
-
scopes,
|
|
8330
|
-
sub,
|
|
8331
|
-
ttl
|
|
8332
|
-
}) => {
|
|
8333
|
-
const safeExtra = extraClaims === undefined ? {} : Object.fromEntries(Object.entries(extraClaims).filter(([key]) => !RESERVED_ACCESS_CLAIMS.has(key)));
|
|
8334
|
-
const claims = {
|
|
8335
|
-
...safeExtra,
|
|
8336
|
-
aud: audience ?? clientId,
|
|
8337
|
-
client_id: clientId,
|
|
8338
|
-
exp: nowSeconds(now + ttl),
|
|
8339
|
-
iat: nowSeconds(now),
|
|
8340
|
-
iss: issuer,
|
|
8341
|
-
jti: crypto.randomUUID(),
|
|
8342
|
-
scope: scopes.join(" "),
|
|
8343
|
-
sub,
|
|
8344
|
-
token_use: "access"
|
|
8345
|
-
};
|
|
8346
|
-
if (act !== undefined)
|
|
8347
|
-
claims.act = act;
|
|
8348
|
-
const cnf = {
|
|
8349
|
-
...dpopJkt === undefined ? {} : { jkt: dpopJkt },
|
|
8350
|
-
...clientCertThumbprint === undefined ? {} : { "x5t#S256": clientCertThumbprint }
|
|
8351
|
-
};
|
|
8352
|
-
if (Object.keys(cnf).length > 0) {
|
|
8353
|
-
claims.cnf = cnf;
|
|
8354
|
-
}
|
|
8355
|
-
return claims;
|
|
8356
|
-
};
|
|
8357
|
-
var exchangeToken = async ({
|
|
8358
|
-
actorClientId,
|
|
8359
|
-
audience,
|
|
8360
|
-
config,
|
|
8361
|
-
dpopJkt,
|
|
8362
|
-
now = Date.now(),
|
|
8363
|
-
requestedScopes,
|
|
8364
|
-
subjectToken
|
|
8365
|
-
}) => {
|
|
8366
|
-
const verified = await verifyJwtWithKeys(subjectToken, signingVerificationKeys(config.signingKey, config.previousSigningKeys));
|
|
8367
|
-
const payload = verified?.payload;
|
|
8368
|
-
if (payload === undefined || typeof payload.sub !== "string" || typeof payload.exp !== "number" || payload.exp <= nowSeconds(now)) {
|
|
8369
|
-
return { error: "invalid_grant", ok: false };
|
|
8370
|
-
}
|
|
8371
|
-
const available = typeof payload.scope === "string" ? payload.scope.split(" ") : [];
|
|
8372
|
-
if (requestedScopes?.some((scope) => !available.includes(scope)) === true) {
|
|
8373
|
-
return { error: "invalid_scope", ok: false };
|
|
8374
|
-
}
|
|
8375
|
-
const scopes = narrowScopes(available, requestedScopes);
|
|
8376
|
-
const ttl = resolveAccessTtl(config.accessTokenTtlMs, scopes);
|
|
8377
|
-
const extraClaims = await config.getAccessTokenClaims?.({
|
|
8378
|
-
audience,
|
|
8379
|
-
clientId: actorClientId,
|
|
8380
|
-
scopes,
|
|
8381
|
-
sub: payload.sub
|
|
8382
|
-
});
|
|
8383
|
-
return {
|
|
8384
|
-
accessToken: await signJwt(buildAccessClaims({
|
|
8385
|
-
act: { sub: actorClientId },
|
|
8386
|
-
audience,
|
|
8387
|
-
clientId: actorClientId,
|
|
8388
|
-
dpopJkt,
|
|
8389
|
-
extraClaims,
|
|
8390
|
-
issuer: config.issuer,
|
|
8391
|
-
now,
|
|
8392
|
-
scopes,
|
|
8393
|
-
sub: payload.sub,
|
|
8394
|
-
ttl
|
|
8395
|
-
}), config.signingKey),
|
|
8396
|
-
expiresIn: Math.floor(ttl / MS_PER_SECOND),
|
|
8397
|
-
ok: true,
|
|
8398
|
-
scope: scopes.join(" ")
|
|
8399
|
-
};
|
|
8400
|
-
};
|
|
8401
|
-
var issueTokenSet = async ({
|
|
8402
|
-
acr,
|
|
8403
|
-
audience,
|
|
8404
|
-
claims,
|
|
8405
|
-
clientCertThumbprint,
|
|
8406
|
-
clientId,
|
|
8407
|
-
config,
|
|
8408
|
-
dpopJkt,
|
|
8409
|
-
familyId,
|
|
8410
|
-
nonce,
|
|
8411
|
-
now = Date.now(),
|
|
8412
|
-
persistRefreshToken,
|
|
8413
|
-
scopes,
|
|
8414
|
-
sub
|
|
8415
|
-
}) => {
|
|
8416
|
-
const accessTtl = resolveAccessTtl(config.accessTokenTtlMs, scopes);
|
|
8417
|
-
const idTtl = config.idTokenTtlMs ?? DEFAULT_ID_TOKEN_TTL_MS;
|
|
8418
|
-
const refreshTtl = config.refreshTokenTtlMs ?? DEFAULT_REFRESH_TOKEN_TTL_MS;
|
|
8419
|
-
const accessExtra = await config.getAccessTokenClaims?.({
|
|
8420
|
-
audience,
|
|
8421
|
-
clientId,
|
|
8422
|
-
scopes,
|
|
8423
|
-
sub
|
|
8424
|
-
});
|
|
8425
|
-
const accessPayload = buildAccessClaims({
|
|
8426
|
-
audience,
|
|
8427
|
-
clientCertThumbprint,
|
|
8428
|
-
clientId,
|
|
8429
|
-
dpopJkt,
|
|
8430
|
-
extraClaims: { ...accessExtra, ...acr === undefined ? {} : { acr } },
|
|
8431
|
-
issuer: config.issuer,
|
|
8432
|
-
now,
|
|
8433
|
-
scopes,
|
|
8434
|
-
sub,
|
|
8435
|
-
ttl: accessTtl
|
|
8436
|
-
});
|
|
8437
|
-
const idPayload = {
|
|
8438
|
-
...claims,
|
|
8439
|
-
aud: clientId,
|
|
8440
|
-
exp: nowSeconds(now + idTtl),
|
|
8441
|
-
iat: nowSeconds(now),
|
|
8442
|
-
iss: config.issuer,
|
|
8443
|
-
sub
|
|
8444
|
-
};
|
|
8445
|
-
if (nonce !== undefined)
|
|
8446
|
-
idPayload.nonce = nonce;
|
|
8447
|
-
if (acr !== undefined)
|
|
8448
|
-
idPayload.acr = acr;
|
|
8449
|
-
const refreshToken = generateSecureToken(TOKEN_BYTES3);
|
|
8450
|
-
const refreshRecord = {
|
|
8451
|
-
acr,
|
|
8452
|
-
audience,
|
|
8453
|
-
claims,
|
|
8454
|
-
clientId,
|
|
8455
|
-
createdAt: now,
|
|
8456
|
-
dpopJkt,
|
|
8457
|
-
expiresAt: now + refreshTtl,
|
|
8458
|
-
familyId: familyId ?? crypto.randomUUID(),
|
|
8459
|
-
scopes,
|
|
8460
|
-
tokenHash: await hashToken(refreshToken),
|
|
8461
|
-
userId: sub
|
|
8462
|
-
};
|
|
8463
|
-
if (persistRefreshToken)
|
|
8464
|
-
await persistRefreshToken(refreshRecord);
|
|
8465
|
-
else
|
|
8466
|
-
await config.refreshTokenStore.saveToken(refreshRecord);
|
|
8467
|
-
return {
|
|
8468
|
-
access_token: await signJwt(accessPayload, config.signingKey),
|
|
8469
|
-
expires_in: Math.floor(accessTtl / MS_PER_SECOND),
|
|
8470
|
-
id_token: await signJwt(idPayload, config.signingKey),
|
|
8471
|
-
refresh_token: refreshToken,
|
|
8472
|
-
scope: scopes.join(" "),
|
|
8473
|
-
token_type: dpopJkt === undefined ? "Bearer" : "DPoP"
|
|
8474
|
-
};
|
|
8475
|
-
};
|
|
8476
|
-
var mcpProtectedResourceMetadata = ({
|
|
8477
|
-
issuer,
|
|
8478
|
-
resource,
|
|
8479
|
-
scopes
|
|
8480
|
-
}) => ({
|
|
8481
|
-
authorization_servers: [issuer],
|
|
8482
|
-
resource,
|
|
8483
|
-
scopes_supported: scopes ?? []
|
|
8484
|
-
});
|
|
8485
|
-
var verifyPkce = async (codeVerifier, codeChallenge) => await hashToken(codeVerifier) === codeChallenge;
|
|
8486
|
-
var inactive = { active: false };
|
|
8487
|
-
var introspectToken = async ({
|
|
8488
|
-
config,
|
|
8489
|
-
hint,
|
|
8490
|
-
now = Date.now(),
|
|
8491
|
-
token
|
|
8492
|
-
}) => {
|
|
8493
|
-
if (hint !== "refresh_token") {
|
|
8494
|
-
const verified = await verifyJwtWithKeys(token, signingVerificationKeys(config.signingKey, config.previousSigningKeys));
|
|
8495
|
-
const payload = verified?.payload;
|
|
8496
|
-
if (payload !== undefined && typeof payload.sub === "string" && typeof payload.exp === "number" && payload.exp > nowSeconds(now)) {
|
|
8497
|
-
return {
|
|
8498
|
-
active: true,
|
|
8499
|
-
client_id: typeof payload.client_id === "string" ? payload.client_id : "",
|
|
8500
|
-
exp: payload.exp,
|
|
8501
|
-
iat: typeof payload.iat === "number" ? payload.iat : 0,
|
|
8502
|
-
scope: typeof payload.scope === "string" ? payload.scope : "",
|
|
8503
|
-
sub: payload.sub,
|
|
8504
|
-
token_type: "access_token"
|
|
8505
|
-
};
|
|
8506
|
-
}
|
|
8507
|
-
}
|
|
8508
|
-
if (hint !== "access_token") {
|
|
8509
|
-
const refresh = await config.refreshTokenStore.getToken(await hashToken(token));
|
|
8510
|
-
if (refresh && refresh.expiresAt > now) {
|
|
8511
|
-
return {
|
|
8512
|
-
active: true,
|
|
8513
|
-
client_id: refresh.clientId,
|
|
8514
|
-
exp: nowSeconds(refresh.expiresAt),
|
|
8515
|
-
iat: nowSeconds(refresh.createdAt),
|
|
8516
|
-
scope: refresh.scopes.join(" "),
|
|
8517
|
-
sub: refresh.userId,
|
|
8518
|
-
token_type: "refresh_token"
|
|
8519
|
-
};
|
|
8520
|
-
}
|
|
8521
|
-
}
|
|
8522
|
-
return inactive;
|
|
8523
|
-
};
|
|
8524
|
-
var revokeRefreshToken = async (config, token) => {
|
|
8525
|
-
const consumed = await config.refreshTokenStore.consumeToken(await hashToken(token));
|
|
8526
|
-
return consumed !== undefined;
|
|
8527
|
-
};
|
|
8528
|
-
var DEVICE_CODE_BYTES = 32;
|
|
8529
|
-
var USER_CODE_HALF_LENGTH = 4;
|
|
8530
|
-
var USER_CODE_ALPHABET = "BCDFGHJKLMNPQRSTVWXZ23456789";
|
|
8531
|
-
var DEFAULT_DEVICE_CODE_TTL_MINUTES = 15;
|
|
8532
|
-
var DEFAULT_DEVICE_CODE_TTL_MS = MILLISECONDS_IN_A_MINUTE * DEFAULT_DEVICE_CODE_TTL_MINUTES;
|
|
8533
|
-
var DEFAULT_DEVICE_POLL_INTERVAL_SECONDS = 5;
|
|
8534
|
-
var generateUserCode = () => {
|
|
8535
|
-
const length = USER_CODE_HALF_LENGTH * 2;
|
|
8536
|
-
const random = crypto.getRandomValues(new Uint8Array(length));
|
|
8537
|
-
let code = "";
|
|
8538
|
-
for (const byte of random) {
|
|
8539
|
-
code += USER_CODE_ALPHABET[byte % USER_CODE_ALPHABET.length];
|
|
8540
|
-
}
|
|
8541
|
-
return `${code.slice(0, USER_CODE_HALF_LENGTH)}-${code.slice(USER_CODE_HALF_LENGTH)}`;
|
|
8542
|
-
};
|
|
8543
|
-
var issueDeviceAuthorization = async ({
|
|
8544
|
-
audience,
|
|
8545
|
-
clientId,
|
|
8546
|
-
config,
|
|
8547
|
-
now = Date.now(),
|
|
8548
|
-
requestedScopes
|
|
8549
|
-
}) => {
|
|
8550
|
-
if (!config.deviceAuthorizationStore) {
|
|
8551
|
-
throw new Error("oidc.deviceAuthorizationStore is not configured \u2014 cannot start a device flow");
|
|
8552
|
-
}
|
|
8553
|
-
const deviceCode = generateSecureToken(DEVICE_CODE_BYTES);
|
|
8554
|
-
const userCode = generateUserCode();
|
|
8555
|
-
const ttl = config.deviceCodeTtlMs ?? DEFAULT_DEVICE_CODE_TTL_MS;
|
|
8556
|
-
const interval = config.devicePollIntervalSeconds ?? DEFAULT_DEVICE_POLL_INTERVAL_SECONDS;
|
|
8557
|
-
await config.deviceAuthorizationStore.saveDeviceAuthorization({
|
|
8558
|
-
audience,
|
|
8559
|
-
clientId,
|
|
8560
|
-
createdAt: now,
|
|
8561
|
-
deviceCodeHash: await hashToken(deviceCode),
|
|
8562
|
-
expiresAt: now + ttl,
|
|
8563
|
-
intervalSeconds: interval,
|
|
8564
|
-
scopes: requestedScopes,
|
|
8565
|
-
status: "pending",
|
|
8566
|
-
userCode
|
|
8567
|
-
});
|
|
8568
|
-
const verificationUri = `${config.issuer}${config.oidcRoute ?? DEFAULT_OIDC_ROUTE}/device`;
|
|
8569
|
-
return {
|
|
8570
|
-
device_code: deviceCode,
|
|
8571
|
-
expires_in: Math.floor(ttl / MS_PER_SECOND),
|
|
8572
|
-
interval,
|
|
8573
|
-
user_code: userCode,
|
|
8574
|
-
verification_uri: verificationUri,
|
|
8575
|
-
verification_uri_complete: `${verificationUri}?user_code=${encodeURIComponent(userCode)}`
|
|
8576
|
-
};
|
|
8577
|
-
};
|
|
8578
|
-
var decideDeviceAuthorization = async (config, userCode, approval) => {
|
|
8579
|
-
if (!config.deviceAuthorizationStore) {
|
|
8580
|
-
return { error: "not_configured", ok: false };
|
|
8581
|
-
}
|
|
8582
|
-
const record = await config.deviceAuthorizationStore.findByUserCode(userCode);
|
|
8583
|
-
if (!record)
|
|
8584
|
-
return { error: "invalid_user_code", ok: false };
|
|
8585
|
-
if (record.expiresAt < Date.now()) {
|
|
8586
|
-
return { error: "expired_token", ok: false };
|
|
8587
|
-
}
|
|
8588
|
-
if (record.status !== "pending") {
|
|
8589
|
-
return { error: "already_decided", ok: false };
|
|
8590
|
-
}
|
|
8591
|
-
await config.deviceAuthorizationStore.updateStatus(record.deviceCodeHash, approval.status, approval.userSub);
|
|
8592
|
-
if (approval.status === "approved" && approval.userSub !== undefined) {
|
|
8593
|
-
await config.onDeviceAuthorizationApproved?.({
|
|
8594
|
-
clientId: record.clientId,
|
|
8595
|
-
scopes: record.scopes,
|
|
8596
|
-
userSub: approval.userSub
|
|
8597
|
-
});
|
|
8598
|
-
}
|
|
8599
|
-
return { ok: true };
|
|
8600
|
-
};
|
|
8601
|
-
var approveDeviceAuthorization = async ({
|
|
8602
|
-
config,
|
|
8603
|
-
userCode,
|
|
8604
|
-
userSub
|
|
8605
|
-
}) => decideDeviceAuthorization(config, userCode, {
|
|
8606
|
-
status: "approved",
|
|
8607
|
-
userSub
|
|
8608
|
-
});
|
|
8609
|
-
var denyDeviceAuthorization = async ({
|
|
8610
|
-
config,
|
|
8611
|
-
userCode
|
|
8612
|
-
}) => decideDeviceAuthorization(config, userCode, { status: "denied" });
|
|
8613
|
-
var exchangeDeviceCode = async ({
|
|
8614
|
-
clientId,
|
|
8615
|
-
config,
|
|
8616
|
-
deviceCode,
|
|
8617
|
-
dpopJkt,
|
|
8618
|
-
now = Date.now()
|
|
8619
|
-
}) => {
|
|
8620
|
-
if (!config.deviceAuthorizationStore) {
|
|
8621
|
-
return { error: "invalid_grant", ok: false };
|
|
8622
|
-
}
|
|
8623
|
-
const deviceCodeHash = await hashToken(deviceCode);
|
|
8624
|
-
const record = await config.deviceAuthorizationStore.findByDeviceCodeHash(deviceCodeHash);
|
|
8625
|
-
if (!record || record.clientId !== clientId) {
|
|
8626
|
-
return { error: "invalid_grant", ok: false };
|
|
8627
|
-
}
|
|
8628
|
-
if (record.expiresAt < now) {
|
|
8629
|
-
await config.deviceAuthorizationStore.deleteByDeviceCodeHash(deviceCodeHash);
|
|
8630
|
-
return { error: "expired_token", ok: false };
|
|
8631
|
-
}
|
|
8632
|
-
if (record.status === "pending") {
|
|
8633
|
-
return { error: "authorization_pending", ok: false };
|
|
8634
|
-
}
|
|
8635
|
-
if (record.status === "denied" || record.userSub === undefined) {
|
|
8636
|
-
return { error: "access_denied", ok: false };
|
|
8637
|
-
}
|
|
8638
|
-
await config.deviceAuthorizationStore.deleteByDeviceCodeHash(deviceCodeHash);
|
|
8639
|
-
const tokenSet = await issueTokenSet({
|
|
8640
|
-
audience: record.audience,
|
|
8641
|
-
clientId,
|
|
8642
|
-
config,
|
|
8643
|
-
dpopJkt,
|
|
8644
|
-
now,
|
|
8645
|
-
scopes: record.scopes,
|
|
8646
|
-
sub: record.userSub
|
|
8647
|
-
});
|
|
8648
|
-
return { ...tokenSet, ok: true };
|
|
8649
|
-
};
|
|
8650
|
-
var AUTH_REQ_ID_BYTES = 32;
|
|
8651
|
-
var DEFAULT_BACKCHANNEL_TTL_MINUTES = 10;
|
|
8652
|
-
var DEFAULT_BACKCHANNEL_TTL_MS = MILLISECONDS_IN_A_MINUTE * DEFAULT_BACKCHANNEL_TTL_MINUTES;
|
|
8653
|
-
var DEFAULT_BACKCHANNEL_POLL_INTERVAL_SECONDS = 5;
|
|
8654
|
-
var CIBA_GRANT_TYPE = "urn:openid:params:grant-type:ciba";
|
|
8655
|
-
var issueBackchannelAuth = async ({
|
|
8656
|
-
clientId,
|
|
8657
|
-
config,
|
|
8658
|
-
loginHint,
|
|
8659
|
-
bindingMessage,
|
|
8660
|
-
now = Date.now(),
|
|
8661
|
-
requestedScopes
|
|
8662
|
-
}) => {
|
|
8663
|
-
if (!config.backchannelAuthStore || !config.resolveBackchannelUser) {
|
|
8664
|
-
return { error: "invalid_request", ok: false };
|
|
8665
|
-
}
|
|
8666
|
-
const client = await config.clientStore.findClient(clientId) ?? await config.resolveClientIdMetadata?.(clientId);
|
|
8667
|
-
if (!client)
|
|
8668
|
-
return { error: "invalid_client", ok: false };
|
|
8669
|
-
const resolved = await config.resolveBackchannelUser({
|
|
8670
|
-
client,
|
|
8671
|
-
loginHint
|
|
8672
|
-
});
|
|
8673
|
-
if (!resolved)
|
|
8674
|
-
return { error: "unknown_user_id", ok: false };
|
|
8675
|
-
const authReqId = generateSecureToken(AUTH_REQ_ID_BYTES);
|
|
8676
|
-
const ttl = config.backchannelAuthTtlMs ?? DEFAULT_BACKCHANNEL_TTL_MS;
|
|
8677
|
-
const interval = config.backchannelPollIntervalSeconds ?? DEFAULT_BACKCHANNEL_POLL_INTERVAL_SECONDS;
|
|
8678
|
-
await config.backchannelAuthStore.saveBackchannelAuth({
|
|
8679
|
-
authReqId,
|
|
8680
|
-
bindingMessage,
|
|
8681
|
-
clientId,
|
|
8682
|
-
createdAt: now,
|
|
8683
|
-
expiresAt: now + ttl,
|
|
8684
|
-
intervalSeconds: interval,
|
|
8685
|
-
scopes: requestedScopes,
|
|
8686
|
-
status: "pending",
|
|
8687
|
-
userSub: resolved.sub
|
|
8688
|
-
});
|
|
8689
|
-
await config.onBackchannelAuthRequest?.({
|
|
8690
|
-
authReqId,
|
|
8691
|
-
bindingMessage,
|
|
8692
|
-
clientId,
|
|
8693
|
-
scopes: requestedScopes,
|
|
8694
|
-
userSub: resolved.sub
|
|
8695
|
-
});
|
|
8696
|
-
return {
|
|
8697
|
-
auth_req_id: authReqId,
|
|
8698
|
-
expires_in: Math.floor(ttl / MS_PER_SECOND),
|
|
8699
|
-
interval,
|
|
8700
|
-
ok: true
|
|
8701
|
-
};
|
|
8702
|
-
};
|
|
8703
|
-
var decideBackchannel = async (config, authReqId, approval) => {
|
|
8704
|
-
if (!config.backchannelAuthStore) {
|
|
8705
|
-
return { error: "not_configured", ok: false };
|
|
8706
|
-
}
|
|
8707
|
-
const record = await config.backchannelAuthStore.findByAuthReqId(authReqId);
|
|
8708
|
-
if (!record)
|
|
8709
|
-
return { error: "invalid_auth_req_id", ok: false };
|
|
8710
|
-
if (record.expiresAt < Date.now()) {
|
|
8711
|
-
return { error: "expired_token", ok: false };
|
|
8712
|
-
}
|
|
8713
|
-
if (record.status !== "pending") {
|
|
8714
|
-
return { error: "already_decided", ok: false };
|
|
8715
|
-
}
|
|
8716
|
-
await config.backchannelAuthStore.updateStatus(authReqId, approval.status, approval.userSub ?? record.userSub);
|
|
8717
|
-
return { ok: true };
|
|
8718
|
-
};
|
|
8719
|
-
var approveBackchannelAuth = async ({
|
|
8720
|
-
authReqId,
|
|
8721
|
-
config,
|
|
8722
|
-
userSub
|
|
8723
|
-
}) => decideBackchannel(config, authReqId, {
|
|
8724
|
-
status: "approved",
|
|
8725
|
-
userSub
|
|
8726
|
-
});
|
|
8727
|
-
var denyBackchannelAuth = async ({
|
|
8728
|
-
authReqId,
|
|
8729
|
-
config
|
|
8730
|
-
}) => decideBackchannel(config, authReqId, { status: "denied" });
|
|
8731
|
-
var exchangeBackchannelAuth = async ({
|
|
8732
|
-
authReqId,
|
|
8733
|
-
clientCertThumbprint,
|
|
8734
|
-
clientId,
|
|
8735
|
-
config,
|
|
8736
|
-
dpopJkt,
|
|
8737
|
-
now = Date.now()
|
|
8738
|
-
}) => {
|
|
8739
|
-
if (!config.backchannelAuthStore) {
|
|
8740
|
-
return { error: "invalid_grant", ok: false };
|
|
8741
|
-
}
|
|
8742
|
-
const record = await config.backchannelAuthStore.findByAuthReqId(authReqId);
|
|
8743
|
-
if (!record || record.clientId !== clientId) {
|
|
8744
|
-
return { error: "invalid_grant", ok: false };
|
|
8745
|
-
}
|
|
8746
|
-
if (record.expiresAt < now) {
|
|
8747
|
-
await config.backchannelAuthStore.deleteByAuthReqId(authReqId);
|
|
8748
|
-
return { error: "expired_token", ok: false };
|
|
8749
|
-
}
|
|
8750
|
-
if (record.lastPolledAt !== undefined && now - record.lastPolledAt < record.intervalSeconds * MS_PER_SECOND) {
|
|
8751
|
-
return { error: "slow_down", ok: false };
|
|
8752
|
-
}
|
|
8753
|
-
await config.backchannelAuthStore.recordPoll(authReqId, now);
|
|
8754
|
-
if (record.status === "pending") {
|
|
8755
|
-
return { error: "authorization_pending", ok: false };
|
|
8756
|
-
}
|
|
8757
|
-
if (record.status === "denied" || record.userSub === undefined) {
|
|
8758
|
-
return { error: "access_denied", ok: false };
|
|
8759
|
-
}
|
|
8760
|
-
await config.backchannelAuthStore.deleteByAuthReqId(authReqId);
|
|
8761
|
-
const tokenSet = await issueTokenSet({
|
|
8762
|
-
clientCertThumbprint,
|
|
8763
|
-
clientId,
|
|
8764
|
-
config,
|
|
8765
|
-
dpopJkt,
|
|
8766
|
-
now,
|
|
8767
|
-
scopes: record.scopes,
|
|
8768
|
-
sub: record.userSub
|
|
8769
|
-
});
|
|
8770
|
-
return { ...tokenSet, ok: true };
|
|
8771
|
-
};
|
|
8772
|
-
|
|
8773
8784
|
// src/oidc/clientAuth.ts
|
|
8774
8785
|
init_constants();
|
|
8775
8786
|
var CLIENT_ASSERTION_TYPE = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer";
|
|
@@ -26778,10 +26789,9 @@ var toUnregisteredSessionData = (row) => ({
|
|
|
26778
26789
|
sessionInformation: cloneRecord(row.session_information_json ?? undefined),
|
|
26779
26790
|
userIdentity: cloneRecord(row.user_identity_json ?? undefined)
|
|
26780
26791
|
});
|
|
26781
|
-
var createNeonAuthSessionStore = (databaseUrl, decodeUser) => {
|
|
26782
|
-
|
|
26783
|
-
const
|
|
26784
|
-
return {
|
|
26792
|
+
var createNeonAuthSessionStore = (databaseUrl, decodeUser) => createPostgresAuthSessionStore(drizzle({ client: as(databaseUrl) }), decodeUser);
|
|
26793
|
+
var createPostgresAuthSessionStore = (db, decodeUser) => {
|
|
26794
|
+
const store = {
|
|
26785
26795
|
deleteExpired: async () => {
|
|
26786
26796
|
const rows = await db.delete(authSessionsTable).where(lt(authSessionsTable.expires_at_ms, Date.now())).returning({ id: authSessionsTable.id });
|
|
26787
26797
|
return rows.length;
|
|
@@ -26859,6 +26869,7 @@ var createNeonAuthSessionStore = (databaseUrl, decodeUser) => {
|
|
|
26859
26869
|
});
|
|
26860
26870
|
}
|
|
26861
26871
|
};
|
|
26872
|
+
return store;
|
|
26862
26873
|
};
|
|
26863
26874
|
// src/providersFromEnv.ts
|
|
26864
26875
|
var envKey = (provider, suffix) => `${provider.replace(/[^a-zA-Z0-9]/g, "_").toUpperCase()}_${suffix}`;
|
|
@@ -34955,6 +34966,30 @@ var createRedisAuthSessionStore = (redis, decodeUser, keyPrefix = "auth:session:
|
|
|
34955
34966
|
}
|
|
34956
34967
|
};
|
|
34957
34968
|
};
|
|
34969
|
+
// src/linkedProviders/credentialError.ts
|
|
34970
|
+
class LinkedProviderCredentialError extends Error {
|
|
34971
|
+
code;
|
|
34972
|
+
recovery;
|
|
34973
|
+
constructor(code, recovery, message, cause) {
|
|
34974
|
+
super(message, { cause });
|
|
34975
|
+
this.code = code;
|
|
34976
|
+
this.recovery = recovery;
|
|
34977
|
+
this.name = "LinkedProviderCredentialError";
|
|
34978
|
+
}
|
|
34979
|
+
}
|
|
34980
|
+
var credentialRefreshError = (error2) => {
|
|
34981
|
+
if (error2 instanceof LinkedProviderCredentialError)
|
|
34982
|
+
return error2;
|
|
34983
|
+
const message = error2 instanceof Error ? error2.message : "";
|
|
34984
|
+
if (/\binvalid_grant\b/.test(message)) {
|
|
34985
|
+
return new LinkedProviderCredentialError("invalid_grant", "reconnect", "Authorization has expired or was revoked. Reconnect your account.", error2);
|
|
34986
|
+
}
|
|
34987
|
+
if (/\b(invalid_client|unauthorized_client)\b/.test(message)) {
|
|
34988
|
+
return new LinkedProviderCredentialError("invalid_client", "configuration", "The provider connection is misconfigured. Contact support.", error2);
|
|
34989
|
+
}
|
|
34990
|
+
return new LinkedProviderCredentialError("refresh_failed", "retry", "The provider could not refresh the connection. Try again.", error2);
|
|
34991
|
+
};
|
|
34992
|
+
|
|
34958
34993
|
// src/linkedProviders/resolver.ts
|
|
34959
34994
|
var uniqueStrings = (values) => [...new Set(values)];
|
|
34960
34995
|
var getEffectiveScopes = (grant, binding) => {
|
|
@@ -35052,6 +35087,30 @@ var resolveBindingCredential = async (grantStore, binding, input) => {
|
|
|
35052
35087
|
}
|
|
35053
35088
|
return buildResolvedCredential(grant, binding);
|
|
35054
35089
|
};
|
|
35090
|
+
var refreshAndRecord = async (grant, input, refresh2, grantStore, now) => {
|
|
35091
|
+
let refreshed;
|
|
35092
|
+
try {
|
|
35093
|
+
const result = await refresh2(grant, input);
|
|
35094
|
+
if (!result)
|
|
35095
|
+
throw new Error("Linked provider access token refresh failed");
|
|
35096
|
+
refreshed = result;
|
|
35097
|
+
} catch (cause) {
|
|
35098
|
+
const error2 = credentialRefreshError(cause);
|
|
35099
|
+
await grantStore.saveGrant({
|
|
35100
|
+
...grant,
|
|
35101
|
+
lastRefreshError: error2.message,
|
|
35102
|
+
metadata: { ...grant.metadata, credentialFailureCode: error2.code, credentialRecovery: error2.recovery },
|
|
35103
|
+
status: "refresh_required",
|
|
35104
|
+
updatedAt: now()
|
|
35105
|
+
});
|
|
35106
|
+
throw error2;
|
|
35107
|
+
}
|
|
35108
|
+
const metadata = { ...refreshed.grant.metadata };
|
|
35109
|
+
delete metadata.credentialRecovery;
|
|
35110
|
+
delete metadata.credentialFailureCode;
|
|
35111
|
+
await grantStore.saveGrant({ ...refreshed.grant, lastRefreshError: undefined, metadata });
|
|
35112
|
+
return refreshed;
|
|
35113
|
+
};
|
|
35055
35114
|
var createLinkedProviderCredentialResolver = ({
|
|
35056
35115
|
grantStore,
|
|
35057
35116
|
bindingStore,
|
|
@@ -35081,11 +35140,7 @@ var createLinkedProviderCredentialResolver = ({
|
|
|
35081
35140
|
if (!refreshAccessTokenLease) {
|
|
35082
35141
|
throw new Error("Linked provider access token lease requires refresh");
|
|
35083
35142
|
}
|
|
35084
|
-
const refreshed = await
|
|
35085
|
-
if (!refreshed) {
|
|
35086
|
-
throw new Error("Linked provider access token refresh failed");
|
|
35087
|
-
}
|
|
35088
|
-
await grantStore.saveGrant(refreshed.grant);
|
|
35143
|
+
const refreshed = await refreshAndRecord(grant, input, refreshAccessTokenLease, grantStore, now);
|
|
35089
35144
|
({ lease } = refreshed);
|
|
35090
35145
|
}
|
|
35091
35146
|
if (!lease) {
|
|
@@ -35178,8 +35233,11 @@ var createOAuthLinkedProviderCredentialResolver = async ({
|
|
|
35178
35233
|
tokenType: grant.tokenType
|
|
35179
35234
|
} : null,
|
|
35180
35235
|
refreshAccessTokenLease: async (grant) => {
|
|
35181
|
-
if (!
|
|
35182
|
-
|
|
35236
|
+
if (!grant.refreshTokenCiphertext) {
|
|
35237
|
+
throw new LinkedProviderCredentialError("missing_refresh_token", "reconnect", "Authorization cannot renew automatically. Reconnect your account.");
|
|
35238
|
+
}
|
|
35239
|
+
if (!isValidProviderOption(grant.authProviderKey)) {
|
|
35240
|
+
throw new LinkedProviderCredentialError("unsupported_provider", "configuration", "This provider cannot renew the connection. Contact support.");
|
|
35183
35241
|
}
|
|
35184
35242
|
const providerKey = grant.authProviderKey;
|
|
35185
35243
|
const providerClientName = typeof grant.metadata?.providerClient === "string" && grant.metadata.providerClient.trim().length > 0 ? grant.metadata.providerClient.trim() : undefined;
|
|
@@ -35189,11 +35247,11 @@ var createOAuthLinkedProviderCredentialResolver = async ({
|
|
|
35189
35247
|
providersConfiguration
|
|
35190
35248
|
});
|
|
35191
35249
|
if ("error" in resolvedProviderClientConfiguration || !resolvedProviderClientConfiguration.config) {
|
|
35192
|
-
|
|
35250
|
+
throw new LinkedProviderCredentialError("provider_configuration", "configuration", "The provider connection is misconfigured. Contact support.");
|
|
35193
35251
|
}
|
|
35194
35252
|
const providerClient = await createOAuth2Client(providerKey, resolvedProviderClientConfiguration.config.credentials);
|
|
35195
35253
|
if (!providerClient || !isRefreshableOAuth2Client(providerKey, providerClient)) {
|
|
35196
|
-
|
|
35254
|
+
throw new LinkedProviderCredentialError("unsupported_refresh", "configuration", "This provider cannot renew the connection. Contact support.");
|
|
35197
35255
|
}
|
|
35198
35256
|
const tokenResponse2 = await providerClient.refreshAccessToken(grant.refreshTokenCiphertext);
|
|
35199
35257
|
const refreshedAt = Date.now();
|
|
@@ -41599,6 +41657,7 @@ var buildAuthApplications = async (configuration) => {
|
|
|
41599
41657
|
onRevocationError,
|
|
41600
41658
|
onSessionCleanup
|
|
41601
41659
|
} = configuration;
|
|
41660
|
+
assertTokenRouteConfiguration(apikeys, oidc);
|
|
41602
41661
|
if (push && nativePush2)
|
|
41603
41662
|
throw new Error("Configure `push`, not both `push` and `nativePush`");
|
|
41604
41663
|
const pushConfig = push ?? nativePush2;
|
|
@@ -42085,5 +42144,5 @@ export {
|
|
|
42085
42144
|
userSessionIdTypebox
|
|
42086
42145
|
};
|
|
42087
42146
|
|
|
42088
|
-
//# debugId=
|
|
42147
|
+
//# debugId=DE4780E5239C13EA64756E2164756E21
|
|
42089
42148
|
//# sourceMappingURL=server.js.map
|