fastmcp 4.23.11 → 4.23.13

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.
Files changed (34) hide show
  1. package/dist/FastMCP.cjs +3 -3
  2. package/dist/FastMCP.d.cts +2 -2
  3. package/dist/FastMCP.d.ts +2 -2
  4. package/dist/FastMCP.js +2 -2
  5. package/dist/{OAuthProvider-BcDb5bOz.d.cts → OAuthProvider-84BWWDBF.d.cts} +55 -1
  6. package/dist/{OAuthProvider-BcDb5bOz.d.ts → OAuthProvider-84BWWDBF.d.ts} +55 -1
  7. package/dist/auth/index.cjs +4 -2
  8. package/dist/auth/index.cjs.map +1 -1
  9. package/dist/auth/index.d.cts +10 -2
  10. package/dist/auth/index.d.ts +10 -2
  11. package/dist/auth/index.js +3 -1
  12. package/dist/bin/fastmcp.cjs +22 -2
  13. package/dist/bin/fastmcp.cjs.map +1 -1
  14. package/dist/bin/fastmcp.js +22 -2
  15. package/dist/bin/fastmcp.js.map +1 -1
  16. package/dist/{chunk-X5ITW7FV.cjs → chunk-4P2GDM7W.cjs} +74 -35
  17. package/dist/chunk-4P2GDM7W.cjs.map +1 -0
  18. package/dist/{chunk-4OWHPJEL.cjs → chunk-BV4PWTT6.cjs} +214 -43
  19. package/dist/chunk-BV4PWTT6.cjs.map +1 -0
  20. package/dist/{chunk-G44SPVP7.js → chunk-CAQLTSJ5.js} +211 -40
  21. package/dist/chunk-CAQLTSJ5.js.map +1 -0
  22. package/dist/{chunk-XUUJOVC7.js → chunk-ULNQF5KG.js} +57 -18
  23. package/dist/chunk-ULNQF5KG.js.map +1 -0
  24. package/dist/examples/custom-routes.cjs +3 -3
  25. package/dist/examples/custom-routes.js +2 -2
  26. package/dist/openapi/index.cjs +7 -7
  27. package/dist/openapi/index.d.cts +1 -1
  28. package/dist/openapi/index.d.ts +1 -1
  29. package/dist/openapi/index.js +2 -2
  30. package/package.json +2 -1
  31. package/dist/chunk-4OWHPJEL.cjs.map +0 -1
  32. package/dist/chunk-G44SPVP7.js.map +0 -1
  33. package/dist/chunk-X5ITW7FV.cjs.map +0 -1
  34. package/dist/chunk-XUUJOVC7.js.map +0 -1
@@ -34,11 +34,26 @@ function requireScopes(...requiredScopes) {
34
34
  }
35
35
 
36
36
  // src/auth/OAuthProxy.ts
37
- import { randomBytes as randomBytes4 } from "crypto";
37
+ import { createHash as createHash2, randomBytes as randomBytes4 } from "crypto";
38
38
  import { z as z2 } from "zod";
39
39
 
40
40
  // src/auth/OAuthProxyStateStore.ts
41
41
  import { z } from "zod";
