@xh/hoist 73.0.0-SNAPSHOT.1741793208717 → 73.0.0-SNAPSHOT.1741808956502
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/CHANGELOG.md +6 -5
- package/admin/tabs/activity/clienterrors/ClientErrorsModel.ts +23 -4
- package/build/types/security/BaseOAuthClient.d.ts +15 -16
- package/build/types/security/Token.d.ts +0 -1
- package/build/types/security/Types.d.ts +12 -0
- package/build/types/security/authzero/AuthZeroClient.d.ts +3 -4
- package/build/types/security/msal/MsalClient.d.ts +3 -4
- package/package.json +1 -1
- package/security/BaseOAuthClient.ts +37 -31
- package/security/Token.ts +0 -2
- package/security/Types.ts +22 -0
- package/security/authzero/AuthZeroClient.ts +6 -8
- package/security/msal/MsalClient.ts +6 -8
- package/tsconfig.tsbuildinfo +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,11 +4,12 @@
|
|
|
4
4
|
|
|
5
5
|
### 🎁 New Features
|
|
6
6
|
|
|
7
|
-
*
|
|
8
|
-
use-cases.
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
explicit names.
|
|
7
|
+
* Modified `TabContainerModel` to make more methods `protected`, improving extensibility for
|
|
8
|
+
advanced use-cases.
|
|
9
|
+
* Enhanced `XH.reloadApp` with new argument to clear query parameters before loading.
|
|
10
|
+
* Enhanced exception handling in `FetchService` to capture messages returned as raw strings, or
|
|
11
|
+
without explicit names.
|
|
12
|
+
* Added dedicated columns to the Admin Console "Client Errors" tab for error names and messages.
|
|
12
13
|
|
|
13
14
|
### 🐞 Bug Fixes
|
|
14
15
|
|
|
@@ -9,7 +9,7 @@ import * as Col from '@xh/hoist/admin/columns';
|
|
|
9
9
|
import {FilterChooserModel} from '@xh/hoist/cmp/filter';
|
|
10
10
|
import {FormModel} from '@xh/hoist/cmp/form';
|
|
11
11
|
import {GridModel} from '@xh/hoist/cmp/grid';
|
|
12
|
-
import {HoistModel, LoadSpec, managed, XH} from '@xh/hoist/core';
|
|
12
|
+
import {HoistModel, LoadSpec, managed, PlainObject, XH} from '@xh/hoist/core';
|
|
13
13
|
import {StoreRecord} from '@xh/hoist/data';
|
|
14
14
|
import {fmtJson} from '@xh/hoist/format';
|
|
15
15
|
import {action, bindable, comparer, computed, makeObservable, observable} from '@xh/hoist/mobx';
|
|
@@ -62,6 +62,14 @@ export class ClientErrorsModel extends HoistModel {
|
|
|
62
62
|
{...Col.appVersion},
|
|
63
63
|
{...Col.appEnvironment},
|
|
64
64
|
{...Col.msg, displayName: 'User Message', hidden},
|
|
65
|
+
{
|
|
66
|
+
field: {name: 'errorName', type: 'string'},
|
|
67
|
+
autosizeMaxWidth: 400
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
field: {name: 'errorMessage', type: 'string'},
|
|
71
|
+
autosizeMaxWidth: 400
|
|
72
|
+
},
|
|
65
73
|
{...Col.error, hidden},
|
|
66
74
|
{...Col.url},
|
|
67
75
|
{...Col.correlationId},
|
|
@@ -119,18 +127,29 @@ export class ClientErrorsModel extends HoistModel {
|
|
|
119
127
|
}
|
|
120
128
|
|
|
121
129
|
override async doLoadAsync(loadSpec: LoadSpec) {
|
|
122
|
-
const {gridModel} = this;
|
|
130
|
+
const {query, gridModel} = this;
|
|
123
131
|
|
|
124
132
|
try {
|
|
125
|
-
const data = await XH.fetchService.postJson({
|
|
133
|
+
const data: PlainObject[] = await XH.fetchService.postJson({
|
|
126
134
|
url: 'clientErrorAdmin',
|
|
127
|
-
body:
|
|
135
|
+
body: query,
|
|
128
136
|
loadSpec
|
|
129
137
|
});
|
|
130
138
|
|
|
139
|
+
// Parse name + message from JSON-serialized error object out to top-level properties.
|
|
140
|
+
data.forEach(it => {
|
|
141
|
+
try {
|
|
142
|
+
const error = JSON.parse(it.error);
|
|
143
|
+
it.errorName = error?.name;
|
|
144
|
+
it.errorMessage = error?.message;
|
|
145
|
+
} catch (ignored) {}
|
|
146
|
+
});
|
|
147
|
+
|
|
131
148
|
gridModel.loadData(data);
|
|
132
149
|
await gridModel.preSelectFirstAsync();
|
|
133
150
|
} catch (e) {
|
|
151
|
+
if (loadSpec.isStale || loadSpec.isAutoRefresh) return;
|
|
152
|
+
|
|
134
153
|
gridModel.clear();
|
|
135
154
|
XH.handleException(e);
|
|
136
155
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { HoistBase } from '@xh/hoist/core';
|
|
2
|
-
import { Token
|
|
2
|
+
import { Token } from '@xh/hoist/security/Token';
|
|
3
|
+
import { AccessTokenSpec, TokenMap } from './Types';
|
|
3
4
|
export type LoginMethod = 'REDIRECT' | 'POPUP';
|
|
4
|
-
export interface BaseOAuthClientConfig<S> {
|
|
5
|
+
export interface BaseOAuthClientConfig<S extends AccessTokenSpec> {
|
|
5
6
|
/** Client ID (GUID) of your app registered with your Oauth provider. */
|
|
6
7
|
clientId: string;
|
|
7
8
|
/**
|
|
@@ -23,24 +24,16 @@ export interface BaseOAuthClientConfig<S> {
|
|
|
23
24
|
* Governs an optional refresh timer that will work to keep the tokens fresh.
|
|
24
25
|
*
|
|
25
26
|
* A typical refresh will use the underlying provider cache, and should not result in
|
|
26
|
-
* network activity. However, if any token
|
|
27
|
+
* network activity. However, if any token would expire before the next autoRefresh,
|
|
27
28
|
* this client will force a call to the underlying provider to get the token.
|
|
28
29
|
*
|
|
29
30
|
* In order to allow aging tokens to be replaced in a timely manner, this value should be
|
|
30
31
|
* significantly shorter than both the minimum token lifetime that will be
|
|
31
|
-
* returned by the underlying API
|
|
32
|
+
* returned by the underlying API.
|
|
32
33
|
*
|
|
33
34
|
* Default is -1, disabling this behavior.
|
|
34
35
|
*/
|
|
35
36
|
autoRefreshSecs?: number;
|
|
36
|
-
/**
|
|
37
|
-
* During auto-refresh, if the remaining lifetime for any token is below this threshold,
|
|
38
|
-
* force the provider to skip the local cache and go directly to the underlying provider for
|
|
39
|
-
* new tokens and refresh tokens.
|
|
40
|
-
*
|
|
41
|
-
* Default is -1, disabling this behavior.
|
|
42
|
-
*/
|
|
43
|
-
autoRefreshSkipCacheSecs?: number;
|
|
44
37
|
/**
|
|
45
38
|
* Scopes to request - if any - beyond the core `['openid', 'email']` scopes, which
|
|
46
39
|
* this client will always request.
|
|
@@ -68,7 +61,7 @@ export interface BaseOAuthClientConfig<S> {
|
|
|
68
61
|
* server-side `AuthenticationService` implementation to validate the token and actually resolve
|
|
69
62
|
* the user.) On init, the client impl will initiate a pop-up or redirect flow as necessary.
|
|
70
63
|
*/
|
|
71
|
-
export declare abstract class BaseOAuthClient<C extends BaseOAuthClientConfig<S>, S> extends HoistBase {
|
|
64
|
+
export declare abstract class BaseOAuthClient<C extends BaseOAuthClientConfig<S>, S extends AccessTokenSpec> extends HoistBase {
|
|
72
65
|
/** Config loaded from UI server + init method. */
|
|
73
66
|
protected config: C;
|
|
74
67
|
/** ID Scopes */
|
|
@@ -100,9 +93,12 @@ export declare abstract class BaseOAuthClient<C extends BaseOAuthClientConfig<S>
|
|
|
100
93
|
*/
|
|
101
94
|
getAccessTokenAsync(key: string): Promise<Token>;
|
|
102
95
|
/**
|
|
103
|
-
* Get all
|
|
96
|
+
* Get all configured tokens.
|
|
104
97
|
*/
|
|
105
|
-
getAllTokensAsync(
|
|
98
|
+
getAllTokensAsync(opts?: {
|
|
99
|
+
eagerOnly?: boolean;
|
|
100
|
+
useCache?: boolean;
|
|
101
|
+
}): Promise<TokenMap>;
|
|
106
102
|
/**
|
|
107
103
|
* The last authenticated OAuth username.
|
|
108
104
|
*
|
|
@@ -143,7 +139,10 @@ export declare abstract class BaseOAuthClient<C extends BaseOAuthClientConfig<S>
|
|
|
143
139
|
protected restoreRedirectState(key: string): void;
|
|
144
140
|
/** Call after requesting the provider library redirect the user away for auth. */
|
|
145
141
|
protected maskAfterRedirectAsync(): Promise<void>;
|
|
146
|
-
protected fetchAllTokensAsync(
|
|
142
|
+
protected fetchAllTokensAsync(opts?: {
|
|
143
|
+
eagerOnly?: boolean;
|
|
144
|
+
useCache?: boolean;
|
|
145
|
+
}): Promise<TokenMap>;
|
|
147
146
|
protected getLocalStorage(key: string, defaultValue?: any): any;
|
|
148
147
|
protected setLocalStorage(key: string, value: any): void;
|
|
149
148
|
private fetchIdTokenSafeAsync;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Token } from './Token';
|
|
2
|
+
export type TokenMap = Record<string, Token>;
|
|
3
|
+
export interface AccessTokenSpec {
|
|
4
|
+
/**
|
|
5
|
+
* Mode governing when the access token should be requested from provider.
|
|
6
|
+
* eager (default) - initiate lookup on initialization, but do not block on failure.
|
|
7
|
+
* lazy - lookup when requested by client.
|
|
8
|
+
*/
|
|
9
|
+
fetchMode: 'eager' | 'lazy';
|
|
10
|
+
/** Scopes for the desired access token.*/
|
|
11
|
+
scopes: string[];
|
|
12
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Auth0ClientOptions } from '@auth0/auth0-spa-js';
|
|
2
|
-
import { Token
|
|
2
|
+
import { Token } from '@xh/hoist/security/Token';
|
|
3
|
+
import { AccessTokenSpec, TokenMap } from '../Types';
|
|
3
4
|
import { BaseOAuthClient, BaseOAuthClientConfig } from '../BaseOAuthClient';
|
|
4
5
|
export interface AuthZeroClientConfig extends BaseOAuthClientConfig<AuthZeroTokenSpec> {
|
|
5
6
|
/** Domain of your app registered with Auth0. */
|
|
@@ -24,9 +25,7 @@ export interface AuthZeroClientConfig extends BaseOAuthClientConfig<AuthZeroToke
|
|
|
24
25
|
*/
|
|
25
26
|
authZeroClientOptions?: Partial<Auth0ClientOptions>;
|
|
26
27
|
}
|
|
27
|
-
export interface AuthZeroTokenSpec {
|
|
28
|
-
/** Scopes for the desired access token.*/
|
|
29
|
-
scopes: string[];
|
|
28
|
+
export interface AuthZeroTokenSpec extends AccessTokenSpec {
|
|
30
29
|
/**
|
|
31
30
|
* Audience (i.e. API) identifier for AccessToken. Must be registered with Auth0.
|
|
32
31
|
* Note that this is required to ensure that issued token is a JWT and not an opaque string.
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as msal from '@azure/msal-browser';
|
|
2
2
|
import { LogLevel } from '@azure/msal-browser';
|
|
3
|
-
import { Token
|
|
3
|
+
import { Token } from '@xh/hoist/security/Token';
|
|
4
|
+
import { AccessTokenSpec, TokenMap } from '../Types';
|
|
4
5
|
import { BaseOAuthClient, BaseOAuthClientConfig } from '../BaseOAuthClient';
|
|
5
6
|
export interface MsalClientConfig extends BaseOAuthClientConfig<MsalTokenSpec> {
|
|
6
7
|
/**
|
|
@@ -44,9 +45,7 @@ export interface MsalClientConfig extends BaseOAuthClientConfig<MsalTokenSpec> {
|
|
|
44
45
|
*/
|
|
45
46
|
msalClientOptions?: Partial<msal.Configuration>;
|
|
46
47
|
}
|
|
47
|
-
export interface MsalTokenSpec {
|
|
48
|
-
/** Scopes for the desired access token. */
|
|
49
|
-
scopes: string[];
|
|
48
|
+
export interface MsalTokenSpec extends AccessTokenSpec {
|
|
50
49
|
/**
|
|
51
50
|
* Scopes to be added to the scopes requested during interactive and SSO logins.
|
|
52
51
|
* See the `scopes` property on `PopupRequest`, `RedirectRequest`, and `SSORequest`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xh/hoist",
|
|
3
|
-
"version": "73.0.0-SNAPSHOT.
|
|
3
|
+
"version": "73.0.0-SNAPSHOT.1741808956502",
|
|
4
4
|
"description": "Hoist add-on for building and deploying React Applications.",
|
|
5
5
|
"repository": "github:xh/hoist-react",
|
|
6
6
|
"homepage": "https://xh.io",
|
|
@@ -9,16 +9,17 @@ import {HoistBase, managed, XH} from '@xh/hoist/core';
|
|
|
9
9
|
import {Icon} from '@xh/hoist/icon';
|
|
10
10
|
import {action, makeObservable} from '@xh/hoist/mobx';
|
|
11
11
|
import {never, wait} from '@xh/hoist/promise';
|
|
12
|
-
import {Token
|
|
12
|
+
import {Token} from '@xh/hoist/security/Token';
|
|
13
|
+
import {AccessTokenSpec, TokenMap} from './Types';
|
|
13
14
|
import {Timer} from '@xh/hoist/utils/async';
|
|
14
15
|
import {MINUTES, olderThan, ONE_MINUTE, SECONDS} from '@xh/hoist/utils/datetime';
|
|
15
16
|
import {isJSON, throwIf} from '@xh/hoist/utils/js';
|
|
16
|
-
import {find, forEach, isEmpty, isObject, keys, pickBy, union} from 'lodash';
|
|
17
|
+
import {find, forEach, isEmpty, isObject, keys, pickBy, toPairs, union} from 'lodash';
|
|
17
18
|
import ShortUniqueId from 'short-unique-id';
|
|
18
19
|
|
|
19
20
|
export type LoginMethod = 'REDIRECT' | 'POPUP';
|
|
20
21
|
|
|
21
|
-
export interface BaseOAuthClientConfig<S> {
|
|
22
|
+
export interface BaseOAuthClientConfig<S extends AccessTokenSpec> {
|
|
22
23
|
/** Client ID (GUID) of your app registered with your Oauth provider. */
|
|
23
24
|
clientId: string;
|
|
24
25
|
|
|
@@ -45,26 +46,17 @@ export interface BaseOAuthClientConfig<S> {
|
|
|
45
46
|
* Governs an optional refresh timer that will work to keep the tokens fresh.
|
|
46
47
|
*
|
|
47
48
|
* A typical refresh will use the underlying provider cache, and should not result in
|
|
48
|
-
* network activity. However, if any token
|
|
49
|
+
* network activity. However, if any token would expire before the next autoRefresh,
|
|
49
50
|
* this client will force a call to the underlying provider to get the token.
|
|
50
51
|
*
|
|
51
52
|
* In order to allow aging tokens to be replaced in a timely manner, this value should be
|
|
52
53
|
* significantly shorter than both the minimum token lifetime that will be
|
|
53
|
-
* returned by the underlying API
|
|
54
|
+
* returned by the underlying API.
|
|
54
55
|
*
|
|
55
56
|
* Default is -1, disabling this behavior.
|
|
56
57
|
*/
|
|
57
58
|
autoRefreshSecs?: number;
|
|
58
59
|
|
|
59
|
-
/**
|
|
60
|
-
* During auto-refresh, if the remaining lifetime for any token is below this threshold,
|
|
61
|
-
* force the provider to skip the local cache and go directly to the underlying provider for
|
|
62
|
-
* new tokens and refresh tokens.
|
|
63
|
-
*
|
|
64
|
-
* Default is -1, disabling this behavior.
|
|
65
|
-
*/
|
|
66
|
-
autoRefreshSkipCacheSecs?: number;
|
|
67
|
-
|
|
68
60
|
/**
|
|
69
61
|
* Scopes to request - if any - beyond the core `['openid', 'email']` scopes, which
|
|
70
62
|
* this client will always request.
|
|
@@ -94,7 +86,10 @@ export interface BaseOAuthClientConfig<S> {
|
|
|
94
86
|
* server-side `AuthenticationService` implementation to validate the token and actually resolve
|
|
95
87
|
* the user.) On init, the client impl will initiate a pop-up or redirect flow as necessary.
|
|
96
88
|
*/
|
|
97
|
-
export abstract class BaseOAuthClient<
|
|
89
|
+
export abstract class BaseOAuthClient<
|
|
90
|
+
C extends BaseOAuthClientConfig<S>,
|
|
91
|
+
S extends AccessTokenSpec
|
|
92
|
+
> extends HoistBase {
|
|
98
93
|
/** Config loaded from UI server + init method. */
|
|
99
94
|
protected config: C;
|
|
100
95
|
|
|
@@ -120,7 +115,6 @@ export abstract class BaseOAuthClient<C extends BaseOAuthClientConfig<S>, S> ext
|
|
|
120
115
|
redirectUrl: 'APP_BASE_URL',
|
|
121
116
|
postLogoutRedirectUrl: 'APP_BASE_URL',
|
|
122
117
|
autoRefreshSecs: -1,
|
|
123
|
-
autoRefreshSkipCacheSecs: -1,
|
|
124
118
|
...config
|
|
125
119
|
};
|
|
126
120
|
throwIf(!config.clientId, 'Missing OAuth clientId. Please review your configuration.');
|
|
@@ -174,10 +168,10 @@ export abstract class BaseOAuthClient<C extends BaseOAuthClientConfig<S>, S> ext
|
|
|
174
168
|
}
|
|
175
169
|
|
|
176
170
|
/**
|
|
177
|
-
* Get all
|
|
171
|
+
* Get all configured tokens.
|
|
178
172
|
*/
|
|
179
|
-
async getAllTokensAsync(): Promise<TokenMap> {
|
|
180
|
-
return this.fetchAllTokensAsync(
|
|
173
|
+
async getAllTokensAsync(opts?: {eagerOnly?: boolean; useCache?: boolean}): Promise<TokenMap> {
|
|
174
|
+
return this.fetchAllTokensAsync(opts);
|
|
181
175
|
}
|
|
182
176
|
|
|
183
177
|
/**
|
|
@@ -313,12 +307,27 @@ export abstract class BaseOAuthClient<C extends BaseOAuthClientConfig<S>, S> ext
|
|
|
313
307
|
await never();
|
|
314
308
|
}
|
|
315
309
|
|
|
316
|
-
protected async fetchAllTokensAsync(
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
310
|
+
protected async fetchAllTokensAsync(opts?: {
|
|
311
|
+
eagerOnly?: boolean;
|
|
312
|
+
useCache?: boolean;
|
|
313
|
+
}): Promise<TokenMap> {
|
|
314
|
+
const eagerOnly = opts?.eagerOnly ?? false,
|
|
315
|
+
useCache = opts?.useCache ?? true,
|
|
316
|
+
accessSpecs = eagerOnly
|
|
317
|
+
? pickBy(this.accessSpecs, spec => spec.fetchMode === 'eager')
|
|
318
|
+
: this.accessSpecs,
|
|
319
|
+
ret: TokenMap = {};
|
|
320
|
+
|
|
321
|
+
await Promise.allSettled(
|
|
322
|
+
toPairs(accessSpecs).map(async ([key, spec]) => {
|
|
323
|
+
try {
|
|
324
|
+
ret[key] = await this.fetchAccessTokenAsync(spec, useCache);
|
|
325
|
+
} catch (e) {
|
|
326
|
+
XH.handleException(e, {logOnServer: true, showAlert: false});
|
|
327
|
+
}
|
|
328
|
+
})
|
|
329
|
+
);
|
|
330
|
+
|
|
322
331
|
// Do this after getting any access tokens --which can also populate the idToken cache!
|
|
323
332
|
ret.id = await this.fetchIdTokenSafeAsync(useCache);
|
|
324
333
|
|
|
@@ -360,22 +369,19 @@ export abstract class BaseOAuthClient<C extends BaseOAuthClientConfig<S>, S> ext
|
|
|
360
369
|
private async onTimerAsync(): Promise<void> {
|
|
361
370
|
const {config, lastRefreshAttempt} = this,
|
|
362
371
|
refreshSecs = config.autoRefreshSecs * SECONDS,
|
|
363
|
-
skipCacheSecs =
|
|
372
|
+
skipCacheSecs = refreshSecs + 5 * SECONDS;
|
|
364
373
|
|
|
365
374
|
if (olderThan(lastRefreshAttempt, refreshSecs)) {
|
|
366
375
|
this.lastRefreshAttempt = Date.now();
|
|
367
376
|
try {
|
|
368
377
|
this.logDebug('Refreshing all tokens:');
|
|
369
378
|
let tokens = await this.fetchAllTokensAsync(),
|
|
370
|
-
aging = pickBy(
|
|
371
|
-
tokens,
|
|
372
|
-
v => skipCacheSecs > 0 && v.expiresWithin(skipCacheSecs)
|
|
373
|
-
);
|
|
379
|
+
aging = pickBy(tokens, v => v.expiresWithin(skipCacheSecs));
|
|
374
380
|
if (!isEmpty(aging)) {
|
|
375
381
|
this.logDebug(
|
|
376
382
|
`Tokens [${keys(aging).join(', ')}] have < ${skipCacheSecs}s remaining, reloading without cache.`
|
|
377
383
|
);
|
|
378
|
-
tokens = await this.fetchAllTokensAsync(false);
|
|
384
|
+
tokens = await this.fetchAllTokensAsync({useCache: false});
|
|
379
385
|
}
|
|
380
386
|
this.logTokensDebug(tokens);
|
|
381
387
|
} catch (e) {
|
package/security/Token.ts
CHANGED
|
@@ -11,8 +11,6 @@ import {jwtDecode} from 'jwt-decode';
|
|
|
11
11
|
import {getRelativeTimestamp} from '@xh/hoist/cmp/relativetimestamp';
|
|
12
12
|
import {isNil} from 'lodash';
|
|
13
13
|
|
|
14
|
-
export type TokenMap = Record<string, Token>;
|
|
15
|
-
|
|
16
14
|
export class Token {
|
|
17
15
|
readonly value: string;
|
|
18
16
|
readonly decoded: PlainObject;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This file belongs to Hoist, an application development toolkit
|
|
3
|
+
* developed by Extremely Heavy Industries (www.xh.io | info@xh.io)
|
|
4
|
+
*
|
|
5
|
+
* Copyright © 2025 Extremely Heavy Industries Inc.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {Token} from './Token';
|
|
9
|
+
|
|
10
|
+
export type TokenMap = Record<string, Token>;
|
|
11
|
+
|
|
12
|
+
export interface AccessTokenSpec {
|
|
13
|
+
/**
|
|
14
|
+
* Mode governing when the access token should be requested from provider.
|
|
15
|
+
* eager (default) - initiate lookup on initialization, but do not block on failure.
|
|
16
|
+
* lazy - lookup when requested by client.
|
|
17
|
+
*/
|
|
18
|
+
fetchMode: 'eager' | 'lazy';
|
|
19
|
+
|
|
20
|
+
/** Scopes for the desired access token.*/
|
|
21
|
+
scopes: string[];
|
|
22
|
+
}
|
|
@@ -8,7 +8,8 @@ import type {Auth0ClientOptions} from '@auth0/auth0-spa-js';
|
|
|
8
8
|
import {Auth0Client} from '@auth0/auth0-spa-js';
|
|
9
9
|
import {XH} from '@xh/hoist/core';
|
|
10
10
|
import {wait} from '@xh/hoist/promise';
|
|
11
|
-
import {Token
|
|
11
|
+
import {Token} from '@xh/hoist/security/Token';
|
|
12
|
+
import {AccessTokenSpec, TokenMap} from '../Types';
|
|
12
13
|
import {SECONDS} from '@xh/hoist/utils/datetime';
|
|
13
14
|
import {mergeDeep, throwIf} from '@xh/hoist/utils/js';
|
|
14
15
|
import {flatMap, union} from 'lodash';
|
|
@@ -40,10 +41,7 @@ export interface AuthZeroClientConfig extends BaseOAuthClientConfig<AuthZeroToke
|
|
|
40
41
|
authZeroClientOptions?: Partial<Auth0ClientOptions>;
|
|
41
42
|
}
|
|
42
43
|
|
|
43
|
-
export interface AuthZeroTokenSpec {
|
|
44
|
-
/** Scopes for the desired access token.*/
|
|
45
|
-
scopes: string[];
|
|
46
|
-
|
|
44
|
+
export interface AuthZeroTokenSpec extends AccessTokenSpec {
|
|
47
45
|
/**
|
|
48
46
|
* Audience (i.e. API) identifier for AccessToken. Must be registered with Auth0.
|
|
49
47
|
* Note that this is required to ensure that issued token is a JWT and not an opaque string.
|
|
@@ -75,7 +73,7 @@ export class AuthZeroClient extends BaseOAuthClient<AuthZeroClientConfig, AuthZe
|
|
|
75
73
|
const {appState} = await client.handleRedirectCallback();
|
|
76
74
|
this.restoreRedirectState(appState);
|
|
77
75
|
await this.noteUserAuthenticatedAsync();
|
|
78
|
-
return this.fetchAllTokensAsync();
|
|
76
|
+
return this.fetchAllTokensAsync({eagerOnly: true});
|
|
79
77
|
}
|
|
80
78
|
|
|
81
79
|
// 1) If we are logged in, try to just reload tokens silently. This is the happy path on
|
|
@@ -83,7 +81,7 @@ export class AuthZeroClient extends BaseOAuthClient<AuthZeroClientConfig, AuthZe
|
|
|
83
81
|
if (await client.isAuthenticated()) {
|
|
84
82
|
try {
|
|
85
83
|
this.logDebug('Attempting silent token load.');
|
|
86
|
-
return await this.fetchAllTokensAsync();
|
|
84
|
+
return await this.fetchAllTokensAsync({eagerOnly: true});
|
|
87
85
|
} catch (e) {
|
|
88
86
|
this.logDebug('Failed to load tokens on init, fall back to login', e.message ?? e);
|
|
89
87
|
}
|
|
@@ -94,7 +92,7 @@ export class AuthZeroClient extends BaseOAuthClient<AuthZeroClientConfig, AuthZe
|
|
|
94
92
|
await this.loginAsync();
|
|
95
93
|
|
|
96
94
|
// 3) return tokens
|
|
97
|
-
return this.fetchAllTokensAsync();
|
|
95
|
+
return this.fetchAllTokensAsync({eagerOnly: true});
|
|
98
96
|
}
|
|
99
97
|
|
|
100
98
|
protected override async doLoginRedirectAsync(): Promise<void> {
|
|
@@ -13,7 +13,8 @@ import {
|
|
|
13
13
|
SilentRequest
|
|
14
14
|
} from '@azure/msal-browser';
|
|
15
15
|
import {XH} from '@xh/hoist/core';
|
|
16
|
-
import {Token
|
|
16
|
+
import {Token} from '@xh/hoist/security/Token';
|
|
17
|
+
import {AccessTokenSpec, TokenMap} from '../Types';
|
|
17
18
|
import {logDebug, logError, logInfo, logWarn, mergeDeep, throwIf} from '@xh/hoist/utils/js';
|
|
18
19
|
import {flatMap, union, uniq} from 'lodash';
|
|
19
20
|
import {BaseOAuthClient, BaseOAuthClientConfig} from '../BaseOAuthClient';
|
|
@@ -65,10 +66,7 @@ export interface MsalClientConfig extends BaseOAuthClientConfig<MsalTokenSpec> {
|
|
|
65
66
|
msalClientOptions?: Partial<msal.Configuration>;
|
|
66
67
|
}
|
|
67
68
|
|
|
68
|
-
export interface MsalTokenSpec {
|
|
69
|
-
/** Scopes for the desired access token. */
|
|
70
|
-
scopes: string[];
|
|
71
|
-
|
|
69
|
+
export interface MsalTokenSpec extends AccessTokenSpec {
|
|
72
70
|
/**
|
|
73
71
|
* Scopes to be added to the scopes requested during interactive and SSO logins.
|
|
74
72
|
* See the `scopes` property on `PopupRequest`, `RedirectRequest`, and `SSORequest`
|
|
@@ -127,7 +125,7 @@ export class MsalClient extends BaseOAuthClient<MsalClientConfig, MsalTokenSpec>
|
|
|
127
125
|
this.logDebug('Completing Redirect login');
|
|
128
126
|
this.noteUserAuthenticated(redirectResp.account);
|
|
129
127
|
this.restoreRedirectState(redirectResp.state);
|
|
130
|
-
return this.fetchAllTokensAsync();
|
|
128
|
+
return this.fetchAllTokensAsync({eagerOnly: true});
|
|
131
129
|
}
|
|
132
130
|
|
|
133
131
|
// 1) If we are logged in, try to just reload tokens silently. This is the happy path on
|
|
@@ -142,7 +140,7 @@ export class MsalClient extends BaseOAuthClient<MsalClientConfig, MsalTokenSpec>
|
|
|
142
140
|
try {
|
|
143
141
|
this.initialTokenLoad = true;
|
|
144
142
|
this.logDebug('Attempting silent token load.');
|
|
145
|
-
return await this.fetchAllTokensAsync();
|
|
143
|
+
return await this.fetchAllTokensAsync({eagerOnly: true});
|
|
146
144
|
} catch (e) {
|
|
147
145
|
this.account = null;
|
|
148
146
|
this.logDebug('Failed to load tokens on init, fall back to login', e.message ?? e);
|
|
@@ -171,7 +169,7 @@ export class MsalClient extends BaseOAuthClient<MsalClientConfig, MsalTokenSpec>
|
|
|
171
169
|
}
|
|
172
170
|
|
|
173
171
|
// 3) Return tokens
|
|
174
|
-
return this.fetchAllTokensAsync();
|
|
172
|
+
return this.fetchAllTokensAsync({eagerOnly: true});
|
|
175
173
|
}
|
|
176
174
|
|
|
177
175
|
protected override async doLoginPopupAsync(): Promise<void> {
|