@auth0/auth0-spa-js 2.18.2 → 2.19.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 -1
- package/dist/auth0-spa-js.development.js +408 -298
- package/dist/auth0-spa-js.development.js.map +1 -1
- package/dist/auth0-spa-js.production.esm.js +1 -1
- package/dist/auth0-spa-js.production.esm.js.map +1 -1
- package/dist/auth0-spa-js.production.js +1 -1
- package/dist/auth0-spa-js.production.js.map +1 -1
- package/dist/auth0-spa-js.worker.development.js +167 -76
- package/dist/auth0-spa-js.worker.development.js.map +1 -1
- package/dist/auth0-spa-js.worker.production.js +1 -1
- package/dist/auth0-spa-js.worker.production.js.map +1 -1
- package/dist/lib/auth0-spa-js.cjs.js +430 -321
- package/dist/lib/auth0-spa-js.cjs.js.map +1 -1
- package/dist/typings/Auth0Client.d.ts +38 -1
- package/dist/typings/api.d.ts +31 -0
- package/dist/typings/cache/cache-manager.d.ts +13 -0
- package/dist/typings/global.d.ts +7 -0
- package/dist/typings/http.d.ts +6 -0
- package/dist/typings/version.d.ts +1 -1
- package/dist/typings/worker/worker.types.d.ts +17 -5
- package/dist/typings/worker/worker.utils.d.ts +11 -5
- package/package.json +3 -3
- package/src/Auth0Client.ts +78 -2
- package/src/api.ts +112 -2
- package/src/cache/cache-manager.ts +57 -0
- package/src/global.ts +8 -0
- package/src/http.ts +28 -21
- package/src/version.ts +1 -1
- package/src/worker/__mocks__/token.worker.ts +3 -3
- package/src/worker/token.worker.ts +174 -3
- package/src/worker/worker.types.ts +23 -5
- package/src/worker/worker.utils.ts +18 -7
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import { MissingRefreshTokenError } from '../errors';
|
|
2
2
|
import { FetchResponse } from '../global';
|
|
3
3
|
import { createQueryParams, fromEntries } from '../utils';
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
WorkerMessage,
|
|
6
|
+
WorkerRefreshTokenMessage,
|
|
7
|
+
WorkerRevokeTokenMessage
|
|
8
|
+
} from './worker.types';
|
|
5
9
|
|
|
6
10
|
let refreshTokens: Record<string, string> = {};
|
|
11
|
+
let allowedBaseUrl: string | null = null;
|
|
7
12
|
|
|
8
13
|
const cacheKey = (audience: string, scope: string) => `${audience}|${scope}`;
|
|
9
14
|
|
|
@@ -21,6 +26,24 @@ const setRefreshToken = (
|
|
|
21
26
|
const deleteRefreshToken = (audience: string, scope: string) =>
|
|
22
27
|
delete refreshTokens[cacheKey(audience, scope)];
|
|
23
28
|
|
|
29
|
+
const getRefreshTokensByAudience = (audience: string): string[] => {
|
|
30
|
+
const seen = new Set<string>();
|
|
31
|
+
Object.entries(refreshTokens).forEach(([key, token]) => {
|
|
32
|
+
if (cacheKeyContainsAudience(audience, key)) {
|
|
33
|
+
seen.add(token);
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
return Array.from(seen);
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const deleteRefreshTokensByValue = (refreshToken: string): void => {
|
|
40
|
+
Object.entries(refreshTokens).forEach(([key, token]) => {
|
|
41
|
+
if (token === refreshToken) {
|
|
42
|
+
delete refreshTokens[key];
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
};
|
|
46
|
+
|
|
24
47
|
const wait = (time: number) =>
|
|
25
48
|
new Promise<void>(resolve => setTimeout(resolve, time));
|
|
26
49
|
|
|
@@ -180,11 +203,159 @@ const messageHandler = async ({
|
|
|
180
203
|
}
|
|
181
204
|
};
|
|
182
205
|
|
|
206
|
+
const revokeMessageHandler = async ({
|
|
207
|
+
data: { timeout, auth, fetchUrl, fetchOptions, useFormData },
|
|
208
|
+
ports: [port]
|
|
209
|
+
}: MessageEvent<WorkerRevokeTokenMessage>) => {
|
|
210
|
+
const { audience } = auth || {};
|
|
211
|
+
|
|
212
|
+
try {
|
|
213
|
+
const tokensToRevoke = getRefreshTokensByAudience(audience);
|
|
214
|
+
|
|
215
|
+
if (tokensToRevoke.length === 0) {
|
|
216
|
+
port.postMessage({ ok: true });
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Parse the base body once; rebuild per RT so each request is independent.
|
|
221
|
+
const baseBody = useFormData
|
|
222
|
+
? formDataToObject(fetchOptions.body as string)
|
|
223
|
+
: JSON.parse(fetchOptions.body as string);
|
|
224
|
+
|
|
225
|
+
for (const refreshToken of tokensToRevoke) {
|
|
226
|
+
const body = useFormData
|
|
227
|
+
? createQueryParams({ ...baseBody, token: refreshToken })
|
|
228
|
+
: JSON.stringify({ ...baseBody, token: refreshToken });
|
|
229
|
+
|
|
230
|
+
let abortController: AbortController | undefined;
|
|
231
|
+
let signal: AbortSignal | undefined;
|
|
232
|
+
|
|
233
|
+
if (typeof AbortController === 'function') {
|
|
234
|
+
abortController = new AbortController();
|
|
235
|
+
signal = abortController.signal;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
let timeoutId: ReturnType<typeof setTimeout>;
|
|
239
|
+
let response: void | Response;
|
|
240
|
+
|
|
241
|
+
try {
|
|
242
|
+
response = await Promise.race([
|
|
243
|
+
new Promise<void>(resolve => { timeoutId = setTimeout(resolve, timeout); }),
|
|
244
|
+
fetch(fetchUrl, { ...fetchOptions, body, signal })
|
|
245
|
+
]).finally(() => clearTimeout(timeoutId));
|
|
246
|
+
} catch (error) {
|
|
247
|
+
port.postMessage({ error: error.message });
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (!response) {
|
|
252
|
+
if (abortController) abortController.abort();
|
|
253
|
+
port.postMessage({ error: "Timeout when executing 'fetch'" });
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (!response.ok) {
|
|
258
|
+
let errorDescription: string | undefined;
|
|
259
|
+
try {
|
|
260
|
+
const { error_description } = JSON.parse(await response.text());
|
|
261
|
+
errorDescription = error_description;
|
|
262
|
+
} catch {
|
|
263
|
+
// body absent or not valid JSON
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
port.postMessage({ error: errorDescription || `HTTP error ${response.status}` });
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
deleteRefreshTokensByValue(refreshToken);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
port.postMessage({ ok: true });
|
|
274
|
+
} catch (error) {
|
|
275
|
+
port.postMessage({
|
|
276
|
+
error: error.message || 'Unknown error during token revocation'
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
const isAuthorizedWorkerRequest = (
|
|
282
|
+
workerRequest: WorkerRefreshTokenMessage | WorkerRevokeTokenMessage,
|
|
283
|
+
expectedPath: string
|
|
284
|
+
) => {
|
|
285
|
+
if (!allowedBaseUrl) {
|
|
286
|
+
return false;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
try {
|
|
290
|
+
const allowedBaseOrigin = new URL(allowedBaseUrl).origin;
|
|
291
|
+
const requestedUrl = new URL(workerRequest.fetchUrl);
|
|
292
|
+
|
|
293
|
+
return (
|
|
294
|
+
requestedUrl.origin === allowedBaseOrigin &&
|
|
295
|
+
requestedUrl.pathname === expectedPath
|
|
296
|
+
);
|
|
297
|
+
} catch {
|
|
298
|
+
return false;
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
const messageRouter = (event: MessageEvent<WorkerMessage>) => {
|
|
303
|
+
const { data, ports } = event;
|
|
304
|
+
const [port] = ports;
|
|
305
|
+
|
|
306
|
+
if ('type' in data && data.type === 'init') {
|
|
307
|
+
if (allowedBaseUrl === null) {
|
|
308
|
+
try {
|
|
309
|
+
new URL(data.allowedBaseUrl);
|
|
310
|
+
allowedBaseUrl = data.allowedBaseUrl;
|
|
311
|
+
} catch {
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
if ('type' in data && data.type === 'revoke') {
|
|
320
|
+
if (!isAuthorizedWorkerRequest(data as WorkerRevokeTokenMessage, '/oauth/revoke')) {
|
|
321
|
+
port?.postMessage({
|
|
322
|
+
ok: false,
|
|
323
|
+
json: {
|
|
324
|
+
error: 'invalid_fetch_url',
|
|
325
|
+
error_description: 'Unauthorized fetch URL'
|
|
326
|
+
},
|
|
327
|
+
headers: {}
|
|
328
|
+
});
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
revokeMessageHandler(event as MessageEvent<WorkerRevokeTokenMessage>);
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
if (
|
|
337
|
+
!('fetchUrl' in data) ||
|
|
338
|
+
!isAuthorizedWorkerRequest(data as WorkerRefreshTokenMessage, '/oauth/token')
|
|
339
|
+
) {
|
|
340
|
+
port?.postMessage({
|
|
341
|
+
ok: false,
|
|
342
|
+
json: {
|
|
343
|
+
error: 'invalid_fetch_url',
|
|
344
|
+
error_description: 'Unauthorized fetch URL'
|
|
345
|
+
},
|
|
346
|
+
headers: {}
|
|
347
|
+
});
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
messageHandler(event as MessageEvent<WorkerRefreshTokenMessage>);
|
|
352
|
+
};
|
|
353
|
+
|
|
183
354
|
// Don't run `addEventListener` in our tests (this is replaced in rollup)
|
|
184
355
|
if (process.env.NODE_ENV === 'test') {
|
|
185
|
-
module.exports = { messageHandler };
|
|
356
|
+
module.exports = { messageHandler, revokeMessageHandler, messageRouter };
|
|
186
357
|
/* c8 ignore next 4 */
|
|
187
358
|
} else {
|
|
188
359
|
// @ts-ignore
|
|
189
|
-
addEventListener('message',
|
|
360
|
+
addEventListener('message', messageRouter);
|
|
190
361
|
}
|
|
@@ -1,16 +1,34 @@
|
|
|
1
1
|
import { FetchOptions } from '../global';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
export type WorkerInitMessage = {
|
|
4
|
+
type: 'init';
|
|
5
|
+
allowedBaseUrl: string;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
type WorkerTokenMessage = {
|
|
7
9
|
timeout: number;
|
|
8
10
|
fetchUrl: string;
|
|
9
11
|
fetchOptions: FetchOptions;
|
|
10
12
|
useFormData?: boolean;
|
|
11
|
-
useMrrt?: boolean;
|
|
12
13
|
auth: {
|
|
13
14
|
audience: string;
|
|
14
15
|
scope: string;
|
|
15
16
|
};
|
|
16
17
|
};
|
|
18
|
+
|
|
19
|
+
export type WorkerRefreshTokenMessage = WorkerTokenMessage & {
|
|
20
|
+
type: 'refresh';
|
|
21
|
+
useMrrt?: boolean;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export type WorkerRevokeTokenMessage = Omit<WorkerTokenMessage, 'auth'> & {
|
|
25
|
+
type: 'revoke';
|
|
26
|
+
auth: {
|
|
27
|
+
audience: string;
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export type WorkerMessage =
|
|
32
|
+
| WorkerInitMessage
|
|
33
|
+
| WorkerRefreshTokenMessage
|
|
34
|
+
| WorkerRevokeTokenMessage;
|
|
@@ -1,16 +1,27 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
WorkerRefreshTokenMessage,
|
|
3
|
+
WorkerRevokeTokenMessage
|
|
4
|
+
} from './worker.types';
|
|
2
5
|
|
|
3
6
|
/**
|
|
4
|
-
* Sends
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
+
* Sends a message to a Web Worker and returns a Promise that resolves with
|
|
8
|
+
* the worker's response, or rejects if the worker replies with an error.
|
|
9
|
+
*
|
|
10
|
+
* Uses a {@link MessageChannel} so each call gets its own private reply port,
|
|
11
|
+
* making concurrent calls safe without shared state.
|
|
12
|
+
*
|
|
13
|
+
* @param message - The typed message to send (`refresh` or `revoke`).
|
|
14
|
+
* @param to - The target {@link Worker} instance.
|
|
15
|
+
* @returns A Promise that resolves with the worker's response payload.
|
|
7
16
|
*/
|
|
8
|
-
export const sendMessage =
|
|
9
|
-
|
|
17
|
+
export const sendMessage = <T = any>(
|
|
18
|
+
message: WorkerRefreshTokenMessage | WorkerRevokeTokenMessage,
|
|
19
|
+
to: Worker
|
|
20
|
+
): Promise<T> =>
|
|
21
|
+
new Promise<T>(function (resolve, reject) {
|
|
10
22
|
const messageChannel = new MessageChannel();
|
|
11
23
|
|
|
12
24
|
messageChannel.port1.onmessage = function (event) {
|
|
13
|
-
// Only for fetch errors, as these get retried
|
|
14
25
|
if (event.data.error) {
|
|
15
26
|
reject(new Error(event.data.error));
|
|
16
27
|
} else {
|