@absolutejs/auth 0.77.1 → 0.79.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 +14 -0
- package/README.md +16 -0
- package/changelog.json +27 -0
- package/dist/apikeys/config.d.ts +1 -1
- package/dist/apikeys/tokenRoutes.d.ts +7 -0
- package/dist/cli/migrate.js +7 -7
- package/dist/cli/migrate.js.map +3 -3
- package/dist/index.js +610 -598
- package/dist/index.js.map +10 -9
- package/dist/linkedProviders/index.js +7 -7
- package/dist/linkedProviders/index.js.map +3 -3
- package/dist/server.js +610 -598
- package/dist/server.js.map +10 -9
- 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";
|
|
@@ -35333,7 +35344,7 @@ var linkedProviderBindingsTable = pgTable("linked_provider_bindings", {
|
|
|
35333
35344
|
available_scopes: jsonb("available_scopes").$type().notNull().default([]),
|
|
35334
35345
|
capabilities: jsonb("capabilities").$type().default([]),
|
|
35335
35346
|
connector_provider: varchar("connector_provider", { length: 64 }).notNull(),
|
|
35336
|
-
created_at: timestamp("created_at").notNull().defaultNow(),
|
|
35347
|
+
created_at: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
35337
35348
|
email: varchar("email", { length: 320 }),
|
|
35338
35349
|
external_account_id: varchar("external_account_id", {
|
|
35339
35350
|
length: 255
|
|
@@ -35346,18 +35357,18 @@ var linkedProviderBindingsTable = pgTable("linked_provider_bindings", {
|
|
|
35346
35357
|
label: varchar("label", { length: 255 }),
|
|
35347
35358
|
metadata: jsonb("metadata").$type().default({}),
|
|
35348
35359
|
status: varchar("status", { length: 64 }).$type().notNull(),
|
|
35349
|
-
updated_at: timestamp("updated_at").notNull().defaultNow(),
|
|
35360
|
+
updated_at: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
|
35350
35361
|
username: varchar("username", { length: 255 })
|
|
35351
35362
|
});
|
|
35352
35363
|
var linkedProviderGrantsTable = pgTable("linked_provider_grants", {
|
|
35353
35364
|
access_token_ciphertext: text("access_token_ciphertext"),
|
|
35354
35365
|
auth_provider_key: varchar("auth_provider_key", { length: 64 }).notNull(),
|
|
35355
|
-
created_at: timestamp("created_at").notNull().defaultNow(),
|
|
35356
|
-
expires_at: timestamp("expires_at"),
|
|
35366
|
+
created_at: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
35367
|
+
expires_at: timestamp("expires_at", { withTimezone: true }),
|
|
35357
35368
|
granted_scopes: jsonb("granted_scopes").$type().notNull().default([]),
|
|
35358
35369
|
id: varchar("id", { length: 255 }).primaryKey(),
|
|
35359
35370
|
last_refresh_error: text("last_refresh_error"),
|
|
35360
|
-
last_refreshed_at: timestamp("last_refreshed_at"),
|
|
35371
|
+
last_refreshed_at: timestamp("last_refreshed_at", { withTimezone: true }),
|
|
35361
35372
|
metadata: jsonb("metadata").$type().default({}),
|
|
35362
35373
|
owner_ref: varchar("owner_ref", { length: 255 }).notNull(),
|
|
35363
35374
|
provider_family: varchar("provider_family", { length: 64 }).notNull(),
|
|
@@ -35365,7 +35376,7 @@ var linkedProviderGrantsTable = pgTable("linked_provider_grants", {
|
|
|
35365
35376
|
refresh_token_ciphertext: text("refresh_token_ciphertext"),
|
|
35366
35377
|
status: varchar("status", { length: 64 }).$type().notNull(),
|
|
35367
35378
|
token_type: varchar("token_type", { length: 64 }),
|
|
35368
|
-
updated_at: timestamp("updated_at").notNull().defaultNow()
|
|
35379
|
+
updated_at: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
|
|
35369
35380
|
});
|
|
35370
35381
|
var toTimestamp = (value) => value === undefined ? null : new Date(value);
|
|
35371
35382
|
var fromTimestamp = (value) => value === null ? undefined : value.getTime();
|
|
@@ -41599,6 +41610,7 @@ var buildAuthApplications = async (configuration) => {
|
|
|
41599
41610
|
onRevocationError,
|
|
41600
41611
|
onSessionCleanup
|
|
41601
41612
|
} = configuration;
|
|
41613
|
+
assertTokenRouteConfiguration(apikeys, oidc);
|
|
41602
41614
|
if (push && nativePush2)
|
|
41603
41615
|
throw new Error("Configure `push`, not both `push` and `nativePush`");
|
|
41604
41616
|
const pushConfig = push ?? nativePush2;
|
|
@@ -42085,5 +42097,5 @@ export {
|
|
|
42085
42097
|
userSessionIdTypebox
|
|
42086
42098
|
};
|
|
42087
42099
|
|
|
42088
|
-
//# debugId=
|
|
42100
|
+
//# debugId=56F634EDC9043EB964756E2164756E21
|
|
42089
42101
|
//# sourceMappingURL=server.js.map
|