@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/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 = "/oauth2/token";
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 DEFAULT_ACCESS_TOKEN_TTL_MS2 = 15 * MILLISECONDS_IN_A_MINUTE;
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 TOKEN_BYTES2 = 32;
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(TOKEN_BYTES2)}`;
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(TOKEN_BYTES2)}`;
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 ?? DEFAULT_ACCESS_TOKEN_TTL_MS2);
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
- const sql2 = as(databaseUrl);
26783
- const db = drizzle({ client: sql2 });
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 refreshAccessTokenLease(grant, input);
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 (!isValidProviderOption(grant.authProviderKey) || !grant.refreshTokenCiphertext) {
35182
- return null;
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
- return null;
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
- return null;
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=2F4B37C045D05FBC64756E2164756E21
42147
+ //# debugId=DE4780E5239C13EA64756E2164756E21
42089
42148
  //# sourceMappingURL=server.js.map