@growth-labs/auth 0.12.0 → 0.14.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/README.md CHANGED
@@ -50,6 +50,7 @@ auth({
50
50
  2. Verifies access token JWT against the internal issuer's JWKS (`{issuerInternal ?? issuer}/.well-known/jwks.json`, cached in Worker memory)
51
51
  3. Requires the access token `aud` to match `resource`; wrong or missing audiences remain unauthenticated
52
52
  4. If expired but refresh token valid → silent refresh, verify the new access token audience, then set new cookies
53
+ — one exchange per token per isolate, and never on a beacon or a prefetch (those are answered as a guest / `425`, and the next real request refreshes)
53
54
  5. Populates `context.locals.user` as `User | null`
54
55
  6. If path matches `gatedPaths` and `user` is null → redirect to `loginPath`
55
56
 
@@ -1 +1 @@
1
- {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/middleware/auth.ts"],"names":[],"mappings":"AACA,OAAO,sBAAsB,CAAA;AAc7B,wBAAsB,SAAS,CAC9B,OAAO,EAAE;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,GAAG,EAAE,GAAG,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,EACxE,IAAI,EAAE,MAAM,OAAO,CAAC,QAAQ,CAAC,GAC3B,OAAO,CAAC,QAAQ,CAAC,CAsGnB"}
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/middleware/auth.ts"],"names":[],"mappings":"AACA,OAAO,sBAAsB,CAAA;AAmB7B,wBAAsB,SAAS,CAC9B,OAAO,EAAE;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,GAAG,EAAE,GAAG,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,EACxE,IAAI,EAAE,MAAM,OAAO,CAAC,QAAQ,CAAC,GAC3B,OAAO,CAAC,QAAQ,CAAC,CA0KnB"}
@@ -4,6 +4,7 @@ import { resolveClientSecret } from '../utils/client-secret.js';
4
4
  import { getCookie, setTokenCookies } from '../utils/cookies.js';
5
5
  import { secretResolutionFailureResponse } from '../utils/fail-closed.js';
6
6
  import { resolveInternalIssuer } from '../utils/issuer.js';
7
+ import { classifyRefreshCarrier, prefetchDeferredResponse, } from '../utils/refresh-carrier.js';
7
8
  import { refreshTokens, verifyAccessToken } from '../utils/tokens.js';
8
9
  const PRIVATE_NO_STORE_HEADERS = [
9
10
  'Cache-Control',
@@ -54,9 +55,22 @@ export async function onRequest(context, next) {
54
55
  }
55
56
  }
56
57
  }
58
+ // A refresh rotates the token, and only a response the browser is waiting
59
+ // for can deliver the successor. A beacon or a prefetch that would need one
60
+ // does not get it: the request is answered as `deferred` and the next
61
+ // consumable request refreshes instead (refresh-carrier.ts says why).
62
+ let refreshFailure = null;
63
+ let deferredCarrier = null;
57
64
  if (!user && refreshToken) {
65
+ const carrier = classifyRefreshCarrier(request);
66
+ if (carrier !== 'consumable')
67
+ deferredCarrier = carrier;
68
+ }
69
+ if (!user && refreshToken && deferredCarrier === null) {
58
70
  const clientId = options.clientId || context.url.hostname;
59
- refreshed = await refreshTokens(issuer, refreshToken, clientId, resolvedClientSecret, options.resource);
71
+ refreshed = await refreshTokens(issuer, refreshToken, clientId, resolvedClientSecret, options.resource, (failure) => {
72
+ refreshFailure = failure;
73
+ });
60
74
  if (refreshed) {
61
75
  user = {
62
76
  id: refreshed.claims.sub,
@@ -66,11 +80,66 @@ export async function onRequest(context, next) {
66
80
  };
67
81
  }
68
82
  }
69
- // 3. Populate context.locals.user
83
+ // 3. Populate context.locals.user and say HOW the session resolved. A site
84
+ // cannot act on `user === null` alone: an anonymous visitor, a member whose
85
+ // refresh token the issuer has definitively rejected, and a member caught by
86
+ // a passing issuer fault all look the same, and the second one was being
87
+ // rendered the locked page with dead cookies he could only fix by logging
88
+ // out and back in.
70
89
  context.locals.user = user;
90
+ const session = resolveSessionState({
91
+ user,
92
+ refreshed: refreshed !== null,
93
+ hadAccessToken: accessToken !== null,
94
+ hadRefreshToken: refreshToken !== null,
95
+ refreshFailure,
96
+ deferred: deferredCarrier !== null,
97
+ });
98
+ context.locals.authSession = session;
99
+ // The cookies are deliberately left alone, even when the state is `expired`.
100
+ // The issuer keeps refresh tokens in an eventually consistent store, so a
101
+ // token only seconds old can read as missing from another location and be
102
+ // answered invalid_grant; today that costs one anonymous page and heals on
103
+ // the next navigation, and clearing would turn it into a forced sign-in.
104
+ // Recovery never needed the clear: signing in again overwrites them.
105
+ const documentNavigation = isDocumentNavigation(request);
106
+ if (deferredCarrier !== null) {
107
+ try {
108
+ console.info('[auth] session refresh deferred', {
109
+ carrier: deferredCarrier,
110
+ pathname: context.url.pathname,
111
+ });
112
+ }
113
+ catch {
114
+ /* a patched console must not turn a guest page into a 500 */
115
+ }
116
+ // A prefetched document rendered as a guest would be shown to the member
117
+ // on navigation as if they had signed out; a non-2xx is dropped instead.
118
+ if (deferredCarrier === 'prefetch')
119
+ return prefetchDeferredResponse();
120
+ }
121
+ else if (!user && refreshToken) {
122
+ const failure = refreshFailure ?? { reason: 'unreported' };
123
+ try {
124
+ console.warn('[auth] session refresh failed', {
125
+ ...failure,
126
+ pathname: context.url.pathname,
127
+ documentNavigation,
128
+ session,
129
+ });
130
+ }
131
+ catch {
132
+ /* a patched console must not turn a guest page into a 500 */
133
+ }
134
+ }
71
135
  // 4. Gate protected paths
72
136
  if (!user && isGatedPath(context.url.pathname, options.gatedPaths)) {
73
- const loginUrl = new URL(`${options.loginPath}?redirect=${encodeURIComponent(context.url.pathname)}`, context.url.origin);
137
+ // Only the issuer ending the session earns the error code, which is the
138
+ // one the login renderer already knows how to explain. A rejected access
139
+ // token with no refresh token beside it (wrong audience, say) is not a
140
+ // session that ended, and its redirect stays exactly as it always was.
141
+ const sessionEnded = refreshFailure?.reason === 'invalid_grant';
142
+ const loginUrl = new URL(`${options.loginPath}?redirect=${encodeURIComponent(context.url.pathname)}${sessionEnded ? '&error=session_expired' : ''}`, context.url.origin);
74
143
  return Response.redirect(loginUrl.toString(), 302);
75
144
  }
76
145
  // 5. Continue to the page
@@ -94,6 +163,30 @@ export async function onRequest(context, next) {
94
163
  }
95
164
  return response;
96
165
  }
166
+ function resolveSessionState(input) {
167
+ if (input.user)
168
+ return input.refreshed ? 'refreshed' : 'verified';
169
+ if (!input.hadAccessToken && !input.hadRefreshToken)
170
+ return 'anonymous';
171
+ if (input.deferred)
172
+ return 'deferred';
173
+ // A rejected access token with no refresh token beside it cannot be revived.
174
+ if (!input.hadRefreshToken)
175
+ return 'expired';
176
+ return input.refreshFailure?.reason === 'invalid_grant' ? 'expired' : 'refresh_failed';
177
+ }
178
+ /** Sec-Fetch-* is authoritative where the browser sends it; Accept is the fallback. */
179
+ function isDocumentNavigation(request) {
180
+ if (request.method !== 'GET')
181
+ return false;
182
+ const dest = request.headers.get('sec-fetch-dest');
183
+ if (dest)
184
+ return dest === 'document';
185
+ const mode = request.headers.get('sec-fetch-mode');
186
+ if (mode)
187
+ return mode === 'navigate';
188
+ return (request.headers.get('accept') ?? '').includes('text/html');
189
+ }
97
190
  function isGatedPath(pathname, gatedPaths) {
98
191
  return gatedPaths.some((pattern) => {
99
192
  if (pattern.endsWith('/*')) {
@@ -1 +1 @@
1
- {"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/middleware/auth.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/middleware/auth.ts"],"names":[],"mappings":"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"}
@@ -36,7 +36,7 @@ export async function GET(context) {
36
36
  }
37
37
  // Emit auth_logout (non-blocking) when the consumer has @growth-labs/analytics
38
38
  // wired. Without analytics installed this is a no-op (analyticsAvailable
39
- // returns false). Documented in packages-docs/event-registry.md.
39
+ // returns false). Documented in packages/analytics/EVENT_REGISTRY.md (growth-labs/publishing).
40
40
  if (analyticsAvailable(context)) {
41
41
  const promise = emitAuthEvent(context, 'auth_logout', {
42
42
  revoked: refreshToken ? 'true' : 'false',
@@ -1 +1 @@
1
- {"version":3,"file":"logout.js","sourceRoot":"","sources":["../../src/routes/logout.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"logout.js","sourceRoot":"","sources":["../../src/routes/logout.ts"],"names":[],"mappings":"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"}
package/dist/types.d.ts CHANGED
@@ -131,10 +131,26 @@ export interface TokenExchangeResult {
131
131
  image?: string;
132
132
  };
133
133
  }
134
+ /**
135
+ * How the request's session resolved. `user === null` alone cannot tell a site
136
+ * what to do: `anonymous` never signed in; `expired` holds credentials that
137
+ * could not be revived on this request — the issuer answered the refresh token
138
+ * `invalid_grant`, or a rejected access token arrived with no refresh token
139
+ * beside it — so offer a sign-in-again path; `refresh_failed` hit a fault that
140
+ * may pass; `deferred` holds a refresh token this request deliberately did not
141
+ * spend, because it is a beacon or a prefetch whose response the browser would
142
+ * not apply (0.14.0) — the next consumable request refreshes, so treat it as a
143
+ * guest request and nothing more. NONE is proof the session is dead (the
144
+ * issuer's token store is eventually consistent), so never clear cookies on
145
+ * them: signing in again overwrites the cookies, and the middleware itself
146
+ * clears nothing.
147
+ */
148
+ export type AuthSessionState = 'anonymous' | 'verified' | 'refreshed' | 'expired' | 'refresh_failed' | 'deferred';
134
149
  declare global {
135
150
  namespace App {
136
151
  interface Locals {
137
152
  user: User | null;
153
+ authSession?: AuthSessionState;
138
154
  synthetic?: boolean;
139
155
  }
140
156
  }
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
@@ -7,6 +7,7 @@ export { type BridgeMigrationOptions, type BridgeMigrationStatement, buildLegacy
7
7
  export { signIdentityCookie, verifyIdentityCookie } from './identity-cookie.js';
8
8
  export { EXCHANGE_SECRET_FAILURE, type ExchangeSecretFailure, getAuthorizationUrl, getMagicLinkUrl, getPasswordLoginUrl, } from './openauth.js';
9
9
  export { createPKCEChallenge, generatePKCE } from './pkce.js';
10
+ export { classifyRefreshCarrier, prefetchDeferredResponse, type RefreshCarrier, } from './refresh-carrier.js';
10
11
  export { generateState, verifyState } from './state.js';
11
- export type { VerifiedClaims } from './tokens.js';
12
+ export type { RefreshFailure, RefreshFailureReason, VerifiedClaims } from './tokens.js';
12
13
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,KAAK,qBAAqB,EAC1B,KAAK,qBAAqB,EAC1B,KAAK,qBAAqB,EAC1B,oBAAoB,EACpB,aAAa,EACb,oBAAoB,EACpB,oBAAoB,EACpB,0BAA0B,EAC1B,qBAAqB,EACrB,cAAc,GACd,MAAM,gBAAgB,CAAA;AACvB,YAAY,EACX,mBAAmB,EACnB,+BAA+B,EAC/B,kBAAkB,GAClB,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EAAE,qBAAqB,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAA;AAC/E,OAAO,EACN,kBAAkB,EAClB,KAAK,OAAO,EACZ,oBAAoB,EACpB,UAAU,EACV,aAAa,EACb,aAAa,GACb,MAAM,UAAU,CAAA;AACjB,OAAO,EAAE,2BAA2B,EAAE,MAAM,kBAAkB,CAAA;AAC9D,OAAO,EACN,KAAK,sBAAsB,EAC3B,KAAK,wBAAwB,EAC7B,gCAAgC,EAChC,mCAAmC,EACnC,wBAAwB,EACxB,iBAAiB,EACjB,0BAA0B,EAC1B,yBAAyB,EACzB,uBAAuB,EACvB,2BAA2B,EAC3B,wBAAwB,EACxB,6BAA6B,GAC7B,MAAM,eAAe,CAAA;AACtB,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAA;AAC/E,OAAO,EACN,uBAAuB,EACvB,KAAK,qBAAqB,EAC1B,mBAAmB,EACnB,eAAe,EACf,mBAAmB,GACnB,MAAM,eAAe,CAAA;AACtB,OAAO,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,WAAW,CAAA;AAC7D,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AACvD,YAAY,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,KAAK,qBAAqB,EAC1B,KAAK,qBAAqB,EAC1B,KAAK,qBAAqB,EAC1B,oBAAoB,EACpB,aAAa,EACb,oBAAoB,EACpB,oBAAoB,EACpB,0BAA0B,EAC1B,qBAAqB,EACrB,cAAc,GACd,MAAM,gBAAgB,CAAA;AACvB,YAAY,EACX,mBAAmB,EACnB,+BAA+B,EAC/B,kBAAkB,GAClB,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EAAE,qBAAqB,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAA;AAC/E,OAAO,EACN,kBAAkB,EAClB,KAAK,OAAO,EACZ,oBAAoB,EACpB,UAAU,EACV,aAAa,EACb,aAAa,GACb,MAAM,UAAU,CAAA;AACjB,OAAO,EAAE,2BAA2B,EAAE,MAAM,kBAAkB,CAAA;AAC9D,OAAO,EACN,KAAK,sBAAsB,EAC3B,KAAK,wBAAwB,EAC7B,gCAAgC,EAChC,mCAAmC,EACnC,wBAAwB,EACxB,iBAAiB,EACjB,0BAA0B,EAC1B,yBAAyB,EACzB,uBAAuB,EACvB,2BAA2B,EAC3B,wBAAwB,EACxB,6BAA6B,GAC7B,MAAM,eAAe,CAAA;AACtB,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAA;AAC/E,OAAO,EACN,uBAAuB,EACvB,KAAK,qBAAqB,EAC1B,mBAAmB,EACnB,eAAe,EACf,mBAAmB,GACnB,MAAM,eAAe,CAAA;AACtB,OAAO,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,WAAW,CAAA;AAC7D,OAAO,EACN,sBAAsB,EACtB,wBAAwB,EACxB,KAAK,cAAc,GACnB,MAAM,sBAAsB,CAAA;AAC7B,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AACvD,YAAY,EAAE,cAAc,EAAE,oBAAoB,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA"}
@@ -6,5 +6,6 @@ export { buildLegacyIdentitySubjectCanary, buildLegacyIdentitySubjectMigration,
6
6
  export { signIdentityCookie, verifyIdentityCookie } from './identity-cookie.js';
7
7
  export { EXCHANGE_SECRET_FAILURE, getAuthorizationUrl, getMagicLinkUrl, getPasswordLoginUrl, } from './openauth.js';
8
8
  export { createPKCEChallenge, generatePKCE } from './pkce.js';
9
+ export { classifyRefreshCarrier, prefetchDeferredResponse, } from './refresh-carrier.js';
9
10
  export { generateState, verifyState } from './state.js';
10
11
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAIN,oBAAoB,EACpB,aAAa,EACb,oBAAoB,EACpB,oBAAoB,EACpB,0BAA0B,EAC1B,qBAAqB,EACrB,cAAc,GACd,MAAM,gBAAgB,CAAA;AAMvB,OAAO,EAAE,qBAAqB,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAA;AAC/E,OAAO,EACN,kBAAkB,EAElB,oBAAoB,EACpB,UAAU,EACV,aAAa,EACb,aAAa,GACb,MAAM,UAAU,CAAA;AACjB,OAAO,EAAE,2BAA2B,EAAE,MAAM,kBAAkB,CAAA;AAC9D,OAAO,EAGN,gCAAgC,EAChC,mCAAmC,EACnC,wBAAwB,EACxB,iBAAiB,EACjB,0BAA0B,EAC1B,yBAAyB,EACzB,uBAAuB,EACvB,2BAA2B,EAC3B,wBAAwB,EACxB,6BAA6B,GAC7B,MAAM,eAAe,CAAA;AACtB,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAA;AAC/E,OAAO,EACN,uBAAuB,EAEvB,mBAAmB,EACnB,eAAe,EACf,mBAAmB,GACnB,MAAM,eAAe,CAAA;AACtB,OAAO,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,WAAW,CAAA;AAC7D,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAIN,oBAAoB,EACpB,aAAa,EACb,oBAAoB,EACpB,oBAAoB,EACpB,0BAA0B,EAC1B,qBAAqB,EACrB,cAAc,GACd,MAAM,gBAAgB,CAAA;AAMvB,OAAO,EAAE,qBAAqB,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAA;AAC/E,OAAO,EACN,kBAAkB,EAElB,oBAAoB,EACpB,UAAU,EACV,aAAa,EACb,aAAa,GACb,MAAM,UAAU,CAAA;AACjB,OAAO,EAAE,2BAA2B,EAAE,MAAM,kBAAkB,CAAA;AAC9D,OAAO,EAGN,gCAAgC,EAChC,mCAAmC,EACnC,wBAAwB,EACxB,iBAAiB,EACjB,0BAA0B,EAC1B,yBAAyB,EACzB,uBAAuB,EACvB,2BAA2B,EAC3B,wBAAwB,EACxB,6BAA6B,GAC7B,MAAM,eAAe,CAAA;AACtB,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAA;AAC/E,OAAO,EACN,uBAAuB,EAEvB,mBAAmB,EACnB,eAAe,EACf,mBAAmB,GACnB,MAAM,eAAe,CAAA;AACtB,OAAO,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,WAAW,CAAA;AAC7D,OAAO,EACN,sBAAsB,EACtB,wBAAwB,GAExB,MAAM,sBAAsB,CAAA;AAC7B,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA"}
@@ -0,0 +1,10 @@
1
+ export type RefreshCarrier = 'consumable' | 'beacon' | 'prefetch';
2
+ export declare function classifyRefreshCarrier(request: Request): RefreshCarrier;
3
+ /**
4
+ * The answer to a prefetch that would have needed a refresh. Non-2xx, so the
5
+ * browser drops it and navigates for real; 425 because the server declines a
6
+ * request it may never be asked to honour. `Vary` keeps a shared cache from
7
+ * serving it to the navigation.
8
+ */
9
+ export declare function prefetchDeferredResponse(): Response;
10
+ //# sourceMappingURL=refresh-carrier.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"refresh-carrier.d.ts","sourceRoot":"","sources":["../../src/utils/refresh-carrier.ts"],"names":[],"mappings":"AA2BA,MAAM,MAAM,cAAc,GAAG,YAAY,GAAG,QAAQ,GAAG,UAAU,CAAA;AAyBjE,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,GAAG,cAAc,CAIvE;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,IAAI,QAAQ,CASnD"}
@@ -0,0 +1,76 @@
1
+ // Which requests may spend a refresh token.
2
+ //
3
+ // A refresh rotates the token: the issuer retires the one presented and the
4
+ // response carries its successor as a Set-Cookie. If that response is never
5
+ // applied, the browser keeps presenting a token nothing will accept again, and
6
+ // the member is locked out until they sign in again (fronts, 2026-09: the
7
+ // issuer's `oauth.token_refresh.rejected` with disposition `rotated` and an
8
+ // `unused` successor is exactly this shape). So a refresh belongs only on a
9
+ // request whose response the browser is waiting for and will apply:
10
+ //
11
+ // - A beacon (`navigator.sendBeacon`, `fetch` with `mode: 'no-cors'`, an
12
+ // `<a ping>`, a Reporting API upload) is fire-and-forget. Its response is
13
+ // discarded, often after the document that sent it is gone. Refreshing on
14
+ // one spends the token for nobody. The request proceeds as a guest and the
15
+ // next consumable request refreshes instead.
16
+ //
17
+ // - A prefetch or prerender (speculation rules, `<link rel="prefetch">`) is
18
+ // speculative: it is the request class the browser cancels most (fronts:
19
+ // 6.5% of prefetches end `canceled` against 1.4% of all requests), and a
20
+ // prefetched document rendered as a guest would be shown to the member on
21
+ // navigation as if they had been signed out. The request is answered with a
22
+ // non-2xx the browser will not use, so the real navigation refreshes.
23
+ //
24
+ // Sec-Fetch-* and Sec-Purpose are browser-set and cannot be forged by page
25
+ // script. Where a client sends none of them, the request is treated as
26
+ // consumable, which is exactly the pre-0.14 behaviour.
27
+ function headerLower(request, name) {
28
+ return (request.headers.get(name) ?? '').toLowerCase();
29
+ }
30
+ /** Speculation-rules and legacy prefetch/prerender markers. */
31
+ function isPrefetch(request) {
32
+ const secPurpose = headerLower(request, 'sec-purpose');
33
+ if (secPurpose.includes('prefetch') || secPurpose.includes('prerender'))
34
+ return true;
35
+ if (headerLower(request, 'purpose') === 'prefetch')
36
+ return true;
37
+ const xPurpose = headerLower(request, 'x-purpose');
38
+ if (xPurpose === 'prefetch' || xPurpose === 'preview')
39
+ return true;
40
+ return headerLower(request, 'x-moz') === 'prefetch';
41
+ }
42
+ /** Fire-and-forget requests whose response nobody reads. */
43
+ function isBeacon(request) {
44
+ const dest = headerLower(request, 'sec-fetch-dest');
45
+ if (dest === 'report')
46
+ return true;
47
+ if (dest === 'empty' && headerLower(request, 'sec-fetch-mode') === 'no-cors')
48
+ return true;
49
+ if (headerLower(request, 'content-type').startsWith('text/ping'))
50
+ return true;
51
+ return request.headers.has('ping-to');
52
+ }
53
+ export function classifyRefreshCarrier(request) {
54
+ if (isPrefetch(request))
55
+ return 'prefetch';
56
+ if (isBeacon(request))
57
+ return 'beacon';
58
+ return 'consumable';
59
+ }
60
+ /**
61
+ * The answer to a prefetch that would have needed a refresh. Non-2xx, so the
62
+ * browser drops it and navigates for real; 425 because the server declines a
63
+ * request it may never be asked to honour. `Vary` keeps a shared cache from
64
+ * serving it to the navigation.
65
+ */
66
+ export function prefetchDeferredResponse() {
67
+ return new Response(null, {
68
+ status: 425,
69
+ statusText: 'Too Early',
70
+ headers: {
71
+ 'Cache-Control': 'private, no-store',
72
+ Vary: 'Sec-Purpose, Cookie',
73
+ },
74
+ });
75
+ }
76
+ //# sourceMappingURL=refresh-carrier.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"refresh-carrier.js","sourceRoot":"","sources":["../../src/utils/refresh-carrier.ts"],"names":[],"mappings":"AAAA,4CAA4C;AAC5C,EAAE;AACF,4EAA4E;AAC5E,4EAA4E;AAC5E,+EAA+E;AAC/E,0EAA0E;AAC1E,4EAA4E;AAC5E,4EAA4E;AAC5E,oEAAoE;AACpE,EAAE;AACF,yEAAyE;AACzE,4EAA4E;AAC5E,4EAA4E;AAC5E,6EAA6E;AAC7E,+CAA+C;AAC/C,EAAE;AACF,4EAA4E;AAC5E,2EAA2E;AAC3E,2EAA2E;AAC3E,4EAA4E;AAC5E,8EAA8E;AAC9E,wEAAwE;AACxE,EAAE;AACF,2EAA2E;AAC3E,uEAAuE;AACvE,uDAAuD;AAIvD,SAAS,WAAW,CAAC,OAAgB,EAAE,IAAY;IAClD,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAA;AACvD,CAAC;AAED,+DAA+D;AAC/D,SAAS,UAAU,CAAC,OAAgB;IACnC,MAAM,UAAU,GAAG,WAAW,CAAC,OAAO,EAAE,aAAa,CAAC,CAAA;IACtD,IAAI,UAAU,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,UAAU,CAAC,QAAQ,CAAC,WAAW,CAAC;QAAE,OAAO,IAAI,CAAA;IACpF,IAAI,WAAW,CAAC,OAAO,EAAE,SAAS,CAAC,KAAK,UAAU;QAAE,OAAO,IAAI,CAAA;IAC/D,MAAM,QAAQ,GAAG,WAAW,CAAC,OAAO,EAAE,WAAW,CAAC,CAAA;IAClD,IAAI,QAAQ,KAAK,UAAU,IAAI,QAAQ,KAAK,SAAS;QAAE,OAAO,IAAI,CAAA;IAClE,OAAO,WAAW,CAAC,OAAO,EAAE,OAAO,CAAC,KAAK,UAAU,CAAA;AACpD,CAAC;AAED,4DAA4D;AAC5D,SAAS,QAAQ,CAAC,OAAgB;IACjC,MAAM,IAAI,GAAG,WAAW,CAAC,OAAO,EAAE,gBAAgB,CAAC,CAAA;IACnD,IAAI,IAAI,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAA;IAClC,IAAI,IAAI,KAAK,OAAO,IAAI,WAAW,CAAC,OAAO,EAAE,gBAAgB,CAAC,KAAK,SAAS;QAAE,OAAO,IAAI,CAAA;IACzF,IAAI,WAAW,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC,UAAU,CAAC,WAAW,CAAC;QAAE,OAAO,IAAI,CAAA;IAC7E,OAAO,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAA;AACtC,CAAC;AAED,MAAM,UAAU,sBAAsB,CAAC,OAAgB;IACtD,IAAI,UAAU,CAAC,OAAO,CAAC;QAAE,OAAO,UAAU,CAAA;IAC1C,IAAI,QAAQ,CAAC,OAAO,CAAC;QAAE,OAAO,QAAQ,CAAA;IACtC,OAAO,YAAY,CAAA;AACpB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,wBAAwB;IACvC,OAAO,IAAI,QAAQ,CAAC,IAAI,EAAE;QACzB,MAAM,EAAE,GAAG;QACX,UAAU,EAAE,WAAW;QACvB,OAAO,EAAE;YACR,eAAe,EAAE,mBAAmB;YACpC,IAAI,EAAE,qBAAqB;SAC3B;KACD,CAAC,CAAA;AACH,CAAC"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Run `exchange` unless an identical exchange is already in flight in this
3
+ * isolate, in which case share its promise. `parts` identify the exchange:
4
+ * issuer, client, resource and the refresh token.
5
+ */
6
+ export declare function singleFlightRefresh<T>(parts: readonly string[], exchange: () => Promise<T>): Promise<T>;
7
+ /** @internal Test-only. Reports how many refreshes are in flight right now. */
8
+ export declare function _refreshFlightsInProgress(): number;
9
+ //# sourceMappingURL=refresh-flight.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"refresh-flight.d.ts","sourceRoot":"","sources":["../../src/utils/refresh-flight.ts"],"names":[],"mappings":"AAkCA;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,CAAC,EACpC,KAAK,EAAE,SAAS,MAAM,EAAE,EACxB,QAAQ,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GACxB,OAAO,CAAC,CAAC,CAAC,CAWZ;AAED,+EAA+E;AAC/E,wBAAgB,yBAAyB,IAAI,MAAM,CAElD"}
@@ -0,0 +1,55 @@
1
+ // One in-flight refresh per refresh token, per isolate.
2
+ //
3
+ // A page whose access token has just expired does not send one request: the
4
+ // document, its same-origin fetches, images and prefetches all arrive within
5
+ // the same second, every one carrying the same refresh token, and every one
6
+ // used to POST /token on its own. The issuer converges them — inside its
7
+ // reuse window (identity-platform: 600s) a re-presented token gets the same
8
+ // successor back — so the race was never a rejection, but it was N exchanges,
9
+ // N JWKS verifications and N marker writes for one rotation. Callers that
10
+ // arrive while an exchange for the same token is in flight now await that
11
+ // exchange and receive its outcome, success or failure alike.
12
+ //
13
+ // Scope is deliberately the isolate. Two tabs are two documents but one
14
+ // cookie jar, so their concurrent refreshes carry the same token and land, in
15
+ // practice, on the same colo and usually the same isolate; where they do not,
16
+ // the issuer's reuse window already converges them, and a cross-isolate lock
17
+ // (KV, Durable Object) would put a network round trip and a new dependency on
18
+ // every refresh to solve a case that is already solved. Nothing is retained
19
+ // after the flight settles for the same reason: a client-side successor cache
20
+ // would be a weaker copy of the issuer's reuse window.
21
+ //
22
+ // The map lives only in this isolate's memory and is keyed on the token
23
+ // together with its client context. Keying synchronously is what makes the
24
+ // guarantee hold: a caller that arrives while a flight is registered shares it,
25
+ // with no asynchronous gap between "look" and "register" for a second caller
26
+ // to slip through. The token is already held as a string by the request that
27
+ // carried it, so the key adds nothing a heap dump did not have.
28
+ const inFlight = new Map();
29
+ function flightKey(parts) {
30
+ return parts.join('\u001f');
31
+ }
32
+ /**
33
+ * Run `exchange` unless an identical exchange is already in flight in this
34
+ * isolate, in which case share its promise. `parts` identify the exchange:
35
+ * issuer, client, resource and the refresh token.
36
+ */
37
+ export function singleFlightRefresh(parts, exchange) {
38
+ const key = flightKey(parts);
39
+ const existing = inFlight.get(key);
40
+ if (existing)
41
+ return existing;
42
+ const flight = Promise.resolve()
43
+ .then(exchange)
44
+ .finally(() => {
45
+ if (inFlight.get(key) === flight)
46
+ inFlight.delete(key);
47
+ });
48
+ inFlight.set(key, flight);
49
+ return flight;
50
+ }
51
+ /** @internal Test-only. Reports how many refreshes are in flight right now. */
52
+ export function _refreshFlightsInProgress() {
53
+ return inFlight.size;
54
+ }
55
+ //# sourceMappingURL=refresh-flight.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"refresh-flight.js","sourceRoot":"","sources":["../../src/utils/refresh-flight.ts"],"names":[],"mappings":"AAAA,wDAAwD;AACxD,EAAE;AACF,4EAA4E;AAC5E,6EAA6E;AAC7E,4EAA4E;AAC5E,yEAAyE;AACzE,4EAA4E;AAC5E,8EAA8E;AAC9E,0EAA0E;AAC1E,0EAA0E;AAC1E,8DAA8D;AAC9D,EAAE;AACF,wEAAwE;AACxE,8EAA8E;AAC9E,8EAA8E;AAC9E,6EAA6E;AAC7E,8EAA8E;AAC9E,4EAA4E;AAC5E,8EAA8E;AAC9E,uDAAuD;AACvD,EAAE;AACF,wEAAwE;AACxE,2EAA2E;AAC3E,gFAAgF;AAChF,6EAA6E;AAC7E,6EAA6E;AAC7E,gEAAgE;AAEhE,MAAM,QAAQ,GAAG,IAAI,GAAG,EAA4B,CAAA;AAEpD,SAAS,SAAS,CAAC,KAAwB;IAC1C,OAAO,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;AAC5B,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CAClC,KAAwB,EACxB,QAA0B;IAE1B,MAAM,GAAG,GAAG,SAAS,CAAC,KAAK,CAAC,CAAA;IAC5B,MAAM,QAAQ,GAAG,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;IAClC,IAAI,QAAQ;QAAE,OAAO,QAAsB,CAAA;IAC3C,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,EAAE;SAC9B,IAAI,CAAC,QAAQ,CAAC;SACd,OAAO,CAAC,GAAG,EAAE;QACb,IAAI,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,MAAM;YAAE,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;IACvD,CAAC,CAAC,CAAA;IACH,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,CAAA;IACzB,OAAO,MAAM,CAAA;AACd,CAAC;AAED,+EAA+E;AAC/E,MAAM,UAAU,yBAAyB;IACxC,OAAO,QAAQ,CAAC,IAAI,CAAA;AACrB,CAAC"}
@@ -28,7 +28,32 @@ export interface VerifyAccessTokenOptions {
28
28
  }
29
29
  export declare function verifyAccessToken(issuer: string, token: string, options?: VerifyAccessTokenOptions): Promise<VerifiedClaims | null>;
30
30
  export declare function verifyIssuedAccessTokenClaims(issuer: string, token: string, audience: string | undefined): Promise<TokenPayloadClaims | null>;
31
- export declare function refreshTokens(issuer: string, refreshToken: string, clientId?: string, clientSecret?: string, resource?: string): Promise<TokenRefreshResult | null>;
31
+ /**
32
+ * Why a refresh produced no tokens. `invalid_grant` is the issuer saying it does
33
+ * not recognise this refresh token — the strongest signal there is, but NOT
34
+ * proof: the issuer's token store is eventually consistent, so a token only
35
+ * seconds old can read as missing from another location. Every other reason may
36
+ * simply pass on a later request. A caller must never discard the session on
37
+ * any of them; signing in again overwrites the cookies without help.
38
+ */
39
+ export type RefreshFailureReason = 'invalid_grant' | 'issuer_rejected' | 'issuer_error' | 'malformed_response' | 'claims_rejected' | 'network' | 'not_configured'
40
+ /** The refresher returned nothing and did not say why. */
41
+ | 'unreported';
42
+ export interface RefreshFailure {
43
+ reason: RefreshFailureReason;
44
+ /** HTTP status of the issuer's answer, when there was one. */
45
+ status?: number;
46
+ /** The OAuth `error` code the issuer returned, when it was readable. */
47
+ error?: string;
48
+ }
49
+ /**
50
+ * Exchange a refresh token for new tokens. Concurrent calls for the same token
51
+ * (same issuer, client and resource) within one isolate share a single
52
+ * exchange and receive its outcome — see `refresh-flight.ts` for why the scope
53
+ * is the isolate and nothing is cached once the flight settles. `onFailure` is
54
+ * this caller's own reporter and fires for followers as well as the leader.
55
+ */
56
+ export declare function refreshTokens(issuer: string, refreshToken: string, clientId?: string, clientSecret?: string, resource?: string, onFailure?: (failure: RefreshFailure) => void): Promise<TokenRefreshResult | null>;
32
57
  export declare function decodeJwtPayload(jwt: string): Record<string, unknown>;
33
58
  /** @internal Test-only helper. Not exported from @growth-labs/auth/utils. */
34
59
  export declare function _resetJwksCache(): void;
@@ -1 +1 @@
1
- {"version":3,"file":"tokens.d.ts","sourceRoot":"","sources":["../../src/utils/tokens.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AA+DrD,MAAM,WAAW,cAAc;IAC9B,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;IAChB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAA;CACpB;AAED,MAAM,WAAW,kBAAmB,SAAQ,cAAc;IACzD,SAAS,EAAE,OAAO,CAAA;CAClB;AAED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,kBAAkB,CAWvF;AAYD,MAAM,WAAW,wBAAwB;IACxC,iFAAiF;IACjF,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAA;IAC5B,iGAAiG;IACjG,UAAU,CAAC,EAAE,MAAM,EAAE,CAAA;IACrB,oFAAoF;IACpF,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,sEAAsE;IACtE,gBAAgB,CAAC,EAAE,OAAO,CAAA;IAC1B,4EAA4E;IAC5E,SAAS,CAAC,EAAE,MAAM,CAAA;CAClB;AAmDD,wBAAsB,iBAAiB,CACtC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,wBAA6B,GACpC,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAuBhC;AAED,wBAAsB,6BAA6B,CAClD,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,GAAG,SAAS,GAC1B,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAKpC;AA6BD,wBAAsB,aAAa,CAClC,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,MAAM,EACpB,QAAQ,CAAC,EAAE,MAAM,EACjB,YAAY,CAAC,EAAE,MAAM,EACrB,QAAQ,CAAC,EAAE,MAAM,GACf,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAmCpC;AAED,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAErE;AAED,6EAA6E;AAC7E,wBAAgB,eAAe,IAAI,IAAI,CAEtC"}
1
+ {"version":3,"file":"tokens.d.ts","sourceRoot":"","sources":["../../src/utils/tokens.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAgErD,MAAM,WAAW,cAAc;IAC9B,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;IAChB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAA;CACpB;AAED,MAAM,WAAW,kBAAmB,SAAQ,cAAc;IACzD,SAAS,EAAE,OAAO,CAAA;CAClB;AAED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,kBAAkB,CAWvF;AAYD,MAAM,WAAW,wBAAwB;IACxC,iFAAiF;IACjF,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAA;IAC5B,iGAAiG;IACjG,UAAU,CAAC,EAAE,MAAM,EAAE,CAAA;IACrB,oFAAoF;IACpF,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,sEAAsE;IACtE,gBAAgB,CAAC,EAAE,OAAO,CAAA;IAC1B,4EAA4E;IAC5E,SAAS,CAAC,EAAE,MAAM,CAAA;CAClB;AAmDD,wBAAsB,iBAAiB,CACtC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,wBAA6B,GACpC,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAuBhC;AAED,wBAAsB,6BAA6B,CAClD,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,GAAG,SAAS,GAC1B,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAKpC;AA6BD;;;;;;;GAOG;AACH,MAAM,MAAM,oBAAoB,GAC7B,eAAe,GACf,iBAAiB,GACjB,cAAc,GACd,oBAAoB,GACpB,iBAAiB,GACjB,SAAS,GACT,gBAAgB;AAClB,0DAA0D;GACxD,YAAY,CAAA;AAEf,MAAM,WAAW,cAAc;IAC9B,MAAM,EAAE,oBAAoB,CAAA;IAC5B,8DAA8D;IAC9D,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,wEAAwE;IACxE,KAAK,CAAC,EAAE,MAAM,CAAA;CACd;AA8ED;;;;;;GAMG;AACH,wBAAsB,aAAa,CAClC,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,MAAM,EACpB,QAAQ,CAAC,EAAE,MAAM,EACjB,YAAY,CAAC,EAAE,MAAM,EACrB,QAAQ,CAAC,EAAE,MAAM,EACjB,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,cAAc,KAAK,IAAI,GAC3C,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAkBpC;AAED,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAErE;AAED,6EAA6E;AAC7E,wBAAgB,eAAe,IAAI,IAAI,CAEtC"}
@@ -2,6 +2,7 @@ import { createLocalJWKSet, jwtVerify } from 'jose';
2
2
  import { JWTExpired } from 'jose/errors';
3
3
  import { createClientSecretBasicAuthHeader } from './client-secret-header.js';
4
4
  import { extractIdentityId } from './identity.js';
5
+ import { singleFlightRefresh } from './refresh-flight.js';
5
6
  const JWKS_TTL = 600_000; // 10 minutes in ms
6
7
  const jwksCache = new Map();
7
8
  async function getJWKS(issuer, requiredKid) {
@@ -159,9 +160,17 @@ function tokenResultFromClaims(accessToken, refreshToken, claims) {
159
160
  },
160
161
  };
161
162
  }
162
- export async function refreshTokens(issuer, refreshToken, clientId, clientSecret, resource) {
163
- if (!resource)
164
- return null;
163
+ async function oauthErrorCode(response) {
164
+ try {
165
+ const body = await response.json();
166
+ const code = body?.error;
167
+ return typeof code === 'string' ? code : undefined;
168
+ }
169
+ catch {
170
+ return undefined;
171
+ }
172
+ }
173
+ async function exchangeRefreshToken(issuer, refreshToken, clientId, clientSecret, resource) {
165
174
  try {
166
175
  const params = new URLSearchParams({
167
176
  grant_type: 'refresh_token',
@@ -181,19 +190,60 @@ export async function refreshTokens(issuer, refreshToken, clientId, clientSecret
181
190
  headers,
182
191
  body: params.toString(),
183
192
  });
184
- if (!response.ok)
185
- return null;
186
- const data = await response.json();
187
- if (!tokenResponseHasTokens(data))
188
- return null;
193
+ if (!response.ok) {
194
+ const error = await oauthErrorCode(response);
195
+ const reason = error === 'invalid_grant'
196
+ ? 'invalid_grant'
197
+ : response.status >= 500
198
+ ? 'issuer_error'
199
+ : 'issuer_rejected';
200
+ return {
201
+ result: null,
202
+ failure: { reason, status: response.status, ...(error ? { error } : {}) },
203
+ };
204
+ }
205
+ const data = await response.json().catch(() => null);
206
+ if (!tokenResponseHasTokens(data)) {
207
+ return { result: null, failure: { reason: 'malformed_response', status: response.status } };
208
+ }
189
209
  const claims = await verifyIssuedAccessTokenClaims(issuer, data.access_token, resource);
190
- if (!claims)
191
- return null;
192
- return tokenResultFromClaims(data.access_token, data.refresh_token, claims);
210
+ if (!claims) {
211
+ return { result: null, failure: { reason: 'claims_rejected', status: response.status } };
212
+ }
213
+ return {
214
+ result: tokenResultFromClaims(data.access_token, data.refresh_token, claims),
215
+ failure: null,
216
+ };
193
217
  }
194
218
  catch {
219
+ return { result: null, failure: { reason: 'network' } };
220
+ }
221
+ }
222
+ /**
223
+ * Exchange a refresh token for new tokens. Concurrent calls for the same token
224
+ * (same issuer, client and resource) within one isolate share a single
225
+ * exchange and receive its outcome — see `refresh-flight.ts` for why the scope
226
+ * is the isolate and nothing is cached once the flight settles. `onFailure` is
227
+ * this caller's own reporter and fires for followers as well as the leader.
228
+ */
229
+ export async function refreshTokens(issuer, refreshToken, clientId, clientSecret, resource, onFailure) {
230
+ // Reporting is advisory: a callback that throws must not change the outcome.
231
+ const report = (failure) => {
232
+ try {
233
+ onFailure?.(failure);
234
+ }
235
+ catch {
236
+ /* the caller's reporter is not this function's failure */
237
+ }
238
+ };
239
+ if (!resource) {
240
+ report({ reason: 'not_configured' });
195
241
  return null;
196
242
  }
243
+ const exchange = await singleFlightRefresh([issuer, clientId ?? '', resource, refreshToken], () => exchangeRefreshToken(issuer, refreshToken, clientId, clientSecret, resource));
244
+ if (exchange.failure)
245
+ report(exchange.failure);
246
+ return exchange.result;
197
247
  }
198
248
  export function decodeJwtPayload(jwt) {
199
249
  return decodeJwtSegment(jwt, 1) ?? {};