@absolutejs/auth 0.29.0-beta.1 → 0.29.0-beta.2

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/index.d.ts CHANGED
@@ -13488,6 +13488,8 @@ export declare const auth: <UserType>({ providersConfiguration, authorizeRoute,
13488
13488
  grant_type?: string | undefined;
13489
13489
  code?: string | undefined;
13490
13490
  redirect_uri?: string | undefined;
13491
+ client_assertion?: string | undefined;
13492
+ client_assertion_type?: string | undefined;
13491
13493
  code_verifier?: string | undefined;
13492
13494
  subject_token?: string | undefined;
13493
13495
  subject_token_type?: string | undefined;
@@ -14900,9 +14902,10 @@ export { generateSigningKey, jwkThumbprint, signJwt, toPublicJwk, verifyJwt } fr
14900
14902
  export type { SigningKey } from './oidc/keys';
14901
14903
  export { verifyDpopProof } from './oidc/dpop';
14902
14904
  export type { DpopResult } from './oidc/dpop';
14903
- export { createInMemoryAuthorizationCodeStore, createInMemoryDeviceAuthorizationStore, createInMemoryLogoutDeliveryStore, createInMemoryOAuthClientStore, createInMemoryOidcRefreshTokenStore } from './oidc/inMemoryStores';
14905
+ export { CLIENT_ASSERTION_TYPE, verifyClientAssertion } from './oidc/clientAuth';
14906
+ export { createInMemoryAuthorizationCodeStore, createInMemoryClientAssertionJtiStore, createInMemoryDeviceAuthorizationStore, createInMemoryLogoutDeliveryStore, createInMemoryOAuthClientStore, createInMemoryOidcRefreshTokenStore } from './oidc/inMemoryStores';
14904
14907
  export { fanOutBackchannelLogout, mintLogoutToken, resolvePostLogoutRedirect, verifyIdTokenHint } from './oidc/logout';
14905
- export { createNeonAuthorizationCodeStore, createNeonDeviceAuthorizationStore, createNeonLogoutDeliveryStore, createNeonOAuthClientStore, createNeonOidcRefreshTokenStore, createPostgresAuthorizationCodeStore, createPostgresDeviceAuthorizationStore, createPostgresLogoutDeliveryStore, createPostgresOAuthClientStore, createPostgresOidcRefreshTokenStore, oauthClientsTable, oauthCodesTable, oauthDeviceAuthorizationsTable, oauthLogoutDeliveriesTable, oauthRefreshTokensTable } from './oidc/postgresStores';
14908
+ export { createNeonAuthorizationCodeStore, createNeonClientAssertionJtiStore, createNeonDeviceAuthorizationStore, createNeonLogoutDeliveryStore, createNeonOAuthClientStore, createNeonOidcRefreshTokenStore, createPostgresAuthorizationCodeStore, createPostgresClientAssertionJtiStore, createPostgresDeviceAuthorizationStore, createPostgresLogoutDeliveryStore, createPostgresOAuthClientStore, createPostgresOidcRefreshTokenStore, oauthClientAssertionJtisTable, oauthClientsTable, oauthCodesTable, oauthDeviceAuthorizationsTable, oauthLogoutDeliveriesTable, oauthRefreshTokensTable } from './oidc/postgresStores';
14906
14909
  export * from './adaptive/config';
14907
14910
  export * from './adaptive/fingerprint';
14908
14911
  export * from './adaptive/types';
package/dist/index.js CHANGED
@@ -4452,6 +4452,105 @@ var exchangeDeviceCode = async ({
4452
4452
  return { ...tokenSet, ok: true };
4453
4453
  };
4454
4454
 
4455
+ // src/oidc/clientAuth.ts
4456
+ var CLIENT_ASSERTION_TYPE = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer";
4457
+ var MAX_ASSERTION_LIFETIME_MINUTES = 5;
4458
+ var SECONDS_PER_MINUTE = 60;
4459
+ var MAX_ASSERTION_LIFETIME_MS = MAX_ASSERTION_LIFETIME_MINUTES * SECONDS_PER_MINUTE * MILLISECONDS_IN_A_SECOND;
4460
+ var jwksCache = new Map;
4461
+ var JWKS_CACHE_TTL_MS = SECONDS_PER_MINUTE * MILLISECONDS_IN_A_SECOND;
4462
+ var JWKS_FETCH_TIMEOUT_SECONDS = 5;
4463
+ var JWKS_FETCH_TIMEOUT_MS = JWKS_FETCH_TIMEOUT_SECONDS * MILLISECONDS_IN_A_SECOND;
4464
+ var fetchJwksUri = async (jwksUri) => {
4465
+ const cached = jwksCache.get(jwksUri);
4466
+ if (cached && Date.now() - cached.fetchedAt < JWKS_CACHE_TTL_MS) {
4467
+ return cached.jwks;
4468
+ }
4469
+ try {
4470
+ const response = await fetch(jwksUri, {
4471
+ signal: AbortSignal.timeout(JWKS_FETCH_TIMEOUT_MS)
4472
+ });
4473
+ if (!response.ok)
4474
+ return;
4475
+ const body = await response.json();
4476
+ if (!Array.isArray(body.keys))
4477
+ return;
4478
+ jwksCache.set(jwksUri, { fetchedAt: Date.now(), jwks: body.keys });
4479
+ return body.keys;
4480
+ } catch {
4481
+ return;
4482
+ }
4483
+ };
4484
+ var resolveClientJwks = async (client) => {
4485
+ if (client.jwks && client.jwks.length > 0)
4486
+ return client.jwks;
4487
+ if (client.jwksUri !== undefined)
4488
+ return fetchJwksUri(client.jwksUri);
4489
+ return;
4490
+ };
4491
+ var verifyAgainstAny = async (assertion, candidates) => {
4492
+ for (const jwk of candidates) {
4493
+ const verified = await verifyJwt(assertion, jwk);
4494
+ if (verified !== undefined)
4495
+ return verified;
4496
+ }
4497
+ return;
4498
+ };
4499
+ var verifyClientAssertion = async ({
4500
+ assertion,
4501
+ expectedAudience,
4502
+ jtiStore,
4503
+ resolveClient
4504
+ }) => {
4505
+ const [, payloadSegment] = assertion.split(".");
4506
+ if (payloadSegment === undefined)
4507
+ return;
4508
+ let payload;
4509
+ try {
4510
+ const parsed = JSON.parse(Buffer.from(payloadSegment, "base64url").toString("utf8"));
4511
+ if (typeof parsed !== "object" || parsed === null)
4512
+ return;
4513
+ payload = parsed;
4514
+ } catch {
4515
+ return;
4516
+ }
4517
+ const {
4518
+ aud,
4519
+ exp,
4520
+ iss,
4521
+ jti,
4522
+ sub
4523
+ } = payload;
4524
+ if (typeof iss !== "string" || typeof sub !== "string" || iss !== sub || typeof exp !== "number") {
4525
+ return;
4526
+ }
4527
+ const audMatches = typeof aud === "string" && aud === expectedAudience || Array.isArray(aud) && aud.includes(expectedAudience);
4528
+ if (!audMatches)
4529
+ return;
4530
+ const expMs = exp * MILLISECONDS_IN_A_SECOND;
4531
+ const now = Date.now();
4532
+ if (expMs <= now || expMs - now > MAX_ASSERTION_LIFETIME_MS) {
4533
+ return;
4534
+ }
4535
+ const client = await resolveClient(iss);
4536
+ if (client === undefined)
4537
+ return;
4538
+ const candidates = await resolveClientJwks(client);
4539
+ if (candidates === undefined || candidates.length === 0)
4540
+ return;
4541
+ const verified = await verifyAgainstAny(assertion, candidates);
4542
+ if (verified === undefined)
4543
+ return;
4544
+ if (jtiStore !== undefined) {
4545
+ if (typeof jti !== "string")
4546
+ return;
4547
+ const fresh = await jtiStore.recordIfFresh(client.clientId, jti, expMs);
4548
+ if (!fresh)
4549
+ return;
4550
+ }
4551
+ return client;
4552
+ };
4553
+
4455
4554
  // src/oidc/dpop.ts
4456
4555
  var DEFAULT_MAX_AGE_MS = 60000;
4457
4556
  var SECONDS_TO_MS = 1000;
@@ -4694,6 +4793,28 @@ var oidcProviderRoutes = (config) => {
4694
4793
  const matches = await constantTimeEqual(await hashToken(clientSecret), client.hashedSecret);
4695
4794
  return matches ? client : undefined;
4696
4795
  };
4796
+ const authenticateTokenClient = async ({
4797
+ basicClientId,
4798
+ basicClientSecret,
4799
+ bodyClientAssertion,
4800
+ bodyClientAssertionType,
4801
+ bodyClientId,
4802
+ bodyClientSecret
4803
+ }) => {
4804
+ if (bodyClientAssertion !== undefined && bodyClientAssertionType === CLIENT_ASSERTION_TYPE) {
4805
+ return verifyClientAssertion({
4806
+ assertion: bodyClientAssertion,
4807
+ expectedAudience: tokenUrl,
4808
+ jtiStore: config.clientAssertionJtiStore,
4809
+ resolveClient: clientStore.findClient
4810
+ });
4811
+ }
4812
+ const clientId = bodyClientId ?? basicClientId;
4813
+ const clientSecret = bodyClientSecret ?? basicClientSecret;
4814
+ if (clientId === undefined)
4815
+ return;
4816
+ return authenticateClient(clientId, clientSecret);
4817
+ };
4697
4818
  const grantAuthorizationCode = async (client, body, dpop) => {
4698
4819
  const {
4699
4820
  code,
@@ -4842,8 +4963,10 @@ var oidcProviderRoutes = (config) => {
4842
4963
  token_endpoint_auth_methods_supported: [
4843
4964
  "client_secret_basic",
4844
4965
  "client_secret_post",
4845
- "none"
4846
- ]
4966
+ "none",
4967
+ "private_key_jwt"
4968
+ ],
4969
+ token_endpoint_auth_signing_alg_values_supported: ["ES256"]
4847
4970
  };
4848
4971
  if (config.deviceAuthorizationStore) {
4849
4972
  discovery.device_authorization_endpoint = `${issuer}${deviceAuthorizationRoute}`;
@@ -4968,12 +5091,14 @@ var oidcProviderRoutes = (config) => {
4968
5091
  })
4969
5092
  }).post(tokenRoute, async ({ body, headers }) => {
4970
5093
  const basic = readBasicAuth2(headers.authorization);
4971
- const clientId = basic.clientId ?? body.client_id;
4972
- const clientSecret = basic.clientSecret ?? body.client_secret;
4973
- if (clientId === undefined) {
4974
- return oauthError2(HTTP_UNAUTHORIZED2, "invalid_client");
4975
- }
4976
- const client = await authenticateClient(clientId, clientSecret);
5094
+ const client = await authenticateTokenClient({
5095
+ basicClientId: basic.clientId,
5096
+ basicClientSecret: basic.clientSecret,
5097
+ bodyClientAssertion: body.client_assertion,
5098
+ bodyClientAssertionType: body.client_assertion_type,
5099
+ bodyClientId: body.client_id,
5100
+ bodyClientSecret: body.client_secret
5101
+ });
4977
5102
  if (client === undefined) {
4978
5103
  return oauthError2(HTTP_UNAUTHORIZED2, "invalid_client");
4979
5104
  }
@@ -4993,6 +5118,8 @@ var oidcProviderRoutes = (config) => {
4993
5118
  }, {
4994
5119
  body: t12.Object({
4995
5120
  audience: t12.Optional(t12.String()),
5121
+ client_assertion: t12.Optional(t12.String()),
5122
+ client_assertion_type: t12.Optional(t12.String()),
4996
5123
  client_id: t12.Optional(t12.String()),
4997
5124
  client_secret: t12.Optional(t12.String()),
4998
5125
  code: t12.Optional(t12.String()),
@@ -20310,6 +20437,23 @@ var createInMemoryAuthorizationCodeStore = () => {
20310
20437
  }
20311
20438
  };
20312
20439
  };
20440
+ var createInMemoryClientAssertionJtiStore = () => {
20441
+ const seen = new Map;
20442
+ return {
20443
+ recordIfFresh: async (clientId, jti, expiresAt) => {
20444
+ const now = Date.now();
20445
+ for (const [key, expiry] of seen) {
20446
+ if (expiry < now)
20447
+ seen.delete(key);
20448
+ }
20449
+ const composite = `${clientId}|${jti}`;
20450
+ if (seen.has(composite))
20451
+ return false;
20452
+ seen.set(composite, expiresAt);
20453
+ return true;
20454
+ }
20455
+ };
20456
+ };
20313
20457
  var createInMemoryDeviceAuthorizationStore = () => {
20314
20458
  const byDeviceCode = new Map;
20315
20459
  return {
@@ -20384,12 +20528,22 @@ var createInMemoryOidcRefreshTokenStore = () => {
20384
20528
  var URL_LENGTH = 2048;
20385
20529
  var DEFAULT_LIST_LIMIT2 = 100;
20386
20530
  var ID_LENGTH7 = 255;
20531
+ var oauthClientAssertionJtisTable = pgTable("auth_oauth_client_assertion_jtis", {
20532
+ client_id: varchar("client_id", { length: ID_LENGTH7 }).notNull(),
20533
+ composite_key: varchar("composite_key", {
20534
+ length: ID_LENGTH7
20535
+ }).primaryKey(),
20536
+ expires_at_ms: bigint("expires_at_ms", { mode: "number" }).notNull(),
20537
+ jti: varchar("jti", { length: ID_LENGTH7 }).notNull()
20538
+ });
20387
20539
  var oauthClientsTable = pgTable("auth_oauth_clients", {
20388
20540
  backchannel_logout_uri: varchar("backchannel_logout_uri", {
20389
20541
  length: URL_LENGTH
20390
20542
  }),
20391
20543
  client_id: varchar("client_id", { length: ID_LENGTH7 }).primaryKey(),
20392
20544
  hashed_secret: varchar("hashed_secret", { length: ID_LENGTH7 }),
20545
+ jwks_json: jsonb("jwks_json").$type(),
20546
+ jwks_uri: varchar("jwks_uri", { length: URL_LENGTH }),
20393
20547
  name: varchar("name", { length: ID_LENGTH7 }).notNull(),
20394
20548
  post_logout_redirect_uris: text("post_logout_redirect_uris").array(),
20395
20549
  redirect_uris: text("redirect_uris").array().notNull(),
@@ -20446,6 +20600,8 @@ var toClient2 = (row) => ({
20446
20600
  backchannelLogoutUri: row.backchannel_logout_uri ?? undefined,
20447
20601
  clientId: row.client_id,
20448
20602
  hashedSecret: row.hashed_secret ?? undefined,
20603
+ jwks: row.jwks_json ?? undefined,
20604
+ jwksUri: row.jwks_uri ?? undefined,
20449
20605
  name: row.name,
20450
20606
  postLogoutRedirectUris: row.post_logout_redirect_uris ?? undefined,
20451
20607
  redirectUris: row.redirect_uris,
@@ -20520,6 +20676,7 @@ var toRefreshValues = (token) => ({
20520
20676
  user_id: token.userId
20521
20677
  });
20522
20678
  var createNeonAuthorizationCodeStore = (databaseUrl) => createPostgresAuthorizationCodeStore(createNeonDatabase(databaseUrl));
20679
+ var createNeonClientAssertionJtiStore = (databaseUrl) => createPostgresClientAssertionJtiStore(createNeonDatabase(databaseUrl));
20523
20680
  var createNeonDeviceAuthorizationStore = (databaseUrl) => createPostgresDeviceAuthorizationStore(createNeonDatabase(databaseUrl));
20524
20681
  var createNeonLogoutDeliveryStore = (databaseUrl) => createPostgresLogoutDeliveryStore(createNeonDatabase(databaseUrl));
20525
20682
  var createNeonOAuthClientStore = (databaseUrl) => createPostgresOAuthClientStore(createNeonDatabase(databaseUrl));
@@ -20533,6 +20690,23 @@ var createPostgresAuthorizationCodeStore = (db) => ({
20533
20690
  await db.insert(oauthCodesTable).values(toCodeValues(code));
20534
20691
  }
20535
20692
  });
20693
+ var createPostgresClientAssertionJtiStore = (db) => ({
20694
+ recordIfFresh: async (clientId, jti, expiresAt) => {
20695
+ await db.delete(oauthClientAssertionJtisTable).where(lt(oauthClientAssertionJtisTable.expires_at_ms, Date.now()));
20696
+ const compositeKey = `${clientId}|${jti}`;
20697
+ try {
20698
+ await db.insert(oauthClientAssertionJtisTable).values({
20699
+ client_id: clientId,
20700
+ composite_key: compositeKey,
20701
+ expires_at_ms: expiresAt,
20702
+ jti
20703
+ });
20704
+ return true;
20705
+ } catch {
20706
+ return false;
20707
+ }
20708
+ }
20709
+ });
20536
20710
  var createPostgresDeviceAuthorizationStore = (db) => ({
20537
20711
  deleteByDeviceCodeHash: async (deviceCodeHash) => {
20538
20712
  await db.delete(oauthDeviceAuthorizationsTable).where(eq(oauthDeviceAuthorizationsTable.device_code_hash, deviceCodeHash));
@@ -22033,6 +22207,7 @@ export {
22033
22207
  verifyIdTokenHint,
22034
22208
  verifyHcaptcha,
22035
22209
  verifyDpopProof,
22210
+ verifyClientAssertion,
22036
22211
  verifyAuditChain,
22037
22212
  verifyApiKey,
22038
22213
  verifyAccessToken,
@@ -22101,6 +22276,7 @@ export {
22101
22276
  oauthDeviceAuthorizationsTable,
22102
22277
  oauthCodesTable,
22103
22278
  oauthClientsTable,
22279
+ oauthClientAssertionJtisTable,
22104
22280
  mintLogoutToken,
22105
22281
  mfaTotpRoutes,
22106
22282
  mfaRoutes,
@@ -22208,6 +22384,7 @@ export {
22208
22384
  createPostgresKnownDeviceStore,
22209
22385
  createPostgresDeviceAuthorizationStore,
22210
22386
  createPostgresCredentialStore,
22387
+ createPostgresClientAssertionJtiStore,
22211
22388
  createPostgresAuthorizationCodeStore,
22212
22389
  createPostgresAuditSink,
22213
22390
  createPostgresApiKeyStore,
@@ -22237,6 +22414,7 @@ export {
22237
22414
  createNeonDeviceAuthorizationStore,
22238
22415
  createNeonDatabase,
22239
22416
  createNeonCredentialStore,
22417
+ createNeonClientAssertionJtiStore,
22240
22418
  createNeonAuthorizationCodeStore,
22241
22419
  createNeonAuthSessionStore,
22242
22420
  createNeonAuditSink,
@@ -22267,6 +22445,7 @@ export {
22267
22445
  createInMemoryKnownDeviceStore,
22268
22446
  createInMemoryDeviceAuthorizationStore,
22269
22447
  createInMemoryCredentialStore,
22448
+ createInMemoryClientAssertionJtiStore,
22270
22449
  createInMemoryCheckCache,
22271
22450
  createInMemoryAuthorizationCodeStore,
22272
22451
  createInMemoryAuthSessionStore,
@@ -22332,8 +22511,9 @@ export {
22332
22511
  DEFAULT_INVITATION_TTL_MS,
22333
22512
  DEFAULT_CREDENTIAL_SESSION_TTL_MS,
22334
22513
  DEFAULT_BACKUP_CODE_COUNT,
22514
+ CLIENT_ASSERTION_TYPE,
22335
22515
  AuthIdentityConflictError
22336
22516
  };
22337
22517
 
22338
- //# debugId=9518CC46948D590564756E2164756E21
22518
+ //# debugId=A9BAB004D30595D664756E2164756E21
22339
22519
  //# sourceMappingURL=index.js.map