@absolutejs/auth 0.54.7 → 0.54.8

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.
@@ -2,8 +2,11 @@ import type { AgentCredentialVerifier } from './types';
2
2
  /** Adapter for Absolute Auth's RFC 9068-style JWT access tokens. It validates
3
3
  * signature, issuer, audience, expiry, and extracts the OAuth client as the
4
4
  * agent identity while retaining `sub` as the authorizing user. */
5
- export declare const createOidcAgentCredentialVerifier: ({ issuer, publicJwk, resource }: {
5
+ export declare const createOidcAgentCredentialVerifier: ({ issuer, isUsedDpopJti, maxDpopAgeMs, publicJwk, requireDpop, resource }: {
6
6
  issuer: string;
7
+ isUsedDpopJti?: (jti: string) => boolean | Promise<boolean>;
8
+ maxDpopAgeMs?: number;
7
9
  publicJwk: JsonWebKey;
10
+ requireDpop?: boolean;
8
11
  resource: string;
9
12
  }) => AgentCredentialVerifier;
package/dist/index.d.ts CHANGED
@@ -363,6 +363,7 @@ export { agentDelegationsTable, agentRegistrationsTable, createNeonAgentDelegati
363
363
  export { createInMemoryAccessTokenStore, createInMemoryApiClientStore, createInMemoryApiKeyStore } from './apikeys/inMemoryStores';
364
364
  export { accessTokensTable, apiClientsTable, apiKeysTable, createNeonAccessTokenStore, createNeonApiClientStore, createNeonApiKeyStore, createPostgresAccessTokenStore, createPostgresApiClientStore, createPostgresApiKeyStore } from './apikeys/postgresStores';
365
365
  export * from './oidc/config';
366
+ export * from './oidc/clientIdMetadata';
366
367
  export * from './oidc/types';
367
368
  export { oidcProviderRoutes } from './oidc/routes';
368
369
  export { generateSigningKey, jwkThumbprint, signJwt, toPublicJwk, verifyJwt } from './oidc/keys';
package/dist/index.js CHANGED
@@ -5488,7 +5488,7 @@ var issueBackchannelAuth = async ({
5488
5488
  if (!config.backchannelAuthStore || !config.resolveBackchannelUser) {
5489
5489
  return { error: "invalid_request", ok: false };
5490
5490
  }
5491
- const client = await config.clientStore.findClient(clientId);
5491
+ const client = await config.clientStore.findClient(clientId) ?? await config.resolveClientIdMetadata?.(clientId);
5492
5492
  if (!client)
5493
5493
  return { error: "invalid_client", ok: false };
5494
5494
  const resolved = await config.resolveBackchannelUser({
@@ -6169,6 +6169,7 @@ var normalizeHtu = (value) => {
6169
6169
  }
6170
6170
  };
6171
6171
  var verifyDpopProof = async ({
6172
+ accessToken,
6172
6173
  htm,
6173
6174
  htu,
6174
6175
  isUsedJti,
@@ -6189,6 +6190,11 @@ var verifyDpopProof = async ({
6189
6190
  if (verified === undefined)
6190
6191
  return;
6191
6192
  const { payload } = verified;
6193
+ if (accessToken !== undefined) {
6194
+ const expectedAth = Buffer.from(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(accessToken))).toString("base64url");
6195
+ if (payload.ath !== expectedAth)
6196
+ return;
6197
+ }
6192
6198
  const iatMs = typeof payload.iat === "number" ? payload.iat * SECONDS_TO_MS : 0;
6193
6199
  if (payload.htm !== htm || normalizeHtu(payload.htu) !== normalizeHtu(htu) || iatMs === 0 || Math.abs(now - iatMs) > maxAgeMs) {
6194
6200
  return;
@@ -6791,8 +6797,9 @@ var oidcProviderRoutes = (config) => {
6791
6797
  const userinfoRoute = `${oidcRoute}/userinfo`;
6792
6798
  const registrationBaseUrl = `${issuer}${registrationRoute}`;
6793
6799
  const tokenUrl = `${issuer}${oidcRoute}/token`;
6800
+ const resolveClient = async (clientId) => await clientStore.findClient(clientId) ?? await config.resolveClientIdMetadata?.(clientId);
6794
6801
  const authenticateClient = async (clientId, clientSecret) => {
6795
- const client = await clientStore.findClient(clientId);
6802
+ const client = await resolveClient(clientId);
6796
6803
  if (client === undefined)
6797
6804
  return;
6798
6805
  if (client.hashedSecret === undefined)
@@ -6835,14 +6842,14 @@ var oidcProviderRoutes = (config) => {
6835
6842
  assertion: bodyClientAssertion,
6836
6843
  expectedAudience: tokenUrl,
6837
6844
  jtiStore: config.clientAssertionJtiStore,
6838
- resolveClient: clientStore.findClient
6845
+ resolveClient
6839
6846
  });
6840
6847
  return client2 === undefined ? undefined : { client: client2, clientCertThumbprint: undefined };
6841
6848
  }
6842
6849
  const clientId = bodyClientId ?? basicClientId;
6843
6850
  if (clientId === undefined)
6844
6851
  return;
6845
- const candidate = await clientStore.findClient(clientId);
6852
+ const candidate = await resolveClient(clientId);
6846
6853
  const mtlsResult = await tryMtlsAuth({
6847
6854
  candidate,
6848
6855
  extract: config.extractTlsClientCert,
@@ -7058,6 +7065,7 @@ var oidcProviderRoutes = (config) => {
7058
7065
  backchannel_logout_session_supported: false,
7059
7066
  backchannel_logout_supported: true,
7060
7067
  code_challenge_methods_supported: ["S256"],
7068
+ client_id_metadata_document_supported: config.resolveClientIdMetadata !== undefined,
7061
7069
  dpop_signing_alg_values_supported: ["ES256"],
7062
7070
  end_session_endpoint: `${issuer}${endSessionRoute}`,
7063
7071
  grant_types_supported: grantTypes,
@@ -7115,7 +7123,7 @@ var oidcProviderRoutes = (config) => {
7115
7123
  idTokenHint: query.id_token_hint
7116
7124
  });
7117
7125
  const clientId = hint?.audClientId ?? query.client_id;
7118
- const client = clientId === undefined ? undefined : await config.clientStore.findClient(clientId);
7126
+ const client = clientId === undefined ? undefined : await resolveClient(clientId);
7119
7127
  const userSession = await loadSessionFromSource({
7120
7128
  authSessionStore,
7121
7129
  session: inMemorySession,
@@ -7167,7 +7175,7 @@ var oidcProviderRoutes = (config) => {
7167
7175
  return jsonResponse({ error: "invalid_request_uri" }, HTTP_BAD_REQUEST2);
7168
7176
  }
7169
7177
  const initialClientId = effectiveQuery.client_id;
7170
- const initialClient = initialClientId === undefined ? undefined : await clientStore.findClient(initialClientId);
7178
+ const initialClient = initialClientId === undefined ? undefined : await resolveClient(initialClientId);
7171
7179
  if (effectiveQuery.request !== undefined && initialClient !== undefined) {
7172
7180
  const parsed = await parseSignedRequestObject({
7173
7181
  client: initialClient,
@@ -24971,15 +24979,18 @@ var readAudience = (audience) => {
24971
24979
  };
24972
24980
  var createOidcAgentCredentialVerifier = ({
24973
24981
  issuer,
24982
+ isUsedDpopJti,
24983
+ maxDpopAgeMs,
24974
24984
  publicJwk,
24985
+ requireDpop = false,
24975
24986
  resource
24976
24987
  }) => {
24977
24988
  const verifier = async (request) => {
24978
24989
  const authorization = request.headers.get("authorization");
24979
- if (authorization === null || !authorization.startsWith(BEARER_PREFIX6)) {
24990
+ if (authorization === null || !authorization.startsWith(BEARER_PREFIX6) && !authorization.startsWith("DPoP ")) {
24980
24991
  return;
24981
24992
  }
24982
- const token = authorization.slice(BEARER_PREFIX6.length).trim();
24993
+ const token = authorization.slice(authorization.indexOf(" ") + 1).trim();
24983
24994
  if (token.length === 0)
24984
24995
  return;
24985
24996
  const verified = await verifyJwt(token, publicJwk);
@@ -24987,6 +24998,22 @@ var createOidcAgentCredentialVerifier = ({
24987
24998
  if (payload === undefined || payload.iss !== issuer || typeof payload.exp !== "number" || payload.exp <= Math.floor(Date.now() / MS_PER_SECOND7) || !readAudience(payload.aud).includes(resource) || typeof payload.client_id !== "string") {
24988
24999
  return;
24989
25000
  }
25001
+ const confirmation = payload.cnf;
25002
+ const boundJkt = typeof confirmation === "object" && confirmation !== null && typeof confirmation.jkt === "string" ? confirmation.jkt : undefined;
25003
+ if (boundJkt !== undefined || requireDpop) {
25004
+ if (!authorization.startsWith("DPoP "))
25005
+ return;
25006
+ const proof = await verifyDpopProof({
25007
+ accessToken: token,
25008
+ htm: request.method,
25009
+ htu: request.url,
25010
+ isUsedJti: isUsedDpopJti,
25011
+ ...maxDpopAgeMs === undefined ? {} : { maxAgeMs: maxDpopAgeMs },
25012
+ proof: request.headers.get("dpop") ?? undefined
25013
+ });
25014
+ if (proof === undefined || boundJkt === undefined || proof.jkt !== boundJkt)
25015
+ return;
25016
+ }
24990
25017
  return {
24991
25018
  agentId: payload.client_id,
24992
25019
  claims: payload,
@@ -25374,6 +25401,91 @@ var createPostgresApiKeyStore = (db) => ({
25374
25401
  await db.update(apiKeysTable).set({ last_used_at_ms: lastUsedAt }).where(eq(apiKeysTable.key_id, keyId));
25375
25402
  }
25376
25403
  });
25404
+ // src/oidc/clientIdMetadata.ts
25405
+ var secureUrl = (value) => {
25406
+ try {
25407
+ return new URL(value).protocol === "https:";
25408
+ } catch {
25409
+ return false;
25410
+ }
25411
+ };
25412
+ var validateClientIdMetadataDocument = (document, expectedClientId) => {
25413
+ const errors = [];
25414
+ if (document.client_id !== expectedClientId)
25415
+ errors.push("client_id does not match the metadata document URL");
25416
+ if (!secureUrl(document.client_id))
25417
+ errors.push("client_id must use HTTPS");
25418
+ if (!Array.isArray(document.redirect_uris) || document.redirect_uris.length === 0)
25419
+ errors.push("redirect_uris is required");
25420
+ for (const uri of document.redirect_uris ?? []) {
25421
+ try {
25422
+ const parsed = new URL(uri);
25423
+ if (parsed.protocol !== "https:" && parsed.hostname !== "localhost")
25424
+ errors.push("redirect URIs must use HTTPS or localhost");
25425
+ } catch {
25426
+ errors.push("redirect URI is invalid");
25427
+ }
25428
+ }
25429
+ for (const [name2, value] of [
25430
+ ["client_uri", document.client_uri],
25431
+ ["logo_uri", document.logo_uri],
25432
+ ["policy_uri", document.policy_uri],
25433
+ ["tos_uri", document.tos_uri],
25434
+ ["jwks_uri", document.jwks_uri]
25435
+ ]) {
25436
+ if (value !== undefined && !secureUrl(value))
25437
+ errors.push(`${name2} must use HTTPS`);
25438
+ }
25439
+ return errors;
25440
+ };
25441
+ var clientIdMetadataToOAuthClient = (document) => ({
25442
+ clientId: document.client_id,
25443
+ grantTypes: document.grant_types ?? ["authorization_code", "refresh_token"],
25444
+ ...document.jwks === undefined ? {} : { jwks: document.jwks.keys },
25445
+ ...document.jwks_uri === undefined ? {} : { jwksUri: document.jwks_uri },
25446
+ name: document.client_name ?? new URL(document.client_id).hostname,
25447
+ redirectUris: [...document.redirect_uris],
25448
+ scopes: document.scope?.split(" ").filter(Boolean) ?? []
25449
+ });
25450
+ var createClientIdMetadataResolver = ({
25451
+ fetch: fetcher,
25452
+ allow = async () => true,
25453
+ cacheTtlMs = 5 * 60 * 1000,
25454
+ maxBytes = 5 * 1024,
25455
+ now = Date.now
25456
+ }) => {
25457
+ const cache = new Map;
25458
+ return async (clientId) => {
25459
+ if (!secureUrl(clientId) || !await allow(clientId))
25460
+ return;
25461
+ const cached = cache.get(clientId);
25462
+ if (cached !== undefined && cached.expiresAt > now())
25463
+ return cached.client;
25464
+ const response = await fetcher(clientId, {
25465
+ headers: { accept: "application/json" },
25466
+ redirect: "error"
25467
+ });
25468
+ if (!response.ok)
25469
+ return;
25470
+ const declaredLength = Number(response.headers.get("content-length") ?? "0");
25471
+ if (declaredLength > maxBytes)
25472
+ return;
25473
+ const bytes = new Uint8Array(await response.arrayBuffer());
25474
+ if (bytes.byteLength > maxBytes)
25475
+ return;
25476
+ let document;
25477
+ try {
25478
+ document = JSON.parse(new TextDecoder().decode(bytes));
25479
+ } catch {
25480
+ return;
25481
+ }
25482
+ if (validateClientIdMetadataDocument(document, clientId).length > 0)
25483
+ return;
25484
+ const client = clientIdMetadataToOAuthClient(document);
25485
+ cache.set(clientId, { client, expiresAt: now() + cacheTtlMs });
25486
+ return client;
25487
+ };
25488
+ };
25377
25489
  // src/oidc/inMemoryStores.ts
25378
25490
  var DEFAULT_LIST_LIMIT = 100;
25379
25491
  var createInMemoryAuthorizationCodeStore = () => {
@@ -27995,6 +28107,7 @@ export {
27995
28107
  vaultEntriesTable,
27996
28108
  validateSession,
27997
28109
  validateEmailDeliverability,
28110
+ validateClientIdMetadataDocument,
27998
28111
  userSessionIdTypebox,
27999
28112
  userInfoChallengeHeader,
28000
28113
  updateRegisteredClient,
@@ -28326,6 +28439,7 @@ export {
28326
28439
  createFederatedTokenStore,
28327
28440
  createCustomOAuth2Client,
28328
28441
  createCredentialOffer,
28442
+ createClientIdMetadataResolver,
28329
28443
  createAuthHtmxRoutes,
28330
28444
  createAuditRedactor,
28331
28445
  createAuditEmitter,
@@ -28339,6 +28453,7 @@ export {
28339
28453
  constantTimeEqual,
28340
28454
  computeCertThumbprint,
28341
28455
  complianceRoutes,
28456
+ clientIdMetadataToOAuthClient,
28342
28457
  check2 as check,
28343
28458
  buildStatusClaim,
28344
28459
  buildIssuerMetadata,
@@ -28414,5 +28529,5 @@ export {
28414
28529
  AuthIdentityConflictError
28415
28530
  };
28416
28531
 
28417
- //# debugId=5F5850B4C390793864756E2164756E21
28532
+ //# debugId=D8A79A996B677FF164756E2164756E21
28418
28533
  //# sourceMappingURL=index.js.map