@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 +1 -0
- package/dist/middleware/auth.d.ts.map +1 -1
- package/dist/middleware/auth.js +96 -3
- package/dist/middleware/auth.js.map +1 -1
- package/dist/routes/logout.js +1 -1
- package/dist/routes/logout.js.map +1 -1
- package/dist/types.d.ts +16 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/index.d.ts +2 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +1 -0
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/refresh-carrier.d.ts +10 -0
- package/dist/utils/refresh-carrier.d.ts.map +1 -0
- package/dist/utils/refresh-carrier.js +76 -0
- package/dist/utils/refresh-carrier.js.map +1 -0
- package/dist/utils/refresh-flight.d.ts +9 -0
- package/dist/utils/refresh-flight.d.ts.map +1 -0
- package/dist/utils/refresh-flight.js +55 -0
- package/dist/utils/refresh-flight.js.map +1 -0
- package/dist/utils/tokens.d.ts +26 -1
- package/dist/utils/tokens.d.ts.map +1 -1
- package/dist/utils/tokens.js +61 -11
- package/dist/utils/tokens.js.map +1 -1
- package/dist/workers.d.ts +7 -0
- package/dist/workers.d.ts.map +1 -1
- package/dist/workers.js +15 -1
- package/dist/workers.js.map +1 -1
- package/package.json +5 -5
- package/src/middleware/auth.ts +103 -4
- package/src/routes/logout.ts +1 -1
- package/src/types.ts +23 -0
- package/src/utils/index.ts +6 -1
- package/src/utils/refresh-carrier.ts +74 -0
- package/src/utils/refresh-flight.ts +59 -0
- package/src/utils/tokens.ts +106 -11
- package/src/workers.ts +25 -1
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;
|
|
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"}
|
package/dist/middleware/auth.js
CHANGED
|
@@ -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
|
-
|
|
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":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAC9C,OAAO,sBAAsB,CAAA;AAE7B,OAAO,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAA;AAC/D,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAA;AAChE,OAAO,EAAE,+BAA+B,EAAE,MAAM,yBAAyB,CAAA;AACzE,OAAO,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAA;AAC1D,OAAO,
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/middleware/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAC9C,OAAO,sBAAsB,CAAA;AAE7B,OAAO,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAA;AAC/D,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAA;AAChE,OAAO,EAAE,+BAA+B,EAAE,MAAM,yBAAyB,CAAA;AACzE,OAAO,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAA;AAC1D,OAAO,EACN,sBAAsB,EACtB,wBAAwB,GAExB,MAAM,6BAA6B,CAAA;AACpC,OAAO,EAAuB,aAAa,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAA;AAE1F,MAAM,wBAAwB,GAAG;IAChC,eAAe;IACf,mBAAmB;IACnB,8BAA8B;CACrB,CAAA;AAEV,MAAM,CAAC,KAAK,UAAU,SAAS,CAC9B,OAAwE,EACxE,IAA6B;IAE7B,MAAM,OAAO,GAAG,SAAS,EAAE,CAAA;IAC3B,MAAM,MAAM,GAAG,qBAAqB,CAAC,OAAO,CAAC,CAAA;IAC7C,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,CAAA;IAE3B,8BAA8B;IAC9B,MAAM,WAAW,GAAG,SAAS,CAAC,OAAO,EAAE,GAAG,OAAO,CAAC,YAAY,KAAK,CAAC,CAAA;IACpE,MAAM,YAAY,GAAG,SAAS,CAAC,OAAO,EAAE,GAAG,OAAO,CAAC,YAAY,KAAK,CAAC,CAAA;IAErE,6BAA6B;IAC7B,IAAI,IAAI,GAAgB,IAAI,CAAA;IAC5B,IAAI,SAAS,GAA8C,IAAI,CAAA;IAC/D,IAAI,oBAAwC,CAAA;IAE5C,uEAAuE;IACvE,0EAA0E;IAC1E,wEAAwE;IACxE,0EAA0E;IAC1E,4EAA4E;IAC5E,2EAA2E;IAC3E,0EAA0E;IAC1E,uEAAuE;IACvE,IAAI,YAAY,IAAI,OAAO,CAAC,YAAY,KAAK,SAAS,EAAE,CAAC;QACxD,MAAM,YAAY,GAAG,MAAM,mBAAmB,CAAC,OAAO,CAAC,YAAY,EAAE,OAAO,CAAC,CAAA;QAC7E,IAAI,CAAC,YAAY,CAAC,EAAE,EAAE,CAAC;YACtB,OAAO,CAAC,MAAM,CAAC,IAAI,GAAG,IAAI,CAAA;YAC1B,OAAO,+BAA+B,CAAC,OAAO,EAAE;gBAC/C,OAAO,EAAE,oBAAoB;gBAC7B,IAAI,EAAE,YAAY,CAAC,IAAI;aACvB,CAAC,CAAA;QACH,CAAC;QACD,oBAAoB,GAAG,YAAY,CAAC,MAAM,IAAI,SAAS,CAAA;IACxD,CAAC;IAED,IAAI,WAAW,EAAE,CAAC;QACjB,MAAM,QAAQ,GAAG,MAAM,iBAAiB,CAAC,MAAM,EAAE,WAAW,EAAE,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAA;QAC7F,IAAI,QAAQ,EAAE,CAAC;YACd,IAAI,GAAG;gBACN,EAAE,EAAE,QAAQ,CAAC,GAAG;gBAChB,KAAK,EAAE,QAAQ,CAAC,KAAK;gBACrB,IAAI,EAAE,QAAQ,CAAC,IAAI,IAAI,IAAI;gBAC3B,SAAS,EAAE,QAAQ,CAAC,KAAK,IAAI,IAAI;aACjC,CAAA;YACD,IAAI,QAAQ,CAAC,SAAS,KAAK,IAAI,IAAI,QAAQ,CAAC,OAAO,KAAK,YAAY,EAAE,CAAC;gBACtE,OAAO,CAAC,MAAM,CAAC,SAAS,GAAG,IAAI,CAAA;YAChC,CAAC;QACF,CAAC;IACF,CAAC;IAED,0EAA0E;IAC1E,4EAA4E;IAC5E,sEAAsE;IACtE,sEAAsE;IACtE,IAAI,cAAc,GAAG,IAA6B,CAAA;IAClD,IAAI,eAAe,GAA0B,IAAI,CAAA;IACjD,IAAI,CAAC,IAAI,IAAI,YAAY,EAAE,CAAC;QAC3B,MAAM,OAAO,GAAG,sBAAsB,CAAC,OAAO,CAAC,CAAA;QAC/C,IAAI,OAAO,KAAK,YAAY;YAAE,eAAe,GAAG,OAAO,CAAA;IACxD,CAAC;IACD,IAAI,CAAC,IAAI,IAAI,YAAY,IAAI,eAAe,KAAK,IAAI,EAAE,CAAC;QACvD,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAA;QACzD,SAAS,GAAG,MAAM,aAAa,CAC9B,MAAM,EACN,YAAY,EACZ,QAAQ,EACR,oBAAoB,EACpB,OAAO,CAAC,QAAQ,EAChB,CAAC,OAAO,EAAE,EAAE;YACX,cAAc,GAAG,OAAO,CAAA;QACzB,CAAC,CACD,CAAA;QACD,IAAI,SAAS,EAAE,CAAC;YACf,IAAI,GAAG;gBACN,EAAE,EAAE,SAAS,CAAC,MAAM,CAAC,GAAG;gBACxB,KAAK,EAAE,SAAS,CAAC,MAAM,CAAC,KAAK;gBAC7B,IAAI,EAAE,SAAS,CAAC,MAAM,CAAC,IAAI,IAAI,IAAI;gBACnC,SAAS,EAAE,SAAS,CAAC,MAAM,CAAC,KAAK,IAAI,IAAI;aACzC,CAAA;QACF,CAAC;IACF,CAAC;IAED,2EAA2E;IAC3E,4EAA4E;IAC5E,6EAA6E;IAC7E,yEAAyE;IACzE,0EAA0E;IAC1E,mBAAmB;IACnB,OAAO,CAAC,MAAM,CAAC,IAAI,GAAG,IAAI,CAAA;IAC1B,MAAM,OAAO,GAAG,mBAAmB,CAAC;QACnC,IAAI;QACJ,SAAS,EAAE,SAAS,KAAK,IAAI;QAC7B,cAAc,EAAE,WAAW,KAAK,IAAI;QACpC,eAAe,EAAE,YAAY,KAAK,IAAI;QACtC,cAAc;QACd,QAAQ,EAAE,eAAe,KAAK,IAAI;KAClC,CAAC,CAAA;IACF,OAAO,CAAC,MAAM,CAAC,WAAW,GAAG,OAAO,CAAA;IAEpC,6EAA6E;IAC7E,0EAA0E;IAC1E,0EAA0E;IAC1E,2EAA2E;IAC3E,yEAAyE;IACzE,qEAAqE;IACrE,MAAM,kBAAkB,GAAG,oBAAoB,CAAC,OAAO,CAAC,CAAA;IAExD,IAAI,eAAe,KAAK,IAAI,EAAE,CAAC;QAC9B,IAAI,CAAC;YACJ,OAAO,CAAC,IAAI,CAAC,iCAAiC,EAAE;gBAC/C,OAAO,EAAE,eAAe;gBACxB,QAAQ,EAAE,OAAO,CAAC,GAAG,CAAC,QAAQ;aAC9B,CAAC,CAAA;QACH,CAAC;QAAC,MAAM,CAAC;YACR,6DAA6D;QAC9D,CAAC;QACD,yEAAyE;QACzE,yEAAyE;QACzE,IAAI,eAAe,KAAK,UAAU;YAAE,OAAO,wBAAwB,EAAE,CAAA;IACtE,CAAC;SAAM,IAAI,CAAC,IAAI,IAAI,YAAY,EAAE,CAAC;QAClC,MAAM,OAAO,GAAmB,cAAc,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,CAAA;QAC1E,IAAI,CAAC;YACJ,OAAO,CAAC,IAAI,CAAC,+BAA+B,EAAE;gBAC7C,GAAG,OAAO;gBACV,QAAQ,EAAE,OAAO,CAAC,GAAG,CAAC,QAAQ;gBAC9B,kBAAkB;gBAClB,OAAO;aACP,CAAC,CAAA;QACH,CAAC;QAAC,MAAM,CAAC;YACR,6DAA6D;QAC9D,CAAC;IACF,CAAC;IAED,0BAA0B;IAC1B,IAAI,CAAC,IAAI,IAAI,WAAW,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC;QACpE,wEAAwE;QACxE,yEAAyE;QACzE,uEAAuE;QACvE,uEAAuE;QACvE,MAAM,YAAY,GAAG,cAAc,EAAE,MAAM,KAAK,eAAe,CAAA;QAC/D,MAAM,QAAQ,GAAG,IAAI,GAAG,CACvB,GAAG,OAAO,CAAC,SAAS,aAAa,kBAAkB,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,GACxE,YAAY,CAAC,CAAC,CAAC,wBAAwB,CAAC,CAAC,CAAC,EAC3C,EAAE,EACF,OAAO,CAAC,GAAG,CAAC,MAAM,CAClB,CAAA;QACD,OAAO,QAAQ,CAAC,QAAQ,CAAC,QAAQ,CAAC,QAAQ,EAAE,EAAE,GAAG,CAAC,CAAA;IACnD,CAAC;IAED,0BAA0B;IAC1B,MAAM,QAAQ,GAAG,MAAM,IAAI,EAAE,CAAA;IAE7B,oFAAoF;IACpF,IAAI,SAAS,IAAI,OAAO,CAAC,MAAM,CAAC,SAAS,KAAK,IAAI,EAAE,CAAC;QACpD,MAAM,gBAAgB,GAAG,IAAI,QAAQ,CAAC,QAAQ,CAAC,IAAI,EAAE;YACpD,MAAM,EAAE,QAAQ,CAAC,MAAM;YACvB,UAAU,EAAE,QAAQ,CAAC,UAAU;YAC/B,OAAO,EAAE,IAAI,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC;SACtC,CAAC,CAAA;QACF,IAAI,SAAS,EAAE,CAAC;YACf,eAAe,CAAC,gBAAgB,EAAE,OAAO,EAAE,SAAS,CAAC,WAAW,EAAE,SAAS,CAAC,YAAY,CAAC,CAAA;QAC1F,CAAC;QACD,IAAI,OAAO,CAAC,MAAM,CAAC,SAAS,KAAK,IAAI,EAAE,CAAC;YACvC,KAAK,MAAM,MAAM,IAAI,wBAAwB,EAAE,CAAC;gBAC/C,gBAAgB,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAA;YAC1D,CAAC;QACF,CAAC;QACD,OAAO,gBAAgB,CAAA;IACxB,CAAC;IAED,OAAO,QAAQ,CAAA;AAChB,CAAC;AAED,SAAS,mBAAmB,CAAC,KAO5B;IACA,IAAI,KAAK,CAAC,IAAI;QAAE,OAAO,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,UAAU,CAAA;IACjE,IAAI,CAAC,KAAK,CAAC,cAAc,IAAI,CAAC,KAAK,CAAC,eAAe;QAAE,OAAO,WAAW,CAAA;IACvE,IAAI,KAAK,CAAC,QAAQ;QAAE,OAAO,UAAU,CAAA;IACrC,6EAA6E;IAC7E,IAAI,CAAC,KAAK,CAAC,eAAe;QAAE,OAAO,SAAS,CAAA;IAC5C,OAAO,KAAK,CAAC,cAAc,EAAE,MAAM,KAAK,eAAe,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,gBAAgB,CAAA;AACvF,CAAC;AAED,uFAAuF;AACvF,SAAS,oBAAoB,CAAC,OAAgB;IAC7C,IAAI,OAAO,CAAC,MAAM,KAAK,KAAK;QAAE,OAAO,KAAK,CAAA;IAC1C,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAA;IAClD,IAAI,IAAI;QAAE,OAAO,IAAI,KAAK,UAAU,CAAA;IACpC,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAA;IAClD,IAAI,IAAI;QAAE,OAAO,IAAI,KAAK,UAAU,CAAA;IACpC,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAA;AACnE,CAAC;AAED,SAAS,WAAW,CAAC,QAAgB,EAAE,UAAoB;IAC1D,OAAO,UAAU,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE;QAClC,IAAI,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;YAC5B,MAAM,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAA,CAAC,6BAA6B;YACjE,OAAO,QAAQ,CAAC,UAAU,CAAC,MAAM,CAAC,CAAA;QACnC,CAAC;QACD,OAAO,QAAQ,KAAK,OAAO,CAAA;IAC5B,CAAC,CAAC,CAAA;AACH,CAAC"}
|
package/dist/routes/logout.js
CHANGED
|
@@ -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
|
|
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":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAC9C,OAAO,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAA;AAEzE,OAAO,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAA;AAC/D,OAAO,EACN,oBAAoB,EACpB,mBAAmB,EACnB,iBAAiB,EACjB,SAAS,GACT,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EAAE,+BAA+B,EAAE,MAAM,yBAAyB,CAAA;AACzE,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAA;AAClD,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAA;AAYzD,MAAM,CAAC,KAAK,UAAU,GAAG,CAAC,OAAsB;IAC/C,MAAM,OAAO,GAAG,SAAS,EAAE,CAAA;IAC3B,MAAM,YAAY,GAAG,SAAS,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,OAAO,CAAC,YAAY,KAAK,CAAC,CAAA;IAC7E,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAA;IACzD,MAAM,SAAS,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAA;IAE3C,mCAAmC;IACnC,yEAAyE;IACzE,sFAAsF;IACtF,kFAAkF;IAClF,4CAA4C;IAC5C,IAAI,iBAAiB,GAA2C,IAAI,CAAA;IACpE,IAAI,YAAY,EAAE,CAAC;QAClB,IAAI,OAAO,CAAC,YAAY,KAAK,SAAS,EAAE,CAAC;YACxC,SAAS,CAAC,WAAW,CAAC,YAAY,EAAE,QAAQ,CAAC,CAAC,CAAA;QAC/C,CAAC;aAAM,CAAC;YACP,MAAM,YAAY,GAAG,MAAM,mBAAmB,CAAC,OAAO,CAAC,YAAY,EAAE,OAAO,CAAC,OAAO,CAAC,CAAA;YACrF,IAAI,YAAY,CAAC,EAAE,EAAE,CAAC;gBACrB,MAAM,MAAM,GAAG,YAAY,CAAC,MAAM,IAAI,SAAS,CAAA;gBAC/C,MAAM,OAAO,GAAG,MAAM;oBACrB,CAAC,CAAC,WAAW,CAAC,YAAY,EAAE,QAAQ,EAAE,MAAM,CAAC;oBAC7C,CAAC,CAAC,WAAW,CAAC,YAAY,EAAE,QAAQ,CAAC,CAAA;gBACtC,SAAS,CAAC,OAAO,CAAC,CAAA;YACnB,CAAC;iBAAM,CAAC;gBACP,iBAAiB,GAAG,YAAY,CAAC,IAAI,CAAA;YACtC,CAAC;QACF,CAAC;IACF,CAAC;IAED,+EAA+E;IAC/E,yEAAyE;IACzE
|
|
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
|
}
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,IAAI;IACpB,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CACzB;AAED,MAAM,MAAM,iBAAiB,GAAG,IAAI,CAAA;AACpC,MAAM,MAAM,mBAAmB,GAAG,IAAI,CAAA;AAEtC,MAAM,WAAW,iBAAiB;IACjC,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,oBAAoB;IACpC,YAAY,EAAE;QACb,MAAM,CAAC,EAAE,MAAM,CAAA;QACf,QAAQ,CAAC,EAAE,MAAM,CAAA;QACjB,SAAS,CAAC,EAAE,MAAM,CAAA;KAClB,CAAA;IACD,SAAS,CAAC,EAAE,aAAa,GAAG,iBAAiB,GAAG,MAAM,CAAA;IACtD,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,iBAAiB,CAAA;CACxB;AAED,MAAM,WAAW,6BAA6B;IAC7C,iBAAiB,EAAE,MAAM,CAAA;IACzB,uBAAuB,EAAE,MAAM,CAAA;IAC/B,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,iBAAiB,CAAA;CACxB;AAED,MAAM,WAAW,4BAA4B;IAC5C,gBAAgB,EAAE,MAAM,CAAA;IACxB,uBAAuB,EAAE,MAAM,CAAA;IAC/B,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,iBAAiB,CAAA;CACxB;AAED,MAAM,WAAW,uBAAuB;IACvC,iBAAiB,EAAE,MAAM,CAAA;IACzB,iBAAiB,EAAE,MAAM,CAAA;IACzB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,iBAAiB,CAAA;CACxB;AAED,MAAM,WAAW,yBAAyB;IACzC,cAAc,EAAE,MAAM,CAAA;IACtB,aAAa,EAAE,MAAM,CAAA;IACrB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,WAAW,CAAC,EAAE,gBAAgB,CAAA;IAC9B,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,iBAAiB,CAAA;CACxB;AAED,MAAM,WAAW,+BAA+B;IAC/C,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,iBAAiB,CAAA;CACxB;AAED,MAAM,MAAM,kBAAkB,GAAG,MAAM,GAAG,QAAQ,GAAG,OAAO,CAAC,MAAM,GAAG,QAAQ,CAAC,CAAA;AAE/E,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,EAAE,oBAAoB,KAAK,kBAAkB,CAAA;AACjF,MAAM,MAAM,sBAAsB,GAAG,CAAC,OAAO,EAAE,6BAA6B,KAAK,kBAAkB,CAAA;AACnG,MAAM,MAAM,qBAAqB,GAAG,CAAC,OAAO,EAAE,4BAA4B,KAAK,kBAAkB,CAAA;AACjG,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,EAAE,uBAAuB,KAAK,kBAAkB,CAAA;AACvF,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,EAAE,yBAAyB,KAAK,kBAAkB,CAAA;AAC3F,MAAM,MAAM,wBAAwB,GAAG,CACtC,OAAO,EAAE,+BAA+B,KACpC,kBAAkB,CAAA;AAEvB;;;;;;;;;;;;;;GAcG;AACH,MAAM,WAAW,gBAAgB;IAChC,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;CACd;AAED,MAAM,WAAW,aAAa;IAC7B,KAAK,CAAC,EAAE,gBAAgB,CAAA;IACxB,cAAc,CAAC,EAAE,gBAAgB,CAAA;IACjC,aAAa,CAAC,EAAE,gBAAgB,CAAA;IAChC,QAAQ,CAAC,EAAE,gBAAgB,CAAA;IAC3B,UAAU,CAAC,EAAE,gBAAgB,CAAA;IAC7B,gBAAgB,CAAC,EAAE,gBAAgB,CAAA;CACnC;AAED;;;;GAIG;AACH,MAAM,WAAW,qBAAqB;IACrC,KAAK,CAAC,EAAE,aAAa,CAAA;IACrB,cAAc,CAAC,EAAE,sBAAsB,CAAA;IACvC,aAAa,CAAC,EAAE,qBAAqB,CAAA;IACrC,QAAQ,CAAC,EAAE,gBAAgB,CAAA;IAC3B,UAAU,CAAC,EAAE,kBAAkB,CAAA;IAC/B,gBAAgB,CAAC,EAAE,wBAAwB,CAAA;CAC3C;AAED,MAAM,WAAW,kBAAkB;IAClC,WAAW,EAAE,MAAM,CAAA;IACnB,YAAY,EAAE,MAAM,CAAA;IACpB,MAAM,EAAE;QACP,GAAG,EAAE,MAAM,CAAA;QACX,KAAK,EAAE,MAAM,CAAA;QACb,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;QACpB,KAAK,CAAC,EAAE,MAAM,CAAA;KACd,CAAA;CACD;AAED,MAAM,WAAW,mBAAmB;IACnC,WAAW,EAAE,MAAM,CAAA;IACnB,YAAY,EAAE,MAAM,CAAA;IACpB,SAAS,EAAE,OAAO,CAAA;IAClB,QAAQ,EAAE,QAAQ,GAAG,UAAU,GAAG,YAAY,GAAG,SAAS,CAAA;IAC1D,MAAM,EAAE;QACP,GAAG,EAAE,MAAM,CAAA;QACX,KAAK,EAAE,MAAM,CAAA;QACb,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;QACpB,KAAK,CAAC,EAAE,MAAM,CAAA;KACd,CAAA;CACD;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/utils/index.d.ts
CHANGED
|
@@ -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"}
|
package/dist/utils/index.js
CHANGED
|
@@ -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
|
package/dist/utils/index.js.map
CHANGED
|
@@ -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"}
|
package/dist/utils/tokens.d.ts
CHANGED
|
@@ -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
|
-
|
|
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;
|
|
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"}
|
package/dist/utils/tokens.js
CHANGED
|
@@ -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
|
-
|
|
163
|
-
|
|
164
|
-
|
|
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
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
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
|
-
|
|
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) ?? {};
|