42
+
43
+ // src/auth/types.ts
44
+ var DEFAULT_ACCESS_TOKEN_TTL = 3600;
45
+ var DEFAULT_ACCESS_TOKEN_TTL_NO_REFRESH = 31536e3;
46
+ var DEFAULT_REFRESH_TOKEN_TTL = 2592e3;
47
+ var TOKEN_ENDPOINT_AUTH_METHODS = [
48
+ "client_secret_basic",
49
+ "client_secret_post",
50
+ "none"
51
+ ];
52
+ var DEFAULT_AUTHORIZATION_CODE_TTL = 300;
53
+ var DEFAULT_TRANSACTION_TTL = 600;
54
+ var DEFAULT_UPSTREAM_REQUEST_TIMEOUT_MS = 1e4;
55
+
56
+ // src/auth/OAuthProxyStateStore.ts
42
57
  var STORAGE_KEY_PREFIX = {
43
58
  client: "client:",
44
59
  code: "code:",
@@ -76,7 +91,11 @@ var proxyDcrClientStorageSchema = z.object({
76
91
  metadata: dcrClientMetadataSchema.optional(),
77
92
  redirectUris: z.array(z.string()),
78
93
  registeredAt: storedDateSchema,
79
- source: z.literal("cimd").optional()
94
+ source: z.literal("cimd").optional(),
95
+ // Optional: registrations written before it was recorded do not carry it.
96
+ // Listed so that it survives a read — an unlisted key is stripped, which
97
+ // would quietly turn every confidential client back into a legacy one.
98
+ tokenEndpointAuthMethod: z.enum(TOKEN_ENDPOINT_AUTH_METHODS).optional()
80
99
  });
81
100
  var upstreamTokenSetStorageSchema = z.object({
82
101
  accessToken: z.string(),
@@ -277,14 +296,6 @@ var OAuthProxyStateStore = class {
277
296
  }
278
297
  };
279
298
 
280
- // src/auth/types.ts
281
- var DEFAULT_ACCESS_TOKEN_TTL = 3600;
282
- var DEFAULT_ACCESS_TOKEN_TTL_NO_REFRESH = 31536e3;
283
- var DEFAULT_REFRESH_TOKEN_TTL = 2592e3;
284
- var DEFAULT_AUTHORIZATION_CODE_TTL = 300;
285
- var DEFAULT_TRANSACTION_TTL = 600;
286
- var DEFAULT_UPSTREAM_REQUEST_TIMEOUT_MS = 1e4;
287
-
288
299
  // src/cancelResponseBody.ts
289
300
  var cancelResponseBody = async (response) => {
290
301
  if (!response.body) {
@@ -484,7 +495,9 @@ var ClaimsExtractor = class {
484
495
  "iss",
485
496
  "jti",
486
497
  "nbf",
487
- "scope"
498
+ "scope",
499
+ // Whether a proxy token is an access or a refresh token.
500
+ "token_use"
488
501
  ]);
489
502
  constructor(config) {
490
503
  if (typeof config === "boolean") {
@@ -973,7 +986,10 @@ var JWTIssuer = class {
973
986
  jti,
974
987
  scope,
975
988
  // Merge additional claims (custom claims from upstream)
976
- ...additionalClaims || {}
989
+ ...additionalClaims || {},
990
+ // After the spread: what kind of token this is is the proxy's call, and
991
+ // no additional claim may change it.
992
+ token_use: "access"
977
993
  };
978
994
  return this.signToken(claims);
979
995
  }
@@ -992,7 +1008,9 @@ var JWTIssuer = class {
992
1008
  jti,
993
1009
  scope,
994
1010
  // Merge additional claims (custom claims from upstream)
995
- ...additionalClaims || {}
1011
+ ...additionalClaims || {},
1012
+ // After the spread — see issueAccessToken.
1013
+ token_use: "refresh"
996
1014
  };
997
1015
  return this.signToken(claims);
998
1016
  }
@@ -1332,6 +1350,10 @@ var RESERVED_AUTHORIZATION_PARAMS = /* @__PURE__ */ new Set([
1332
1350
  "scope",
1333
1351
  "state"
1334
1352
  ]);
1353
+ var passthroughRefreshBindingSchema = z2.object({
1354
+ clientAuthenticated: z2.boolean(),
1355
+ clientId: z2.string()
1356
+ });
1335
1357
  var OAuthProxy = class {
1336
1358
  claimsExtractor = null;
1337
1359
  cleanupInterval = null;
@@ -1453,17 +1475,18 @@ var OAuthProxy = class {
1453
1475
  "code_challenge_method 'plain' is not supported; use S256"
1454
1476
  );
1455
1477
  }
1456
- if (registeredClient.source === "cimd") {
1478
+ if (isPublicClient(registeredClient)) {
1479
+ const kind = describeUnauthenticatedClient(registeredClient);
1457
1480
  if (!params.code_challenge) {
1458
1481
  throw new OAuthProxyError(
1459
1482
  "invalid_request",
1460
- "code_challenge is required for Client ID Metadata Document clients"
1483
+ `code_challenge is required for ${kind}`
1461
1484
  );
1462
1485
  }
1463
1486
  if (params.code_challenge_method !== "S256") {
1464
1487
  throw new OAuthProxyError(
1465
1488
  "invalid_request",
1466
- "code_challenge_method must be S256 for Client ID Metadata Document clients"
1489
+ `code_challenge_method must be S256 for ${kind}`
1467
1490
  );
1468
1491
  }
1469
1492
  }
@@ -1497,10 +1520,7 @@ var OAuthProxy = class {
1497
1520
  "Only authorization_code grant type is supported"
1498
1521
  );
1499
1522
  }
1500
- const registeredClient = await this.resolveClient(request.client_id);
1501
- if (!registeredClient) {
1502
- throw new OAuthProxyError("invalid_client", "Unknown client_id");
1503
- }
1523
+ const { authenticated, client } = await this.authenticateClient(request);
1504
1524
  const consumed = await this.stateStore.consumeClientCode(request.code);
1505
1525
  if (!consumed) {
1506
1526
  throw new OAuthProxyError(
@@ -1516,13 +1536,16 @@ var OAuthProxy = class {
1516
1536
  );
1517
1537
  }
1518
1538
  const clientCode = consumed.clientCode;
1519
- if (clientCode.clientId !== request.client_id) {
1520
- throw new OAuthProxyError("invalid_client", "Client ID mismatch");
1539
+ if (clientCode.clientId !== client.clientId) {
1540
+ throw new OAuthProxyError(
1541
+ "invalid_grant",
1542
+ "Authorization code was issued to another client"
1543
+ );
1521
1544
  }
1522
- if (registeredClient.source === "cimd" && !clientCode.codeChallenge) {
1545
+ if (!authenticated && (!clientCode.codeChallenge || clientCode.codeChallengeMethod !== "S256")) {
1523
1546
  throw new OAuthProxyError(
1524
1547
  "invalid_grant",
1525
- "PKCE is required for Client ID Metadata Document clients"
1548
+ `S256 PKCE is required for ${describeUnauthenticatedClient(client)}`
1526
1549
  );
1527
1550
  }
1528
1551
  if (clientCode.codeChallenge) {
@@ -1550,7 +1573,8 @@ var OAuthProxy = class {
1550
1573
  if (this.config.enableTokenSwap && this.jwtIssuer) {
1551
1574
  return await this.issueSwappedTokens(
1552
1575
  clientCode.clientId,
1553
- clientCode.upstreamTokens
1576
+ clientCode.upstreamTokens,
1577
+ authenticated
1554
1578
  );
1555
1579
  } else {
1556
1580
  const response = {
@@ -1559,6 +1583,11 @@ var OAuthProxy = class {
1559
1583
  token_type: clientCode.upstreamTokens.tokenType
1560
1584
  };
1561
1585
  if (clientCode.upstreamTokens.refreshToken) {
1586
+ await this.savePassthroughRefreshBinding(
1587
+ clientCode.upstreamTokens.refreshToken,
1588
+ { clientAuthenticated: authenticated, clientId: client.clientId },
1589
+ clientCode.upstreamTokens.refreshExpiresIn
1590
+ );
1562
1591
  response.refresh_token = clientCode.upstreamTokens.refreshToken;
1563
1592
  }
1564
1593
  if (clientCode.upstreamTokens.idToken) {
@@ -1580,10 +1609,11 @@ var OAuthProxy = class {
1580
1609
  "Only refresh_token grant type is supported"
1581
1610
  );
1582
1611
  }
1612
+ const caller = await this.authenticateClient(request);
1583
1613
  if (this.config.enableTokenSwap && this.jwtIssuer) {
1584
- return await this.handleSwapModeRefresh(request);
1614
+ return await this.handleSwapModeRefresh(request, caller);
1585
1615
  }
1586
- return await this.handlePassthroughRefresh(request);
1616
+ return await this.handlePassthroughRefresh(request, caller);
1587
1617
  }
1588
1618
  /**
1589
1619
  * Get OAuth discovery metadata
@@ -1721,6 +1751,9 @@ var OAuthProxy = class {
1721
1751
  if (!result.valid || !result.claims?.jti) {
1722
1752
  return null;
1723
1753
  }
1754
+ if (result.claims.token_use !== "access") {
1755
+ return null;
1756
+ }
1724
1757
  const mapping = await this.tokenStorage.get(
1725
1758
  `mapping:${result.claims.jti}`
1726
1759
  );
@@ -1750,8 +1783,15 @@ var OAuthProxy = class {
1750
1783
  );
1751
1784
  }
1752
1785
  }
1786
+ const tokenEndpointAuthMethod = request.token_endpoint_auth_method || "client_secret_basic";
1787
+ if (!isTokenEndpointAuthMethod(tokenEndpointAuthMethod)) {
1788
+ throw new OAuthProxyError(
1789
+ "invalid_client_metadata",
1790
+ `Unsupported token_endpoint_auth_method: ${tokenEndpointAuthMethod}. Supported: ${TOKEN_ENDPOINT_AUTH_METHODS.join(", ")}`
1791
+ );
1792
+ }
1753
1793
  const proxyClientId = randomBytes4(16).toString("hex");
1754
- const proxyClientSecret = randomBytes4(32).toString("base64url");
1794
+ const proxyClientSecret = tokenEndpointAuthMethod === "none" ? void 0 : randomBytes4(32).toString("base64url");
1755
1795
  const client = {
1756
1796
  callbackUrl: request.redirect_uris[0],
1757
1797
  clientId: proxyClientId,
@@ -1770,7 +1810,8 @@ var OAuthProxy = class {
1770
1810
  tos_uri: request.tos_uri
1771
1811
  },
1772
1812
  redirectUris: request.redirect_uris,
1773
- registeredAt: /* @__PURE__ */ new Date()
1813
+ registeredAt: /* @__PURE__ */ new Date(),
1814
+ tokenEndpointAuthMethod
1774
1815
  };
1775
1816
  await this.stateStore.saveRegisteredClient(client);
1776
1817
  this.stateStore.cacheRegisteredClient(client);
@@ -1780,8 +1821,9 @@ var OAuthProxy = class {
1780
1821
  // Echo back optional metadata
1781
1822
  client_name: request.client_name,
1782
1823
  client_secret: proxyClientSecret,
1783
- client_secret_expires_at: 0,
1784
- // Never expires
1824
+ // RFC 7591 §3.2.1: required alongside a secret, and only then.
1825
+ client_secret_expires_at: proxyClientSecret ? 0 : void 0,
1826
+ // 0: never
1785
1827
  client_uri: request.client_uri,
1786
1828
  contacts: request.contacts,
1787
1829
  grant_types: request.grant_types || [
@@ -1797,11 +1839,49 @@ var OAuthProxy = class {
1797
1839
  scope: request.scope,
1798
1840
  software_id: request.software_id,
1799
1841
  software_version: request.software_version,
1800
- token_endpoint_auth_method: request.token_endpoint_auth_method || "client_secret_basic",
1842
+ token_endpoint_auth_method: tokenEndpointAuthMethod,
1801
1843
  tos_uri: request.tos_uri
1802
1844
  };
1803
1845
  return response;
1804
1846
  }
1847
+ /**
1848
+ * RFC 6749 §2.3 / §3.2.1: identify the client behind a token request and,
1849
+ * when it presents a secret, authenticate it.
1850
+ *
1851
+ * A client registered with a secret method must present its secret. A
1852
+ * public client must not present one: it was issued none. A registration
1853
+ * that predates recorded auth methods may do either — it was issued a
1854
+ * secret whatever it asked for — and is treated as unauthenticated when it
1855
+ * leaves it out, which the grants then hold it to.
1856
+ *
1857
+ * Throws `invalid_client` on any failure. Nothing here touches grant state,
1858
+ * so a caller that fails cannot spend a code or refresh token.
1859
+ */
1860
+ async authenticateClient(request) {
1861
+ if (!request.client_id) {
1862
+ throw new OAuthProxyError("invalid_client", "client_id is required");
1863
+ }
1864
+ const client = await this.resolveClient(request.client_id);
1865
+ if (!client) {
1866
+ throw new OAuthProxyError("invalid_client", "Unknown client_id");
1867
+ }
1868
+ if (request.client_secret) {
1869
+ if (!client.clientSecret || !secretsMatch(request.client_secret, client.clientSecret)) {
1870
+ throw new OAuthProxyError(
1871
+ "invalid_client",
1872
+ "Client authentication failed"
1873
+ );
1874
+ }
1875
+ return { authenticated: true, client };
1876
+ }
1877
+ if (client.tokenEndpointAuthMethod === "client_secret_basic" || client.tokenEndpointAuthMethod === "client_secret_post") {
1878
+ throw new OAuthProxyError(
1879
+ "invalid_client",
1880
+ "Client authentication failed: client_secret is required"
1881
+ );
1882
+ }
1883
+ return { authenticated: false, client };
1884
+ }
1805
1885
  /**
1806
1886
  * Calculate access token TTL from upstream tokens
1807
1887
  */
@@ -2019,7 +2099,29 @@ var OAuthProxy = class {
2019
2099
  /**
2020
2100
  * Handle passthrough mode refresh - forward refresh token directly to upstream
2021
2101
  */
2022
- async handlePassthroughRefresh(request) {
2102
+ async handlePassthroughRefresh(request, caller) {
2103
+ const bindingKey = this.passthroughRefreshKey(request.refresh_token);
2104
+ const binding = passthroughRefreshBindingSchema.safeParse(
2105
+ await this.tokenStorage.get(bindingKey)
2106
+ );
2107
+ if (!binding.success) {
2108
+ throw new OAuthProxyError(
2109
+ "invalid_grant",
2110
+ "Invalid or expired refresh token"
2111
+ );
2112
+ }
2113
+ if (binding.data.clientId !== caller.client.clientId) {
2114
+ throw new OAuthProxyError(
2115
+ "invalid_grant",
2116
+ "Refresh token was issued to another client"
2117
+ );
2118
+ }
2119
+ if (binding.data.clientAuthenticated && !caller.authenticated) {
2120
+ throw new OAuthProxyError(
2121
+ "invalid_client",
2122
+ "Client authentication failed: client_secret is required"
2123
+ );
2124
+ }
2023
2125
  const useBasicAuth = this.config.upstreamTokenEndpointAuthMethod === "client_secret_basic";
2024
2126
  const bodyParams = {
2025
2127
  grant_type: "refresh_token",
@@ -2050,6 +2152,17 @@ var OAuthProxy = class {
2050
2152
  throw new OAuthProxyError(errorCode, errorDescription);
2051
2153
  }
2052
2154
  const tokens = await this.parseTokenResponse(tokenResponse);
2155
+ await this.savePassthroughRefreshBinding(
2156
+ tokens.refresh_token || request.refresh_token,
2157
+ {
2158
+ clientAuthenticated: caller.authenticated,
2159
+ clientId: caller.client.clientId
2160
+ },
2161
+ tokens.refresh_expires_in
2162
+ );
2163
+ if (tokens.refresh_token && tokens.refresh_token !== request.refresh_token) {
2164
+ await this.tokenStorage.delete(bindingKey);
2165
+ }
2053
2166
  return {
2054
2167
  access_token: tokens.access_token,
2055
2168
  expires_in: tokens.expires_in || 3600,
@@ -2062,21 +2175,30 @@ var OAuthProxy = class {
2062
2175
  /**
2063
2176
  * Handle swap mode refresh - verify FastMCP JWT and issue new tokens
2064
2177
  */
2065
- async handleSwapModeRefresh(request) {
2178
+ async handleSwapModeRefresh(request, caller) {
2066
2179
  if (!this.jwtIssuer) {
2067
2180
  throw new Error("JWT issuer not initialized");
2068
2181
  }
2069
2182
  const verifyResult = await this.jwtIssuer.verify(request.refresh_token);
2070
- if (!verifyResult.valid) {
2183
+ if (!verifyResult.valid || !verifyResult.claims) {
2071
2184
  throw new OAuthProxyError(
2072
2185
  "invalid_grant",
2073
2186
  "Invalid or expired refresh token"
2074
2187
  );
2075
2188
  }
2076
- const jti = verifyResult.claims?.jti;
2189
+ if (verifyResult.claims.token_use !== "refresh") {
2190
+ throw new OAuthProxyError("invalid_grant", "Not a refresh token");
2191
+ }
2192
+ const jti = verifyResult.claims.jti;
2077
2193
  if (!jti) {
2078
2194
  throw new OAuthProxyError("invalid_grant", "Refresh token missing JTI");
2079
2195
  }
2196
+ if (verifyResult.claims.client_id !== caller.client.clientId) {
2197
+ throw new OAuthProxyError(
2198
+ "invalid_grant",
2199
+ "Refresh token was issued to another client"
2200
+ );
2201
+ }
2080
2202
  const mapping = await this.takeMapping(jti);
2081
2203
  if (!mapping) {
2082
2204
  throw new OAuthProxyError(
@@ -2085,6 +2207,18 @@ var OAuthProxy = class {
2085
2207
  );
2086
2208
  }
2087
2209
  try {
2210
+ if (mapping.clientId !== caller.client.clientId) {
2211
+ throw new OAuthProxyError(
2212
+ "invalid_grant",
2213
+ "Refresh token was issued to another client"
2214
+ );
2215
+ }
2216
+ if (mapping.clientAuthenticated !== false && !caller.authenticated) {
2217
+ throw new OAuthProxyError(
2218
+ "invalid_client",
2219
+ "Client authentication failed: client_secret is required"
2220
+ );
2221
+ }
2088
2222
  const upstreamTokens = await this.tokenStorage.get(
2089
2223
  `upstream:${mapping.upstreamTokenKey}`
2090
2224
  );
@@ -2120,7 +2254,8 @@ var OAuthProxy = class {
2120
2254
  return await this.issueSwappedTokensForRefresh(
2121
2255
  mapping.clientId,
2122
2256
  refreshedUpstreamTokens,
2123
- mapping.upstreamTokenKey
2257
+ mapping.upstreamTokenKey,
2258
+ caller.authenticated
2124
2259
  );
2125
2260
  } catch (error) {
2126
2261
  try {
@@ -2138,7 +2273,7 @@ var OAuthProxy = class {
2138
2273
  * Issue swapped tokens (JWT pattern)
2139
2274
  * Issues short-lived FastMCP JWTs and stores upstream tokens securely
2140
2275
  */
2141
- async issueSwappedTokens(clientId, upstreamTokens) {
2276
+ async issueSwappedTokens(clientId, upstreamTokens, clientAuthenticated) {
2142
2277
  if (!this.jwtIssuer) {
2143
2278
  throw new Error("JWT issuer not initialized");
2144
2279
  }
@@ -2197,6 +2332,7 @@ var OAuthProxy = class {
2197
2332
  await this.tokenStorage.save(
2198
2333
  `mapping:${refreshJti}`,
2199
2334
  {
2335
+ clientAuthenticated,
2200
2336
  clientId,
2201
2337
  createdAt: /* @__PURE__ */ new Date(),
2202
2338
  expiresAt: new Date(Date.now() + refreshTokenTtl * 1e3),
@@ -2213,7 +2349,7 @@ var OAuthProxy = class {
2213
2349
  /**
2214
2350
  * Issue swapped tokens for refresh flow
2215
2351
  */
2216
- async issueSwappedTokensForRefresh(clientId, upstreamTokens, upstreamTokenKey) {
2352
+ async issueSwappedTokensForRefresh(clientId, upstreamTokens, upstreamTokenKey, clientAuthenticated) {
2217
2353
  if (!this.jwtIssuer) {
2218
2354
  throw new Error("JWT issuer not initialized");
2219
2355
  }
@@ -2256,6 +2392,7 @@ var OAuthProxy = class {
2256
2392
  await this.tokenStorage.save(
2257
2393
  `mapping:${refreshJti}`,
2258
2394
  {
2395
+ clientAuthenticated,
2259
2396
  clientId,
2260
2397
  createdAt: /* @__PURE__ */ new Date(),
2261
2398
  expiresAt: new Date(Date.now() + refreshTokenTtl * 1e3),
@@ -2328,6 +2465,14 @@ var OAuthProxy = class {
2328
2465
  const rawJson = await response.json();
2329
2466
  return tokenResponseSchema.parse(rawJson);
2330
2467
  }
2468
+ /**
2469
+ * Storage key for the client binding of a passthrough-mode upstream refresh
2470
+ * token. Keyed by its hash: storage keys are not encrypted, and the token
2471
+ * itself is a live upstream credential.
2472
+ */
2473
+ passthroughRefreshKey(refreshToken) {
2474
+ return `passthrough-refresh:${sha256(refreshToken)}`;
2475
+ }
2331
2476
  /**
2332
2477
  * Redirect to upstream OAuth provider
2333
2478
  */
@@ -2460,6 +2605,18 @@ var OAuthProxy = class {
2460
2605
  }
2461
2606
  await this.tokenStorage.save(`mapping:${jti}`, mapping, ttl);
2462
2607
  }
2608
+ /**
2609
+ * Record which client a passthrough-mode upstream refresh token was handed
2610
+ * to. It lives as long as the upstream says the token does, or as the
2611
+ * configured refresh-token lifetime when it does not say.
2612
+ */
2613
+ async savePassthroughRefreshBinding(refreshToken, binding, refreshExpiresIn) {
2614
+ await this.tokenStorage.save(
2615
+ this.passthroughRefreshKey(refreshToken),
2616
+ binding,
2617
+ refreshExpiresIn ?? this.config.refreshTokenTtl ?? DEFAULT_REFRESH_TOKEN_TTL
2618
+ );
2619
+ }
2463
2620
  /**
2464
2621
  * Start periodic cleanup of expired transactions and codes
2465
2622
  */
@@ -2524,6 +2681,19 @@ var OAuthProxy = class {
2524
2681
  );
2525
2682
  }
2526
2683
  };
2684
+ var isPublicClient = (client) => client.source === "cimd" || client.tokenEndpointAuthMethod === "none";
2685
+ var describeUnauthenticatedClient = (client) => {
2686
+ if (client.source === "cimd") {
2687
+ return "Client ID Metadata Document clients";
2688
+ }
2689
+ if (client.tokenEndpointAuthMethod === "none") {
2690
+ return 'clients registered with token_endpoint_auth_method "none"';
2691
+ }
2692
+ return "clients that do not authenticate with their client_secret";
2693
+ };
2694
+ var isTokenEndpointAuthMethod = (value) => TOKEN_ENDPOINT_AUTH_METHODS.includes(value);
2695
+ var sha256 = (value) => createHash2("sha256").update(value).digest("base64url");
2696
+ var secretsMatch = (presented, expected) => equalsConstantTime(sha256(presented), sha256(expected));
2527
2697
  var OAuthProxyError = class extends Error {
2528
2698
  constructor(code, description, statusCode = 400) {
2529
2699
  super(code);
@@ -3136,6 +3306,7 @@ export {
3136
3306
  DEFAULT_ACCESS_TOKEN_TTL,
3137
3307
  DEFAULT_ACCESS_TOKEN_TTL_NO_REFRESH,
3138
3308
  DEFAULT_REFRESH_TOKEN_TTL,
3309
+ TOKEN_ENDPOINT_AUTH_METHODS,
3139
3310
  DEFAULT_AUTHORIZATION_CODE_TTL,
3140
3311
  DEFAULT_TRANSACTION_TTL,
3141
3312
  ConsentManager,
@@ -3153,4 +3324,4 @@ export {
3153
3324
  DiskStore,
3154
3325
  JWKSVerifier
3155
3326
  };
3156
- //# sourceMappingURL=chunk-G44SPVP7.js.map
3327
+ //# sourceMappingURL=chunk-CAQLTSJ5.js.map