@imtbl/auth 2.10.7-alpha.5 → 2.10.7-alpha.6

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.
@@ -1,643 +0,0 @@
1
- import {
2
- ErrorResponse,
3
- ErrorTimeout,
4
- InMemoryWebStorage,
5
- User as OidcUser,
6
- UserManager,
7
- UserManagerSettings,
8
- WebStorageStateStore,
9
- } from 'oidc-client-ts';
10
- import axios from 'axios';
11
- import jwt_decode from 'jwt-decode';
12
- import { getDetail, Detail } from '@imtbl/metrics';
13
- import localForage from 'localforage';
14
- import DeviceCredentialsManager from './storage/device_credentials_manager';
15
- import logger from './utils/logger';
16
- import { isAccessTokenExpiredOrExpiring } from './utils/token';
17
- import { PassportError, PassportErrorType, withPassportError } from './errors';
18
- import {
19
- DirectLoginOptions,
20
- PassportMetadata,
21
- User,
22
- DeviceTokenResponse,
23
- IdTokenPayload,
24
- OidcConfiguration,
25
- UserZkEvm,
26
- isUserZkEvm,
27
- } from './types';
28
- import { IAuthConfiguration } from './config';
29
- import LoginPopupOverlay from './overlay/loginPopupOverlay';
30
- import EmbeddedLoginPrompt from './login/embeddedLoginPrompt';
31
- import { LocalForageAsyncStorage } from './storage/LocalForageAsyncStorage';
32
-
33
- const LOGIN_POPUP_CLOSED_POLLING_DURATION = 500;
34
-
35
- const formUrlEncodedHeader = {
36
- headers: {
37
- 'Content-Type': 'application/x-www-form-urlencoded',
38
- },
39
- };
40
-
41
- const logoutEndpoint = '/v2/logout';
42
- const crossSdkBridgeLogoutEndpoint = '/im-logged-out';
43
- const authorizeEndpoint = '/authorize';
44
-
45
- const getLogoutEndpointPath = (crossSdkBridgeEnabled: boolean): string => (
46
- crossSdkBridgeEnabled ? crossSdkBridgeLogoutEndpoint : logoutEndpoint
47
- );
48
-
49
- const getAuthConfiguration = (config: IAuthConfiguration): UserManagerSettings => {
50
- const { authenticationDomain, oidcConfiguration } = config;
51
-
52
- let store;
53
- if (config.crossSdkBridgeEnabled) {
54
- store = new LocalForageAsyncStorage('ImmutableSDKPassport', localForage.INDEXEDDB);
55
- } else if (typeof window !== 'undefined') {
56
- store = window.localStorage;
57
- } else {
58
- store = new InMemoryWebStorage();
59
- }
60
- const userStore = new WebStorageStateStore({ store });
61
-
62
- const endSessionEndpoint = new URL(
63
- getLogoutEndpointPath(config.crossSdkBridgeEnabled),
64
- authenticationDomain.replace(/^(?:https?:\/\/)?(.*)/, 'https://$1'),
65
- );
66
- endSessionEndpoint.searchParams.set('client_id', oidcConfiguration.clientId);
67
- if (oidcConfiguration.logoutRedirectUri) {
68
- endSessionEndpoint.searchParams.set('returnTo', oidcConfiguration.logoutRedirectUri);
69
- }
70
-
71
- const baseConfiguration: UserManagerSettings = {
72
- authority: authenticationDomain,
73
- redirect_uri: oidcConfiguration.redirectUri,
74
- popup_redirect_uri: oidcConfiguration.popupRedirectUri || oidcConfiguration.redirectUri,
75
- client_id: oidcConfiguration.clientId,
76
- metadata: {
77
- authorization_endpoint: `${authenticationDomain}/authorize`,
78
- token_endpoint: `${authenticationDomain}/oauth/token`,
79
- userinfo_endpoint: `${authenticationDomain}/userinfo`,
80
- end_session_endpoint: endSessionEndpoint.toString(),
81
- revocation_endpoint: `${authenticationDomain}/oauth/revoke`,
82
- },
83
- automaticSilentRenew: false, // Disabled until https://github.com/authts/oidc-client-ts/issues/430 has been resolved
84
- scope: oidcConfiguration.scope,
85
- userStore,
86
- revokeTokenTypes: ['refresh_token'],
87
- extraQueryParams: {
88
- ...(oidcConfiguration.audience ? { audience: oidcConfiguration.audience } : {}),
89
- },
90
- } as UserManagerSettings;
91
-
92
- return baseConfiguration;
93
- };
94
-
95
- function base64URLEncode(str: ArrayBuffer | Uint8Array) {
96
- return btoa(String.fromCharCode(...new Uint8Array(str)))
97
- .replace(/\+/g, '-')
98
- .replace(/\//g, '_')
99
- .replace(/=/g, '');
100
- }
101
-
102
- async function sha256(buffer: string) {
103
- const encoder = new TextEncoder();
104
- const data = encoder.encode(buffer);
105
- return await window.crypto.subtle.digest('SHA-256', data);
106
- }
107
-
108
- export default class AuthManager {
109
- private userManager;
110
-
111
- private deviceCredentialsManager: DeviceCredentialsManager;
112
-
113
- private readonly config: IAuthConfiguration;
114
-
115
- private readonly embeddedLoginPrompt: EmbeddedLoginPrompt;
116
-
117
- private readonly logoutMode: Exclude<OidcConfiguration['logoutMode'], undefined>;
118
-
119
- /**
120
- * Promise that is used to prevent multiple concurrent calls to the refresh token endpoint.
121
- */
122
- private refreshingPromise: Promise<User | null> | null = null;
123
-
124
- constructor(config: IAuthConfiguration, embeddedLoginPrompt: EmbeddedLoginPrompt) {
125
- this.config = config;
126
- this.userManager = new UserManager(getAuthConfiguration(config));
127
- this.deviceCredentialsManager = new DeviceCredentialsManager();
128
- this.embeddedLoginPrompt = embeddedLoginPrompt;
129
- this.logoutMode = config.oidcConfiguration.logoutMode || 'redirect';
130
- }
131
-
132
- private static mapOidcUserToDomainModel = (oidcUser: OidcUser): User => {
133
- let passport: PassportMetadata | undefined;
134
- if (oidcUser.id_token) {
135
- passport = jwt_decode<IdTokenPayload>(oidcUser.id_token)?.passport;
136
- }
137
-
138
- const user: User = {
139
- expired: oidcUser.expired,
140
- idToken: oidcUser.id_token,
141
- accessToken: oidcUser.access_token,
142
- refreshToken: oidcUser.refresh_token,
143
- profile: {
144
- sub: oidcUser.profile.sub,
145
- email: oidcUser.profile.email,
146
- nickname: oidcUser.profile.nickname,
147
- },
148
- };
149
- if (passport?.zkevm_eth_address && passport?.zkevm_user_admin_address) {
150
- user.zkEvm = {
151
- ethAddress: passport.zkevm_eth_address,
152
- userAdminAddress: passport.zkevm_user_admin_address,
153
- };
154
- }
155
- return user;
156
- };
157
-
158
- private static mapDeviceTokenResponseToOidcUser = (tokenResponse: DeviceTokenResponse): OidcUser => {
159
- const idTokenPayload: IdTokenPayload = jwt_decode(tokenResponse.id_token);
160
-
161
- return new OidcUser({
162
- id_token: tokenResponse.id_token,
163
- access_token: tokenResponse.access_token,
164
- refresh_token: tokenResponse.refresh_token,
165
- token_type: tokenResponse.token_type,
166
- profile: {
167
- sub: idTokenPayload.sub,
168
- iss: idTokenPayload.iss,
169
- aud: idTokenPayload.aud,
170
- exp: idTokenPayload.exp,
171
- iat: idTokenPayload.iat,
172
- email: idTokenPayload.email,
173
- nickname: idTokenPayload.nickname,
174
- passport: idTokenPayload.passport,
175
- },
176
- });
177
- };
178
-
179
- private buildExtraQueryParams(
180
- directLoginOptions?: DirectLoginOptions,
181
- imPassportTraceId?: string,
182
- ): Record<string, string> {
183
- const params: Record<string, string> = {
184
- ...(this.userManager.settings?.extraQueryParams ?? {}),
185
- rid: getDetail(Detail.RUNTIME_ID) || '',
186
- third_party_a_id: '',
187
- };
188
-
189
- if (directLoginOptions) {
190
- // If method is email, only include direct login params if email is valid
191
- if (directLoginOptions.directLoginMethod === 'email') {
192
- const emailValue = directLoginOptions.email;
193
- if (emailValue) {
194
- params.direct = directLoginOptions.directLoginMethod;
195
- params.email = emailValue;
196
- }
197
- // If email method but no valid email, disregard both direct and email params
198
- } else {
199
- // For non-email methods (social login), always include direct param
200
- params.direct = directLoginOptions.directLoginMethod;
201
- }
202
- if (directLoginOptions.marketingConsentStatus) {
203
- params.marketingConsent = directLoginOptions.marketingConsentStatus;
204
- }
205
- }
206
-
207
- if (imPassportTraceId) {
208
- params.im_passport_trace_id = imPassportTraceId;
209
- }
210
-
211
- return params;
212
- }
213
-
214
- public async getClientId(): Promise<string> {
215
- return this.config.oidcConfiguration.clientId;
216
- }
217
-
218
- public async loginWithRedirect(directLoginOptions?: DirectLoginOptions): Promise<void> {
219
- await this.userManager.clearStaleState();
220
- return withPassportError<void>(async () => {
221
- const extraQueryParams = this.buildExtraQueryParams(directLoginOptions);
222
-
223
- await this.userManager.signinRedirect({
224
- extraQueryParams,
225
- });
226
- }, PassportErrorType.AUTHENTICATION_ERROR);
227
- }
228
-
229
- /**
230
- * login
231
- * @param directLoginOptions If provided, contains login method and marketing consent options
232
- * @param directLoginOptions.directLoginMethod The login method to use (e.g., 'google', 'apple', 'email')
233
- * @param directLoginOptions.marketingConsentStatus Marketing consent status ('opted_in' or 'unsubscribed')
234
- * @param directLoginOptions.email Required when directLoginMethod is 'email'
235
- */
236
- public async login(directLoginOptions?: DirectLoginOptions): Promise<User> {
237
- return withPassportError<User>(async () => {
238
- // If directLoginOptions are provided, then the consumer has rendered their own initial login screen.
239
- // If not, display the embedded login prompt and pass the returned direct login options and imPassportTraceId to the login popup.
240
- let directLoginOptionsToUse: DirectLoginOptions | undefined;
241
- let imPassportTraceId: string | undefined;
242
- if (directLoginOptions) {
243
- directLoginOptionsToUse = directLoginOptions;
244
- } else if (!this.config.popupOverlayOptions?.disableHeadlessLoginPromptOverlay) {
245
- const {
246
- imPassportTraceId: embeddedLoginPromptImPassportTraceId,
247
- ...embeddedLoginPromptDirectLoginOptions
248
- } = await this.embeddedLoginPrompt.displayEmbeddedLoginPrompt();
249
- directLoginOptionsToUse = embeddedLoginPromptDirectLoginOptions;
250
- imPassportTraceId = embeddedLoginPromptImPassportTraceId;
251
- }
252
-
253
- const popupWindowTarget = window.crypto.randomUUID();
254
- const signinPopup = async () => {
255
- const extraQueryParams = this.buildExtraQueryParams(directLoginOptionsToUse, imPassportTraceId);
256
-
257
- const userPromise = this.userManager.signinPopup({
258
- extraQueryParams,
259
- popupWindowFeatures: {
260
- width: 410,
261
- height: 450,
262
- },
263
- popupWindowTarget,
264
- });
265
-
266
- // ID-3950: https://github.com/authts/oidc-client-ts/issues/2043
267
- // The promise returned from `signinPopup` no longer rejects when the popup is closed.
268
- // We can prevent this from impacting consumers by obtaining a reference to the popup and rejecting the promise
269
- // that is returned by this method if the popup is closed by the user.
270
-
271
- // Attempt to get a reference to the popup window
272
- const popupRef = window.open('', popupWindowTarget);
273
- if (popupRef) {
274
- // Create a promise that rejects when popup is closed
275
- const popupClosedPromise = new Promise<never>((_, reject) => {
276
- const timer = setInterval(() => {
277
- if (popupRef.closed) {
278
- clearInterval(timer);
279
- reject(new Error('Popup closed by user'));
280
- }
281
- }, LOGIN_POPUP_CLOSED_POLLING_DURATION);
282
-
283
- // Clean up timer when the user promise resolves/rejects
284
- userPromise.finally(() => {
285
- clearInterval(timer);
286
- popupRef.close();
287
- });
288
- });
289
-
290
- // Race between user authentication and popup being closed
291
- return Promise.race([userPromise, popupClosedPromise]);
292
- }
293
-
294
- return userPromise;
295
- };
296
-
297
- // This promise attempts to open the signin popup, and displays the blocked popup overlay if necessary.
298
- return new Promise((resolve, reject) => {
299
- signinPopup()
300
- .then((oidcUser) => {
301
- resolve(AuthManager.mapOidcUserToDomainModel(oidcUser));
302
- })
303
- .catch((error: unknown) => {
304
- // Reject with the error if it is not caused by a blocked popup
305
- if (!(error instanceof Error) || error.message !== 'Attempted to navigate on a disposed window') {
306
- reject(error);
307
- return;
308
- }
309
-
310
- // Popup was blocked; append the blocked popup overlay to allow the user to try again.
311
- let popupHasBeenOpened: boolean = false;
312
- const overlay = new LoginPopupOverlay(this.config.popupOverlayOptions || {}, true);
313
- overlay.append(
314
- async () => {
315
- try {
316
- if (!popupHasBeenOpened) {
317
- // The user is attempting to open the popup again. It's safe to assume that this will not fail,
318
- // as there are no async operations between the button interaction & the popup being opened.
319
- popupHasBeenOpened = true;
320
- const oidcUser = await signinPopup();
321
- overlay.remove();
322
- resolve(AuthManager.mapOidcUserToDomainModel(oidcUser));
323
- } else {
324
- // The popup has already been opened. By calling `window.open` with the same target as the
325
- // previously opened popup, no new window will be opened. Instead, the existing popup
326
- // will be focused. This works as expected in most browsers at the time of implementation, but
327
- // the following exceptions do exist:
328
- // - Safari: Only the initial call will focus the window, subsequent calls will do nothing.
329
- // - Firefox: The window will not be focussed, nothing will happen.
330
- window.open('', popupWindowTarget);
331
- }
332
- } catch (retryError: unknown) {
333
- overlay.remove();
334
- reject(retryError);
335
- }
336
- },
337
- () => {
338
- overlay.remove();
339
- reject(new Error('Popup closed by user'));
340
- },
341
- );
342
- });
343
- });
344
- }, PassportErrorType.AUTHENTICATION_ERROR);
345
- }
346
-
347
- public async getUserOrLogin(): Promise<User> {
348
- let user: User | null = null;
349
- try {
350
- user = await this.getUser();
351
- } catch (err) {
352
- logger.warn('Failed to retrieve a cached user session', err);
353
- }
354
-
355
- return user || this.login();
356
- }
357
-
358
- private static shouldUseSigninPopupCallback(): boolean {
359
- // ID-3950: https://github.com/authts/oidc-client-ts/issues/2043
360
- // Detect when the login was initiated via a popup
361
- try {
362
- const urlParams = new URLSearchParams(window.location.search);
363
- const stateParam = urlParams.get('state');
364
- const localStorageKey = `oidc.${stateParam}`;
365
-
366
- const localStorageValue = localStorage.getItem(localStorageKey);
367
- const loginState = JSON.parse(localStorageValue || '{}');
368
-
369
- return loginState?.request_type === 'si:p';
370
- } catch (err) {
371
- return false;
372
- }
373
- }
374
-
375
- public async loginCallback(): Promise<undefined | User> {
376
- return withPassportError<undefined | User>(async () => {
377
- // ID-3950: https://github.com/authts/oidc-client-ts/issues/2043
378
- // When using `signinPopup` to initiate a login, call the `signinPopupCallback` method and
379
- // set the `keepOpen` flag to `true`, as the `login` method is now responsible for closing the popup.
380
- // See the comment in the `login` method for more details.
381
- if (AuthManager.shouldUseSigninPopupCallback()) {
382
- await this.userManager.signinPopupCallback(undefined, true);
383
- return undefined;
384
- }
385
- const oidcUser = await this.userManager.signinCallback();
386
- if (!oidcUser) {
387
- return undefined;
388
- }
389
-
390
- return AuthManager.mapOidcUserToDomainModel(oidcUser);
391
- }, PassportErrorType.AUTHENTICATION_ERROR);
392
- }
393
-
394
- public async getPKCEAuthorizationUrl(
395
- directLoginOptions?: DirectLoginOptions,
396
- imPassportTraceId?: string,
397
- ): Promise<string> {
398
- const verifier = base64URLEncode(window.crypto.getRandomValues(new Uint8Array(32)));
399
- const challenge = base64URLEncode(await sha256(verifier));
400
-
401
- // https://auth0.com/docs/secure/attack-protection/state-parameters
402
- const state = base64URLEncode(window.crypto.getRandomValues(new Uint8Array(32)));
403
-
404
- const {
405
- redirectUri, scope, audience, clientId,
406
- } = this.config.oidcConfiguration;
407
-
408
- this.deviceCredentialsManager.savePKCEData({ state, verifier });
409
-
410
- const pKCEAuthorizationUrl = new URL(authorizeEndpoint, this.config.authenticationDomain);
411
- pKCEAuthorizationUrl.searchParams.set('response_type', 'code');
412
- pKCEAuthorizationUrl.searchParams.set('code_challenge', challenge);
413
- pKCEAuthorizationUrl.searchParams.set('code_challenge_method', 'S256');
414
- pKCEAuthorizationUrl.searchParams.set('client_id', clientId);
415
- pKCEAuthorizationUrl.searchParams.set('redirect_uri', redirectUri);
416
- pKCEAuthorizationUrl.searchParams.set('state', state);
417
-
418
- if (scope) pKCEAuthorizationUrl.searchParams.set('scope', scope);
419
- if (audience) pKCEAuthorizationUrl.searchParams.set('audience', audience);
420
-
421
- if (directLoginOptions) {
422
- // If method is email, only include direct login params if email is valid
423
- if (directLoginOptions.directLoginMethod === 'email') {
424
- const emailValue = directLoginOptions.email;
425
- if (emailValue) {
426
- pKCEAuthorizationUrl.searchParams.set('direct', directLoginOptions.directLoginMethod);
427
- pKCEAuthorizationUrl.searchParams.set('email', emailValue);
428
- }
429
- } else {
430
- // For non-email methods (social login), always include direct param
431
- pKCEAuthorizationUrl.searchParams.set('direct', directLoginOptions.directLoginMethod);
432
- }
433
- if (directLoginOptions.marketingConsentStatus) {
434
- pKCEAuthorizationUrl.searchParams.set('marketingConsent', directLoginOptions.marketingConsentStatus);
435
- }
436
- }
437
-
438
- if (imPassportTraceId) {
439
- pKCEAuthorizationUrl.searchParams.set('im_passport_trace_id', imPassportTraceId);
440
- }
441
-
442
- return pKCEAuthorizationUrl.toString();
443
- }
444
-
445
- public async loginWithPKCEFlowCallback(authorizationCode: string, state: string): Promise<User> {
446
- return withPassportError<User>(async () => {
447
- const pkceData = this.deviceCredentialsManager.getPKCEData();
448
- if (!pkceData) {
449
- throw new Error('No code verifier or state for PKCE');
450
- }
451
-
452
- if (state !== pkceData.state) {
453
- throw new Error('Provided state does not match stored state');
454
- }
455
-
456
- const tokenResponse = await this.getPKCEToken(authorizationCode, pkceData.verifier);
457
- const oidcUser = AuthManager.mapDeviceTokenResponseToOidcUser(tokenResponse);
458
- const user = AuthManager.mapOidcUserToDomainModel(oidcUser);
459
- await this.userManager.storeUser(oidcUser);
460
-
461
- return user;
462
- }, PassportErrorType.AUTHENTICATION_ERROR);
463
- }
464
-
465
- private async getPKCEToken(authorizationCode: string, codeVerifier: string): Promise<DeviceTokenResponse> {
466
- const response = await axios.post<DeviceTokenResponse>(
467
- `${this.config.authenticationDomain}/oauth/token`,
468
- {
469
- client_id: this.config.oidcConfiguration.clientId,
470
- grant_type: 'authorization_code',
471
- code_verifier: codeVerifier,
472
- code: authorizationCode,
473
- redirect_uri: this.config.oidcConfiguration.redirectUri,
474
- },
475
- formUrlEncodedHeader,
476
- );
477
-
478
- return response.data;
479
- }
480
-
481
- public async storeTokens(tokenResponse: DeviceTokenResponse): Promise<User> {
482
- return withPassportError<User>(async () => {
483
- const oidcUser = AuthManager.mapDeviceTokenResponseToOidcUser(tokenResponse);
484
- const user = AuthManager.mapOidcUserToDomainModel(oidcUser);
485
- await this.userManager.storeUser(oidcUser);
486
-
487
- return user;
488
- }, PassportErrorType.AUTHENTICATION_ERROR);
489
- }
490
-
491
- public async logout(): Promise<void> {
492
- return withPassportError<void>(async () => {
493
- await this.userManager.revokeTokens(['refresh_token']);
494
-
495
- if (this.logoutMode === 'silent') {
496
- await this.userManager.signoutSilent();
497
- } else {
498
- await this.userManager.signoutRedirect();
499
- }
500
- }, PassportErrorType.LOGOUT_ERROR);
501
- }
502
-
503
- public async logoutSilentCallback(url: string): Promise<void> {
504
- return this.userManager.signoutSilentCallback(url);
505
- }
506
-
507
- public async removeUser(): Promise<void> {
508
- return this.userManager.removeUser();
509
- }
510
-
511
- public async getLogoutUrl(): Promise<string | null> {
512
- const endSessionEndpoint = this.userManager.settings?.metadata?.end_session_endpoint;
513
-
514
- if (!endSessionEndpoint) {
515
- logger.warn('Failed to get logout URL');
516
- return null;
517
- }
518
-
519
- return endSessionEndpoint;
520
- }
521
-
522
- public forceUserRefreshInBackground() {
523
- this.refreshTokenAndUpdatePromise().catch((error) => {
524
- logger.warn('Failed to refresh user token', error);
525
- });
526
- }
527
-
528
- public async forceUserRefresh(): Promise<User | null> {
529
- return this.refreshTokenAndUpdatePromise().catch((error) => {
530
- logger.warn('Failed to refresh user token', error);
531
- return null;
532
- });
533
- }
534
-
535
- /**
536
- * Refreshes the token and returns the user.
537
- * If the token is already being refreshed, returns the existing promise.
538
- */
539
- private async refreshTokenAndUpdatePromise(): Promise<User | null> {
540
- if (this.refreshingPromise) return this.refreshingPromise;
541
-
542
- // eslint-disable-next-line no-async-promise-executor
543
- this.refreshingPromise = new Promise(async (resolve, reject) => {
544
- try {
545
- const newOidcUser = await this.userManager.signinSilent();
546
- if (newOidcUser) {
547
- resolve(AuthManager.mapOidcUserToDomainModel(newOidcUser));
548
- return;
549
- }
550
- resolve(null);
551
- } catch (err) {
552
- let passportErrorType = PassportErrorType.AUTHENTICATION_ERROR;
553
- let errorMessage = 'Failed to refresh token';
554
- let removeUser = true;
555
-
556
- if (err instanceof ErrorTimeout) {
557
- passportErrorType = PassportErrorType.SILENT_LOGIN_ERROR;
558
- errorMessage = `${errorMessage}: ${err.message}`;
559
- removeUser = false;
560
- } else if (err instanceof ErrorResponse) {
561
- passportErrorType = PassportErrorType.NOT_LOGGED_IN_ERROR;
562
- errorMessage = `${errorMessage}: ${err.message || err.error_description}`;
563
- } else if (err instanceof Error) {
564
- errorMessage = `${errorMessage}: ${err.message}`;
565
- } else if (typeof err === 'string') {
566
- errorMessage = `${errorMessage}: ${err}`;
567
- }
568
-
569
- if (removeUser) {
570
- try {
571
- await this.userManager.removeUser();
572
- } catch (removeUserError) {
573
- if (removeUserError instanceof Error) {
574
- errorMessage = `${errorMessage}: Failed to remove user: ${removeUserError.message}`;
575
- }
576
- }
577
- }
578
-
579
- reject(new PassportError(errorMessage, passportErrorType));
580
- } finally {
581
- this.refreshingPromise = null; // Reset the promise after completion
582
- }
583
- });
584
-
585
- return this.refreshingPromise;
586
- }
587
-
588
- /**
589
- *
590
- * @param typeAssertion {(user: User) => boolean} - Optional. If provided, then the User will be checked against
591
- * the typeAssertion. If the user meets the requirements, then it will be typed as T and returned. If the User
592
- * does NOT meet the type assertion, then execution will continue, and we will attempt to obtain a User that does
593
- * meet the type assertion.
594
- *
595
- * This function will attempt to obtain a User in the following order:
596
- * 1. If the User is currently refreshing, wait for the refresh to complete.
597
- * 2. Attempt to obtain a User from storage that has not expired.
598
- * 3. Attempt to refresh the User if a refresh token is present.
599
- * 4. Return null if no valid User can be obtained.
600
- */
601
- public async getUser<T extends User>(
602
- typeAssertion: (user: User) => user is T = (user: User): user is T => true,
603
- ): Promise<T | null> {
604
- if (this.refreshingPromise) {
605
- const user = await this.refreshingPromise;
606
- if (user && typeAssertion(user)) {
607
- return user;
608
- }
609
-
610
- return null;
611
- }
612
-
613
- const oidcUser = await this.userManager.getUser();
614
- if (!oidcUser) return null;
615
-
616
- // if the token is not expired or expiring in 30 seconds or less, return the user
617
- if (!isAccessTokenExpiredOrExpiring(oidcUser)) {
618
- const user = AuthManager.mapOidcUserToDomainModel(oidcUser);
619
- if (user && typeAssertion(user)) {
620
- return user;
621
- }
622
- }
623
-
624
- // if the token is expired or expiring in 30 seconds or less, refresh the token
625
- if (oidcUser.refresh_token) {
626
- const user = await this.refreshTokenAndUpdatePromise();
627
- if (user && typeAssertion(user)) {
628
- return user;
629
- }
630
- }
631
-
632
- return null;
633
- }
634
-
635
- public async getUserZkEvm(): Promise<UserZkEvm> {
636
- const user = await this.getUser(isUserZkEvm);
637
- if (!user) {
638
- throw new Error('Failed to obtain a User with the required ZkEvm attributes');
639
- }
640
-
641
- return user;
642
- }
643
- }