@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,4 +1,4 @@
|
|
|
1
|
-
import { Auth0ClientOptions, RedirectLoginOptions, PopupLoginOptions, PopupConfigOptions, RedirectLoginResult, GetTokenSilentlyOptions, GetTokenWithPopupOptions, LogoutOptions, User, IdToken, GetTokenSilentlyVerboseResponse, TokenEndpointResponse, ConnectAccountRedirectResult, RedirectConnectAccountOptions, ClientConfiguration } from './global';
|
|
1
|
+
import { Auth0ClientOptions, RedirectLoginOptions, PopupLoginOptions, PopupConfigOptions, RedirectLoginResult, GetTokenSilentlyOptions, GetTokenWithPopupOptions, LogoutOptions, User, IdToken, GetTokenSilentlyVerboseResponse, TokenEndpointResponse, ConnectAccountRedirectResult, RedirectConnectAccountOptions, ClientConfiguration, RevokeRefreshTokenOptions } from './global';
|
|
2
2
|
import { CustomTokenExchangeOptions } from './TokenExchange';
|
|
3
3
|
import { Dpop } from './dpop/dpop';
|
|
4
4
|
import { Fetcher, type FetcherConfig, type CustomFetchMinimalOutput } from './fetcher';
|
|
@@ -262,6 +262,43 @@ export declare class Auth0Client {
|
|
|
262
262
|
* @param options
|
|
263
263
|
*/
|
|
264
264
|
private _buildLogoutUrl;
|
|
265
|
+
/**
|
|
266
|
+
* ```js
|
|
267
|
+
* await auth0.revokeRefreshToken();
|
|
268
|
+
* ```
|
|
269
|
+
*
|
|
270
|
+
* Revokes the refresh token using the `/oauth/revoke` endpoint.
|
|
271
|
+
* This invalidates the refresh token so it can no longer be used to obtain new access tokens.
|
|
272
|
+
*
|
|
273
|
+
* The method works with both memory and localStorage cache modes:
|
|
274
|
+
* - For memory storage with worker: The refresh token never leaves the worker thread,
|
|
275
|
+
* maintaining security isolation
|
|
276
|
+
* - For localStorage: The token is retrieved from cache and revoked
|
|
277
|
+
*
|
|
278
|
+
* If `useRefreshTokens` is disabled, this method does nothing.
|
|
279
|
+
*
|
|
280
|
+
* **Important:** This method revokes the refresh token for a single audience. If your
|
|
281
|
+
* application requests tokens for multiple audiences, each audience may have its own
|
|
282
|
+
* refresh token. To fully revoke all refresh tokens, call this method once per audience.
|
|
283
|
+
* If you want to terminate the user's session entirely, use `logout()` instead.
|
|
284
|
+
*
|
|
285
|
+
* When using Multi-Resource Refresh Tokens (MRRT), a single refresh token may cover
|
|
286
|
+
* multiple audiences. In that case, revoking it will affect all cache entries that
|
|
287
|
+
* share the same token.
|
|
288
|
+
*
|
|
289
|
+
* @param options - Optional parameters to identify which refresh token to revoke.
|
|
290
|
+
* Defaults to the audience configured in `authorizationParams`.
|
|
291
|
+
*
|
|
292
|
+
* @example
|
|
293
|
+
* // Revoke the default refresh token
|
|
294
|
+
* await auth0.revokeRefreshToken();
|
|
295
|
+
*
|
|
296
|
+
* @example
|
|
297
|
+
* // Revoke refresh tokens for each audience individually
|
|
298
|
+
* await auth0.revokeRefreshToken({ audience: 'https://api.example.com' });
|
|
299
|
+
* await auth0.revokeRefreshToken({ audience: 'https://api2.example.com' });
|
|
300
|
+
*/
|
|
301
|
+
revokeRefreshToken(options?: RevokeRefreshTokenOptions): Promise<void>;
|
|
265
302
|
/**
|
|
266
303
|
* ```js
|
|
267
304
|
* await auth0.logout(options);
|
package/dist/typings/api.d.ts
CHANGED
|
@@ -1,2 +1,33 @@
|
|
|
1
1
|
import { TokenEndpointOptions, TokenEndpointResponse } from './global';
|
|
2
|
+
/**
|
|
3
|
+
* @ignore
|
|
4
|
+
* Internal options for the revokeToken API call.
|
|
5
|
+
* Kept in api.ts (not global.ts) so it is not part of the public type surface.
|
|
6
|
+
*/
|
|
7
|
+
interface RevokeTokenOptions {
|
|
8
|
+
baseUrl: string;
|
|
9
|
+
/** Maps directly to the OAuth `client_id` parameter. */
|
|
10
|
+
client_id: string;
|
|
11
|
+
/** Tokens to revoke. Empty for the worker path — the worker holds its own store. */
|
|
12
|
+
refreshTokens: string[];
|
|
13
|
+
audience?: string;
|
|
14
|
+
timeout?: number;
|
|
15
|
+
auth0Client?: any;
|
|
16
|
+
useFormData?: boolean;
|
|
17
|
+
onRefreshTokenRevoked?: (refreshToken: string) => Promise<void> | void;
|
|
18
|
+
}
|
|
2
19
|
export declare function oauthToken({ baseUrl, timeout, audience, scope, auth0Client, useFormData, useMrrt, dpop, ...options }: TokenEndpointOptions, worker?: Worker): Promise<TokenEndpointResponse>;
|
|
20
|
+
/**
|
|
21
|
+
* Revokes refresh tokens using the /oauth/revoke endpoint.
|
|
22
|
+
*
|
|
23
|
+
* Mirrors the oauthToken pattern: the worker/non-worker dispatch lives here,
|
|
24
|
+
* keeping Auth0Client free of transport concerns.
|
|
25
|
+
*
|
|
26
|
+
* - Worker path: sends a single message; the worker holds its own RT store and
|
|
27
|
+
* loops internally. refreshTokens is empty (worker ignores it).
|
|
28
|
+
* - Non-worker path: loops over refreshTokens and issues one request per token.
|
|
29
|
+
*
|
|
30
|
+
* @throws {GenericError} If any revoke request fails
|
|
31
|
+
*/
|
|
32
|
+
export declare function revokeToken({ baseUrl, timeout, auth0Client, useFormData, refreshTokens, audience, client_id, onRefreshTokenRevoked }: RevokeTokenOptions, worker?: Worker): Promise<void>;
|
|
33
|
+
export {};
|
|
@@ -11,6 +11,7 @@ export declare class CacheManager {
|
|
|
11
11
|
private modifiedCachedEntry;
|
|
12
12
|
set(entry: CacheEntry): Promise<void>;
|
|
13
13
|
remove(client_id: string, audience?: string, scope?: string): Promise<void>;
|
|
14
|
+
stripRefreshToken(refreshToken: string): Promise<void>;
|
|
14
15
|
clear(clientId?: string): Promise<void>;
|
|
15
16
|
private wrapCacheEntry;
|
|
16
17
|
private getCacheKeys;
|
|
@@ -42,6 +43,18 @@ export declare class CacheManager {
|
|
|
42
43
|
* @param allKeys A list of existing cache keys
|
|
43
44
|
*/
|
|
44
45
|
private getEntryWithRefreshToken;
|
|
46
|
+
/**
|
|
47
|
+
* Returns all distinct refresh tokens stored for a given audience and client.
|
|
48
|
+
*
|
|
49
|
+
* Multiple cache entries may exist for the same audience when different scope
|
|
50
|
+
* combinations were obtained via separate authorization flows, each potentially
|
|
51
|
+
* carrying a different refresh token. A Set is used to deduplicate tokens that
|
|
52
|
+
* are shared across entries (e.g. MRRT).
|
|
53
|
+
*
|
|
54
|
+
* @param audience The audience to look up
|
|
55
|
+
* @param clientId The client id to scope the lookup
|
|
56
|
+
*/
|
|
57
|
+
getRefreshTokensByAudience(audience: string, clientId: string): Promise<string[]>;
|
|
45
58
|
/**
|
|
46
59
|
* Updates the refresh token in all cache entries that contain the old refresh token.
|
|
47
60
|
*
|
package/dist/typings/global.d.ts
CHANGED
|
@@ -816,4 +816,11 @@ export type FetchResponse = {
|
|
|
816
816
|
json: any;
|
|
817
817
|
};
|
|
818
818
|
export type GetTokenSilentlyVerboseResponse = Omit<TokenEndpointResponse, 'refresh_token'>;
|
|
819
|
+
/**
|
|
820
|
+
* Options for revoking a refresh token
|
|
821
|
+
*/
|
|
822
|
+
export interface RevokeRefreshTokenOptions {
|
|
823
|
+
/** Audience to identify which refresh token to revoke. Omit for default audience. */
|
|
824
|
+
audience?: string;
|
|
825
|
+
}
|
|
819
826
|
export type { Authenticator, AuthenticatorType, OobChannel, MfaFactorType, EnrollParams, EnrollOtpParams, EnrollSmsParams, EnrollVoiceParams, EnrollEmailParams, EnrollPushParams, EnrollmentResponse, OtpEnrollmentResponse, OobEnrollmentResponse, ChallengeAuthenticatorParams, ChallengeResponse, VerifyParams, MfaGrantType, EnrollmentFactor } from './mfa/types';
|
package/dist/typings/http.d.ts
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { FetchOptions } from './global';
|
|
2
2
|
import { Dpop } from './dpop/dpop';
|
|
3
3
|
export declare const createAbortController: () => AbortController;
|
|
4
|
+
/**
|
|
5
|
+
* Wraps a single `fetch` call with an AbortController-based timeout and
|
|
6
|
+
* returns the raw `Response`. Shared by the JSON token path and the revoke
|
|
7
|
+
* path to avoid duplicating abort/timeout orchestration.
|
|
8
|
+
*/
|
|
9
|
+
export declare const fetchWithTimeout: (fetchUrl: string, fetchOptions: FetchOptions, timeout: number) => Promise<Response>;
|
|
4
10
|
export declare const switchFetch: (fetchUrl: string, audience: string, scope: string, fetchOptions: FetchOptions, worker?: Worker, useFormData?: boolean, timeout?: number, useMrrt?: boolean) => Promise<any>;
|
|
5
11
|
export declare function getJSON<T>(url: string, timeout: number | undefined, audience: string, scope: string, options: FetchOptions, worker?: Worker, useFormData?: boolean, useMrrt?: boolean, dpop?: Pick<Dpop, 'generateProof' | 'getNonce' | 'setNonce'>, isDpopRetry?: boolean): Promise<T>;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
declare const _default: "2.
|
|
1
|
+
declare const _default: "2.19.0";
|
|
2
2
|
export default _default;
|
|
@@ -1,15 +1,27 @@
|
|
|
1
1
|
import { FetchOptions } from '../global';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
export type WorkerInitMessage = {
|
|
3
|
+
type: 'init';
|
|
4
|
+
allowedBaseUrl: string;
|
|
5
|
+
};
|
|
6
|
+
type WorkerTokenMessage = {
|
|
6
7
|
timeout: number;
|
|
7
8
|
fetchUrl: string;
|
|
8
9
|
fetchOptions: FetchOptions;
|
|
9
10
|
useFormData?: boolean;
|
|
10
|
-
useMrrt?: boolean;
|
|
11
11
|
auth: {
|
|
12
12
|
audience: string;
|
|
13
13
|
scope: string;
|
|
14
14
|
};
|
|
15
15
|
};
|
|
16
|
+
export type WorkerRefreshTokenMessage = WorkerTokenMessage & {
|
|
17
|
+
type: 'refresh';
|
|
18
|
+
useMrrt?: boolean;
|
|
19
|
+
};
|
|
20
|
+
export type WorkerRevokeTokenMessage = Omit<WorkerTokenMessage, 'auth'> & {
|
|
21
|
+
type: 'revoke';
|
|
22
|
+
auth: {
|
|
23
|
+
audience: string;
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
export type WorkerMessage = WorkerInitMessage | WorkerRefreshTokenMessage | WorkerRevokeTokenMessage;
|
|
27
|
+
export {};
|
|
@@ -1,7 +1,13 @@
|
|
|
1
|
-
import { WorkerRefreshTokenMessage } from './worker.types';
|
|
1
|
+
import { WorkerRefreshTokenMessage, WorkerRevokeTokenMessage } from './worker.types';
|
|
2
2
|
/**
|
|
3
|
-
* Sends
|
|
4
|
-
*
|
|
5
|
-
*
|
|
3
|
+
* Sends a message to a Web Worker and returns a Promise that resolves with
|
|
4
|
+
* the worker's response, or rejects if the worker replies with an error.
|
|
5
|
+
*
|
|
6
|
+
* Uses a {@link MessageChannel} so each call gets its own private reply port,
|
|
7
|
+
* making concurrent calls safe without shared state.
|
|
8
|
+
*
|
|
9
|
+
* @param message - The typed message to send (`refresh` or `revoke`).
|
|
10
|
+
* @param to - The target {@link Worker} instance.
|
|
11
|
+
* @returns A Promise that resolves with the worker's response payload.
|
|
6
12
|
*/
|
|
7
|
-
export declare const sendMessage: (message: WorkerRefreshTokenMessage, to: Worker) => Promise<
|
|
13
|
+
export declare const sendMessage: <T = any>(message: WorkerRefreshTokenMessage | WorkerRevokeTokenMessage, to: Worker) => Promise<T>;
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"name": "@auth0/auth0-spa-js",
|
|
4
4
|
"description": "Auth0 SDK for Single Page Applications using Authorization Code Grant Flow with PKCE",
|
|
5
5
|
"license": "MIT",
|
|
6
|
-
"version": "2.
|
|
6
|
+
"version": "2.19.0",
|
|
7
7
|
"main": "dist/lib/auth0-spa-js.cjs.js",
|
|
8
8
|
"types": "dist/typings/index.d.ts",
|
|
9
9
|
"module": "dist/auth0-spa-js.production.esm.js",
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"serve:coverage": "serve coverage/lcov-report -n",
|
|
52
52
|
"serve:stats": "serve bundle-stats -n",
|
|
53
53
|
"print-bundle-size": "node ./scripts/print-bundle-size.mjs",
|
|
54
|
-
"prepack": "npm run build && npm run build:types
|
|
54
|
+
"prepack": "npm run build && npm run build:types",
|
|
55
55
|
"publish:cdn": "ccu --trace"
|
|
56
56
|
},
|
|
57
57
|
"devDependencies": {
|
|
@@ -93,7 +93,7 @@
|
|
|
93
93
|
"rollup-plugin-livereload": "^2.0.5",
|
|
94
94
|
"rollup-plugin-sourcemaps": "^0.6.3",
|
|
95
95
|
"rollup-plugin-terser": "^7.0.2",
|
|
96
|
-
"rollup-plugin-typescript2": "^0.
|
|
96
|
+
"rollup-plugin-typescript2": "^0.37.0",
|
|
97
97
|
"rollup-plugin-visualizer": "^5.7.1",
|
|
98
98
|
"rollup-plugin-web-worker-loader": "~1.6.1",
|
|
99
99
|
"serve": "^14.0.1",
|
package/src/Auth0Client.ts
CHANGED
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
|
|
18
18
|
import { getLockManager, type ILockManager } from './lock';
|
|
19
19
|
|
|
20
|
-
import { oauthToken } from './api';
|
|
20
|
+
import { oauthToken, revokeToken } from './api';
|
|
21
21
|
|
|
22
22
|
import { injectDefaultScopes, scopesToRequest } from './scope';
|
|
23
23
|
|
|
@@ -90,7 +90,8 @@ import {
|
|
|
90
90
|
RedirectConnectAccountOptions,
|
|
91
91
|
ResponseType,
|
|
92
92
|
ClientAuthorizationParams,
|
|
93
|
-
ClientConfiguration
|
|
93
|
+
ClientConfiguration,
|
|
94
|
+
RevokeRefreshTokenOptions
|
|
94
95
|
} from './global';
|
|
95
96
|
|
|
96
97
|
// @ts-ignore
|
|
@@ -306,6 +307,11 @@ export class Auth0Client {
|
|
|
306
307
|
} else {
|
|
307
308
|
this.worker = new TokenWorker();
|
|
308
309
|
}
|
|
310
|
+
|
|
311
|
+
this.worker!.postMessage({
|
|
312
|
+
type: 'init',
|
|
313
|
+
allowedBaseUrl: this.domainUrl
|
|
314
|
+
});
|
|
309
315
|
}
|
|
310
316
|
}
|
|
311
317
|
|
|
@@ -1139,6 +1145,76 @@ export class Auth0Client {
|
|
|
1139
1145
|
return url + federatedQuery;
|
|
1140
1146
|
}
|
|
1141
1147
|
|
|
1148
|
+
/**
|
|
1149
|
+
* ```js
|
|
1150
|
+
* await auth0.revokeRefreshToken();
|
|
1151
|
+
* ```
|
|
1152
|
+
*
|
|
1153
|
+
* Revokes the refresh token using the `/oauth/revoke` endpoint.
|
|
1154
|
+
* This invalidates the refresh token so it can no longer be used to obtain new access tokens.
|
|
1155
|
+
*
|
|
1156
|
+
* The method works with both memory and localStorage cache modes:
|
|
1157
|
+
* - For memory storage with worker: The refresh token never leaves the worker thread,
|
|
1158
|
+
* maintaining security isolation
|
|
1159
|
+
* - For localStorage: The token is retrieved from cache and revoked
|
|
1160
|
+
*
|
|
1161
|
+
* If `useRefreshTokens` is disabled, this method does nothing.
|
|
1162
|
+
*
|
|
1163
|
+
* **Important:** This method revokes the refresh token for a single audience. If your
|
|
1164
|
+
* application requests tokens for multiple audiences, each audience may have its own
|
|
1165
|
+
* refresh token. To fully revoke all refresh tokens, call this method once per audience.
|
|
1166
|
+
* If you want to terminate the user's session entirely, use `logout()` instead.
|
|
1167
|
+
*
|
|
1168
|
+
* When using Multi-Resource Refresh Tokens (MRRT), a single refresh token may cover
|
|
1169
|
+
* multiple audiences. In that case, revoking it will affect all cache entries that
|
|
1170
|
+
* share the same token.
|
|
1171
|
+
*
|
|
1172
|
+
* @param options - Optional parameters to identify which refresh token to revoke.
|
|
1173
|
+
* Defaults to the audience configured in `authorizationParams`.
|
|
1174
|
+
*
|
|
1175
|
+
* @example
|
|
1176
|
+
* // Revoke the default refresh token
|
|
1177
|
+
* await auth0.revokeRefreshToken();
|
|
1178
|
+
*
|
|
1179
|
+
* @example
|
|
1180
|
+
* // Revoke refresh tokens for each audience individually
|
|
1181
|
+
* await auth0.revokeRefreshToken({ audience: 'https://api.example.com' });
|
|
1182
|
+
* await auth0.revokeRefreshToken({ audience: 'https://api2.example.com' });
|
|
1183
|
+
*/
|
|
1184
|
+
public async revokeRefreshToken(options: RevokeRefreshTokenOptions = {}): Promise<void> {
|
|
1185
|
+
if (!this.options.useRefreshTokens) {
|
|
1186
|
+
return;
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
const audience =
|
|
1190
|
+
options.audience || this.options.authorizationParams.audience;
|
|
1191
|
+
|
|
1192
|
+
const resolvedAudience = audience || DEFAULT_AUDIENCE;
|
|
1193
|
+
|
|
1194
|
+
// For the non-worker path the main-thread cache holds the refresh tokens.
|
|
1195
|
+
// For the worker path the worker holds its own RT store — the cache returns
|
|
1196
|
+
// [] and revokeToken sends a single message; the worker loops internally.
|
|
1197
|
+
const refreshTokens = await this.cacheManager.getRefreshTokensByAudience(
|
|
1198
|
+
resolvedAudience,
|
|
1199
|
+
this.options.clientId
|
|
1200
|
+
);
|
|
1201
|
+
|
|
1202
|
+
await revokeToken(
|
|
1203
|
+
{
|
|
1204
|
+
baseUrl: this.domainUrl,
|
|
1205
|
+
timeout: this.httpTimeoutMs,
|
|
1206
|
+
auth0Client: this.options.auth0Client,
|
|
1207
|
+
useFormData: this.options.useFormData,
|
|
1208
|
+
client_id: this.options.clientId,
|
|
1209
|
+
refreshTokens,
|
|
1210
|
+
audience: resolvedAudience,
|
|
1211
|
+
onRefreshTokenRevoked: refreshToken =>
|
|
1212
|
+
this.cacheManager.stripRefreshToken(refreshToken)
|
|
1213
|
+
},
|
|
1214
|
+
this.worker
|
|
1215
|
+
);
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1142
1218
|
/**
|
|
1143
1219
|
* ```js
|
|
1144
1220
|
* await auth0.logout(options);
|
package/src/api.ts
CHANGED
|
@@ -1,7 +1,31 @@
|
|
|
1
1
|
import { TokenEndpointOptions, TokenEndpointResponse } from './global';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
DEFAULT_AUTH0_CLIENT,
|
|
4
|
+
DEFAULT_AUDIENCE,
|
|
5
|
+
DEFAULT_FETCH_TIMEOUT_MS
|
|
6
|
+
} from './constants';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @ignore
|
|
10
|
+
* Internal options for the revokeToken API call.
|
|
11
|
+
* Kept in api.ts (not global.ts) so it is not part of the public type surface.
|
|
12
|
+
*/
|
|
13
|
+
interface RevokeTokenOptions {
|
|
14
|
+
baseUrl: string;
|
|
15
|
+
/** Maps directly to the OAuth `client_id` parameter. */
|
|
16
|
+
client_id: string;
|
|
17
|
+
/** Tokens to revoke. Empty for the worker path — the worker holds its own store. */
|
|
18
|
+
refreshTokens: string[];
|
|
19
|
+
audience?: string;
|
|
20
|
+
timeout?: number;
|
|
21
|
+
auth0Client?: any;
|
|
22
|
+
useFormData?: boolean;
|
|
23
|
+
onRefreshTokenRevoked?: (refreshToken: string) => Promise<void> | void;
|
|
24
|
+
}
|
|
3
25
|
import * as dpopUtils from './dpop/utils';
|
|
4
|
-
import {
|
|
26
|
+
import { GenericError } from './errors';
|
|
27
|
+
import { getJSON, fetchWithTimeout } from './http';
|
|
28
|
+
import { sendMessage } from './worker/worker.utils';
|
|
5
29
|
import { createQueryParams, stripAuth0Client } from './utils';
|
|
6
30
|
|
|
7
31
|
export async function oauthToken(
|
|
@@ -59,3 +83,89 @@ export async function oauthToken(
|
|
|
59
83
|
isDpopSupported ? dpop : undefined
|
|
60
84
|
);
|
|
61
85
|
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Revokes refresh tokens using the /oauth/revoke endpoint.
|
|
89
|
+
*
|
|
90
|
+
* Mirrors the oauthToken pattern: the worker/non-worker dispatch lives here,
|
|
91
|
+
* keeping Auth0Client free of transport concerns.
|
|
92
|
+
*
|
|
93
|
+
* - Worker path: sends a single message; the worker holds its own RT store and
|
|
94
|
+
* loops internally. refreshTokens is empty (worker ignores it).
|
|
95
|
+
* - Non-worker path: loops over refreshTokens and issues one request per token.
|
|
96
|
+
*
|
|
97
|
+
* @throws {GenericError} If any revoke request fails
|
|
98
|
+
*/
|
|
99
|
+
export async function revokeToken(
|
|
100
|
+
{
|
|
101
|
+
baseUrl,
|
|
102
|
+
timeout,
|
|
103
|
+
auth0Client,
|
|
104
|
+
useFormData,
|
|
105
|
+
refreshTokens,
|
|
106
|
+
audience,
|
|
107
|
+
client_id,
|
|
108
|
+
onRefreshTokenRevoked
|
|
109
|
+
}: RevokeTokenOptions,
|
|
110
|
+
worker?: Worker
|
|
111
|
+
): Promise<void> {
|
|
112
|
+
const resolvedTimeout = timeout || DEFAULT_FETCH_TIMEOUT_MS;
|
|
113
|
+
// token_type_hint is a SHOULD per RFC 7009 §2.1; used in both paths below.
|
|
114
|
+
const token_type_hint = 'refresh_token' as const;
|
|
115
|
+
const fetchUrl = `${baseUrl}/oauth/revoke`;
|
|
116
|
+
const headers = {
|
|
117
|
+
'Content-Type': useFormData
|
|
118
|
+
? 'application/x-www-form-urlencoded'
|
|
119
|
+
: 'application/json',
|
|
120
|
+
'Auth0-Client': btoa(
|
|
121
|
+
JSON.stringify(stripAuth0Client(auth0Client || DEFAULT_AUTH0_CLIENT))
|
|
122
|
+
)
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
if (worker) {
|
|
126
|
+
// Worker holds its own RT store and injects each token into the request.
|
|
127
|
+
// Send the base body (without token) so the worker can loop over its tokens.
|
|
128
|
+
const baseParams = { client_id, token_type_hint };
|
|
129
|
+
const body = useFormData
|
|
130
|
+
? createQueryParams(baseParams)
|
|
131
|
+
: JSON.stringify(baseParams);
|
|
132
|
+
|
|
133
|
+
return sendMessage(
|
|
134
|
+
{
|
|
135
|
+
type: 'revoke',
|
|
136
|
+
timeout: resolvedTimeout,
|
|
137
|
+
fetchUrl,
|
|
138
|
+
fetchOptions: { method: 'POST', body, headers },
|
|
139
|
+
useFormData,
|
|
140
|
+
auth: { audience: audience ?? DEFAULT_AUDIENCE }
|
|
141
|
+
},
|
|
142
|
+
worker
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
for (const refreshToken of refreshTokens) {
|
|
147
|
+
const params = { client_id, token_type_hint, token: refreshToken };
|
|
148
|
+
const body = useFormData
|
|
149
|
+
? createQueryParams(params)
|
|
150
|
+
: JSON.stringify(params);
|
|
151
|
+
|
|
152
|
+
const response = await fetchWithTimeout(
|
|
153
|
+
fetchUrl,
|
|
154
|
+
{ method: 'POST', body, headers },
|
|
155
|
+
resolvedTimeout
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
if (!response.ok) {
|
|
159
|
+
let error: string | undefined;
|
|
160
|
+
let errorDescription: string | undefined;
|
|
161
|
+
try {
|
|
162
|
+
({ error, error_description: errorDescription } = JSON.parse(await response.text()));
|
|
163
|
+
} catch {
|
|
164
|
+
// body absent or not valid JSON
|
|
165
|
+
}
|
|
166
|
+
throw new GenericError(error || 'revoke_error', errorDescription || `HTTP error ${response.status}`);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
await onRefreshTokenRevoked?.(refreshToken);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
@@ -163,6 +163,23 @@ export class CacheManager {
|
|
|
163
163
|
await this.cache.remove(cacheKey.toKey());
|
|
164
164
|
}
|
|
165
165
|
|
|
166
|
+
async stripRefreshToken(refreshToken: string): Promise<void> {
|
|
167
|
+
const keys = await this.getCacheKeys();
|
|
168
|
+
|
|
169
|
+
/* c8 ignore next */
|
|
170
|
+
if (!keys) return;
|
|
171
|
+
|
|
172
|
+
// Find all cache entries that have this refresh token and strip only the refresh token,
|
|
173
|
+
// leaving the access token intact (it remains valid until it expires)
|
|
174
|
+
for (const key of keys) {
|
|
175
|
+
const entry = await this.cache.get<WrappedCacheEntry>(key);
|
|
176
|
+
if (entry?.body?.refresh_token === refreshToken) {
|
|
177
|
+
delete entry.body.refresh_token;
|
|
178
|
+
await this.cache.set(key, entry);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
166
183
|
async clear(clientId?: string): Promise<void> {
|
|
167
184
|
const keys = await this.getCacheKeys();
|
|
168
185
|
|
|
@@ -269,6 +286,46 @@ export class CacheManager {
|
|
|
269
286
|
return undefined;
|
|
270
287
|
}
|
|
271
288
|
|
|
289
|
+
/**
|
|
290
|
+
* Returns all distinct refresh tokens stored for a given audience and client.
|
|
291
|
+
*
|
|
292
|
+
* Multiple cache entries may exist for the same audience when different scope
|
|
293
|
+
* combinations were obtained via separate authorization flows, each potentially
|
|
294
|
+
* carrying a different refresh token. A Set is used to deduplicate tokens that
|
|
295
|
+
* are shared across entries (e.g. MRRT).
|
|
296
|
+
*
|
|
297
|
+
* @param audience The audience to look up
|
|
298
|
+
* @param clientId The client id to scope the lookup
|
|
299
|
+
*/
|
|
300
|
+
async getRefreshTokensByAudience(
|
|
301
|
+
audience: string,
|
|
302
|
+
clientId: string
|
|
303
|
+
): Promise<string[]> {
|
|
304
|
+
const keys = await this.getCacheKeys();
|
|
305
|
+
|
|
306
|
+
if (!keys) return [];
|
|
307
|
+
|
|
308
|
+
const tokens = new Set<string>();
|
|
309
|
+
|
|
310
|
+
for (const key of keys) {
|
|
311
|
+
const cacheKey = CacheKey.fromKey(key);
|
|
312
|
+
|
|
313
|
+
if (
|
|
314
|
+
cacheKey.prefix === CACHE_KEY_PREFIX &&
|
|
315
|
+
cacheKey.clientId === clientId &&
|
|
316
|
+
cacheKey.audience === audience
|
|
317
|
+
) {
|
|
318
|
+
const entry = await this.cache.get<WrappedCacheEntry>(key);
|
|
319
|
+
|
|
320
|
+
if (entry?.body?.refresh_token) {
|
|
321
|
+
tokens.add(entry.body.refresh_token);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
return Array.from(tokens);
|
|
327
|
+
}
|
|
328
|
+
|
|
272
329
|
/**
|
|
273
330
|
* Updates the refresh token in all cache entries that contain the old refresh token.
|
|
274
331
|
*
|
package/src/global.ts
CHANGED
|
@@ -903,6 +903,14 @@ export type GetTokenSilentlyVerboseResponse = Omit<
|
|
|
903
903
|
'refresh_token'
|
|
904
904
|
>;
|
|
905
905
|
|
|
906
|
+
/**
|
|
907
|
+
* Options for revoking a refresh token
|
|
908
|
+
*/
|
|
909
|
+
export interface RevokeRefreshTokenOptions {
|
|
910
|
+
/** Audience to identify which refresh token to revoke. Omit for default audience. */
|
|
911
|
+
audience?: string;
|
|
912
|
+
}
|
|
913
|
+
|
|
906
914
|
// MFA API types
|
|
907
915
|
export type {
|
|
908
916
|
Authenticator,
|
package/src/http.ts
CHANGED
|
@@ -17,27 +17,16 @@ import { DPOP_NONCE_HEADER } from './dpop/utils';
|
|
|
17
17
|
|
|
18
18
|
export const createAbortController = () => new AbortController();
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* This is not needed, but do it anyway so the object shape is the
|
|
29
|
-
* same as when using a Web Worker (which *does* need this, see
|
|
30
|
-
* src/worker/token.worker.ts).
|
|
31
|
-
*/
|
|
32
|
-
headers: fromEntries(response.headers)
|
|
33
|
-
};
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
const fetchWithoutWorker = async (
|
|
20
|
+
/**
|
|
21
|
+
* Wraps a single `fetch` call with an AbortController-based timeout and
|
|
22
|
+
* returns the raw `Response`. Shared by the JSON token path and the revoke
|
|
23
|
+
* path to avoid duplicating abort/timeout orchestration.
|
|
24
|
+
*/
|
|
25
|
+
export const fetchWithTimeout = (
|
|
37
26
|
fetchUrl: string,
|
|
38
27
|
fetchOptions: FetchOptions,
|
|
39
28
|
timeout: number
|
|
40
|
-
) => {
|
|
29
|
+
): Promise<Response> => {
|
|
41
30
|
const controller = createAbortController();
|
|
42
31
|
fetchOptions.signal = controller.signal;
|
|
43
32
|
|
|
@@ -45,9 +34,8 @@ const fetchWithoutWorker = async (
|
|
|
45
34
|
|
|
46
35
|
// The promise will resolve with one of these two promises (the fetch or the timeout), whichever completes first.
|
|
47
36
|
return Promise.race([
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
new Promise((_, reject) => {
|
|
37
|
+
fetch(fetchUrl, fetchOptions),
|
|
38
|
+
new Promise<never>((_, reject) => {
|
|
51
39
|
timeoutId = setTimeout(() => {
|
|
52
40
|
controller.abort();
|
|
53
41
|
reject(new Error("Timeout when executing 'fetch'"));
|
|
@@ -58,6 +46,24 @@ const fetchWithoutWorker = async (
|
|
|
58
46
|
});
|
|
59
47
|
};
|
|
60
48
|
|
|
49
|
+
const fetchWithoutWorker = async (
|
|
50
|
+
fetchUrl: string,
|
|
51
|
+
fetchOptions: FetchOptions,
|
|
52
|
+
timeout: number
|
|
53
|
+
) => {
|
|
54
|
+
const response = await fetchWithTimeout(fetchUrl, fetchOptions, timeout);
|
|
55
|
+
return {
|
|
56
|
+
ok: response.ok,
|
|
57
|
+
json: await response.json(),
|
|
58
|
+
/**
|
|
59
|
+
* This is not needed, but do it anyway so the object shape is the
|
|
60
|
+
* same as when using a Web Worker (which *does* need this, see
|
|
61
|
+
* src/worker/token.worker.ts).
|
|
62
|
+
*/
|
|
63
|
+
headers: fromEntries(response.headers)
|
|
64
|
+
};
|
|
65
|
+
};
|
|
66
|
+
|
|
61
67
|
const fetchWithWorker = async (
|
|
62
68
|
fetchUrl: string,
|
|
63
69
|
audience: string,
|
|
@@ -70,6 +76,7 @@ const fetchWithWorker = async (
|
|
|
70
76
|
) => {
|
|
71
77
|
return sendMessage(
|
|
72
78
|
{
|
|
79
|
+
type: 'refresh',
|
|
73
80
|
auth: {
|
|
74
81
|
audience,
|
|
75
82
|
scope
|
package/src/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export default '2.
|
|
1
|
+
export default '2.19.0';
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
const {
|
|
1
|
+
const { messageRouter } = jest.requireActual('../token.worker');
|
|
2
2
|
|
|
3
3
|
export default class {
|
|
4
4
|
postMessage(data, ports) {
|
|
5
|
-
|
|
5
|
+
messageRouter({
|
|
6
6
|
data,
|
|
7
|
-
ports
|
|
7
|
+
ports: ports || []
|
|
8
8
|
});
|
|
9
9
|
}
|
|
10
10
|
}
|