@equinor/fusion-framework-module-msal 11.0.0 → 11.0.1
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/dist/esm/version.js +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/version.d.ts +1 -1
- package/package.json +8 -5
- package/CHANGELOG.md +0 -1212
- package/docs/api-reference.md +0 -85
- package/docs/auth-code-flow.md +0 -86
- package/docs/migration-v2-to-v4.md +0 -115
- package/docs/testing.md +0 -191
- package/docs/troubleshooting.md +0 -17
- package/docs/version-management.md +0 -67
- package/src/MsalClient.interface.ts +0 -139
- package/src/MsalClient.ts +0 -326
- package/src/MsalConfigurator.ts +0 -486
- package/src/MsalProvider.interface.ts +0 -179
- package/src/MsalProvider.ts +0 -776
- package/src/MsalProxyProvider.interface.ts +0 -72
- package/src/__tests__/MsalConfigurator.test.ts +0 -222
- package/src/__tests__/MsalProvider.test.ts +0 -74
- package/src/__tests__/create-proxy-provider.test.ts +0 -77
- package/src/__tests__/mock/create-mock-user-from-token.test.ts +0 -46
- package/src/__tests__/mock/msal-mock.test.ts +0 -613
- package/src/__tests__/versioning/resolve-version.test.ts +0 -161
- package/src/create-client-log-callback.ts +0 -102
- package/src/create-proxy-provider.ts +0 -97
- package/src/index.ts +0 -48
- package/src/mock/MsalMockClient.ts +0 -618
- package/src/mock/MsalMockConfigurator.ts +0 -305
- package/src/mock/create-mock-token.ts +0 -92
- package/src/mock/create-mock-user-from-token.ts +0 -46
- package/src/mock/create-msal-mock-client.ts +0 -25
- package/src/mock/decode-jwt-segment.ts +0 -22
- package/src/mock/index.ts +0 -30
- package/src/mock/module.ts +0 -54
- package/src/module.ts +0 -142
- package/src/msal-config-schema.ts +0 -81
- package/src/static.ts +0 -38
- package/src/telemetry-config-schema.ts +0 -25
- package/src/types.ts +0 -16
- package/src/util/compare-origin.ts +0 -18
- package/src/util/normalize-uri.ts +0 -24
- package/src/util/redirect.ts +0 -19
- package/src/v2/IAuthClient.interface.ts +0 -114
- package/src/v2/Logger.ts +0 -204
- package/src/v2/MsalProvider.interface.ts +0 -102
- package/src/v2/create-proxy-client.ts +0 -195
- package/src/v2/create-proxy-provider.ts +0 -177
- package/src/v2/map-account-info.ts +0 -23
- package/src/v2/map-authentication-result.ts +0 -28
- package/src/v2/types.ts +0 -674
- package/src/v4/create-proxy-provider.ts +0 -75
- package/src/v4/index.ts +0 -13
- package/src/v4/types.ts +0 -727
- package/src/version.ts +0 -2
- package/src/versioning/VersionError.ts +0 -64
- package/src/versioning/index.ts +0 -29
- package/src/versioning/resolve-version.ts +0 -154
- package/src/versioning/types.ts +0 -60
- package/tsconfig.json +0 -18
- package/vitest.config.ts +0 -11
|
@@ -1,618 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
AccountInfo,
|
|
3
|
-
AuthenticationResult,
|
|
4
|
-
AuthorizationCodeRequest,
|
|
5
|
-
PopupRequest,
|
|
6
|
-
RedirectRequest,
|
|
7
|
-
SilentRequest,
|
|
8
|
-
SsoSilentRequest,
|
|
9
|
-
EndSessionRequest,
|
|
10
|
-
EndSessionPopupRequest,
|
|
11
|
-
InitializeApplicationRequest,
|
|
12
|
-
WrapperSKU,
|
|
13
|
-
INavigationClient,
|
|
14
|
-
BrowserConfiguration,
|
|
15
|
-
Logger,
|
|
16
|
-
PerformanceCallbackFunction,
|
|
17
|
-
EventCallbackFunction,
|
|
18
|
-
EventType,
|
|
19
|
-
ClearCacheRequest,
|
|
20
|
-
} from '@azure/msal-browser';
|
|
21
|
-
|
|
22
|
-
import type {
|
|
23
|
-
AcquireTokenOptions,
|
|
24
|
-
AcquireTokenResult,
|
|
25
|
-
IMsalClient,
|
|
26
|
-
LoginOptions,
|
|
27
|
-
LoginResult,
|
|
28
|
-
} from '../MsalClient.interface';
|
|
29
|
-
import type { MsalClientConfig, MsalClient } from '../MsalClient';
|
|
30
|
-
|
|
31
|
-
import { createMockToken } from './create-mock-token';
|
|
32
|
-
/**
|
|
33
|
-
* The user a mock MSAL client signs in.
|
|
34
|
-
*
|
|
35
|
-
* @remarks
|
|
36
|
-
* Deliberately separate from {@link MsalClientConfig}: a client is configured
|
|
37
|
-
* with *what it talks to*, never with *who is signed in*. The real client learns
|
|
38
|
-
* the user from Entra ID, so the mock is told after it is constructed — see
|
|
39
|
-
* {@link MsalMockClient.setUser | setUser}.
|
|
40
|
-
*/
|
|
41
|
-
export interface MsalMockUser {
|
|
42
|
-
/** Display name of the signed-in user. Defaults to `Test User`. */
|
|
43
|
-
name?: string;
|
|
44
|
-
/** UPN / email of the signed-in user. Defaults to `test.user@equinor.com`. */
|
|
45
|
-
username?: string;
|
|
46
|
-
/** Object ID of the signed-in user. Defaults to `fusion-mock-user`. */
|
|
47
|
-
userId?: string;
|
|
48
|
-
/** Tenant the user belongs to. Defaults to the client's configured tenant. */
|
|
49
|
-
tenantId?: string;
|
|
50
|
-
/** Scopes granted when a request does not specify its own. */
|
|
51
|
-
scopes?: string[];
|
|
52
|
-
/** Preconfigured account to use for signed-in state. */
|
|
53
|
-
account?: AccountInfo;
|
|
54
|
-
/**
|
|
55
|
-
* Start without a signed-in user, while keeping this identity.
|
|
56
|
-
*
|
|
57
|
-
* @remarks
|
|
58
|
-
* Silent flows then resolve empty so the provider follows its unauthenticated
|
|
59
|
-
* path, while an explicit login still succeeds *as this user*. That lets a
|
|
60
|
-
* test drive the sign-in journey and assert on who it ends up as, rather than
|
|
61
|
-
* only on its end state.
|
|
62
|
-
*
|
|
63
|
-
* Pass `null` instead of a user when the identity does not matter.
|
|
64
|
-
*/
|
|
65
|
-
signedOut?: boolean;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* A stand-in for the MSAL client that resolves tokens in-process.
|
|
70
|
-
*
|
|
71
|
-
* @remarks
|
|
72
|
-
* Constructed from {@link MsalClientConfig} — the very same argument
|
|
73
|
-
* {@link MsalClient} takes — so it is a drop-in substitute rather than a second
|
|
74
|
-
* API to learn. `setClientConfig` therefore means the same thing whether a test
|
|
75
|
-
* runs against Entra ID or against this client.
|
|
76
|
-
*
|
|
77
|
-
* Only the boundary that would contact Entra ID is replaced. The real
|
|
78
|
-
* `MsalProvider` runs on top of it unchanged, so account handling, silent-token
|
|
79
|
-
* preference, scope resolution, proxy providers and telemetry behave as they do in
|
|
80
|
-
* production — the test exercises the framework rather than the mock.
|
|
81
|
-
*
|
|
82
|
-
* Tokens are structurally valid, unsigned JWTs and are byte-identical between runs.
|
|
83
|
-
* They are **not** cryptographically valid and are rejected by any real service.
|
|
84
|
-
*/
|
|
85
|
-
export class MsalMockClient implements IMsalClient {
|
|
86
|
-
#user: Required<Pick<MsalMockUser, 'name' | 'username' | 'userId' | 'tenantId' | 'scopes'>> & {
|
|
87
|
-
clientId: string;
|
|
88
|
-
};
|
|
89
|
-
#cache = new Map<string, AccountInfo>();
|
|
90
|
-
#activeAccountId: string | null = null;
|
|
91
|
-
#token: string | null = null;
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* The account currently signed in, or `null`.
|
|
95
|
-
*
|
|
96
|
-
* @remarks
|
|
97
|
-
* Reads through the cache rather than holding an account of its own, so an
|
|
98
|
-
* account removed by a sign-out cannot linger as the active one.
|
|
99
|
-
*
|
|
100
|
-
* @returns The cached active account, or `null` when none is active.
|
|
101
|
-
*/
|
|
102
|
-
get #account(): AccountInfo | null {
|
|
103
|
-
return this.#activeAccountId ? (this.#cache.get(this.#activeAccountId) ?? null) : null;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* Signs an account in, adding it to the cache and making it active.
|
|
108
|
-
*
|
|
109
|
-
* @param account - The account to sign in.
|
|
110
|
-
* @returns The signed-in account.
|
|
111
|
-
*/
|
|
112
|
-
#signIn(account: AccountInfo): AccountInfo {
|
|
113
|
-
this.#cache.set(account.homeAccountId, account);
|
|
114
|
-
this.#activeAccountId = account.homeAccountId;
|
|
115
|
-
return account;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* Signs the active account out, as MSAL does — the account leaves the cache.
|
|
120
|
-
*/
|
|
121
|
-
#signOut(): void {
|
|
122
|
-
// Only clear the cache entry when a session is active; this preserves an already signed-out state.
|
|
123
|
-
if (this.#activeAccountId) {
|
|
124
|
-
this.#cache.delete(this.#activeAccountId);
|
|
125
|
-
this.#activeAccountId = null;
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Mirrors MSAL's redirect completion, which is a no-op for this in-process mock.
|
|
131
|
-
* @returns Always `null`, because the mock performs no redirect.
|
|
132
|
-
*/
|
|
133
|
-
public async handleRedirectPromise(): Promise<AuthenticationResult | null> {
|
|
134
|
-
return null;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/**
|
|
138
|
-
* Mirrors silent SSO and returns a token for the cached mock account.
|
|
139
|
-
* @param request - Silent SSO options and requested scopes.
|
|
140
|
-
* @returns A mock authentication result.
|
|
141
|
-
* @throws When no account is cached.
|
|
142
|
-
*/
|
|
143
|
-
public async ssoSilent(request: SsoSilentRequest): Promise<AuthenticationResult> {
|
|
144
|
-
// Silent SSO must fail without a session so providers exercise their login path.
|
|
145
|
-
if (!this.#account) {
|
|
146
|
-
throw new Error('MsalMockClient: no cached account for silent sign-in');
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
return this.#createResult(request.scopes);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
* Mirrors popup login by signing in the configured mock user immediately.
|
|
154
|
-
* @param request - Optional popup options and requested scopes.
|
|
155
|
-
* @returns A mock authentication result.
|
|
156
|
-
*/
|
|
157
|
-
public async loginPopup(request?: PopupRequest): Promise<AuthenticationResult> {
|
|
158
|
-
this.#signIn(this.#createAccount());
|
|
159
|
-
return this.#createResult(request?.scopes);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
/**
|
|
163
|
-
* Mirrors redirect login without navigating, because the mock has no browser boundary.
|
|
164
|
-
* @param _request - Redirect options, accepted for interface compatibility.
|
|
165
|
-
*/
|
|
166
|
-
public async loginRedirect(_request?: RedirectRequest): Promise<void> {
|
|
167
|
-
this.#signIn(this.#createAccount());
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
/**
|
|
171
|
-
* Mirrors the framework login entry point with an immediate mock sign-in.
|
|
172
|
-
* @param options - Login options including the requested scopes.
|
|
173
|
-
* @returns A mock login result.
|
|
174
|
-
*/
|
|
175
|
-
public async login(options: LoginOptions): Promise<LoginResult> {
|
|
176
|
-
this.#signIn(this.#createAccount());
|
|
177
|
-
return this.#createResult((options.request as { scopes?: string[] })?.scopes);
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
/** Mirrors logout by removing the active mock account from the cache. */
|
|
181
|
-
public async logout(): Promise<void> {
|
|
182
|
-
this.#signOut();
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
/**
|
|
186
|
-
* Mirrors MSAL initialization without performing a network handshake.
|
|
187
|
-
* @param _request - Initialization options, accepted for interface compatibility.
|
|
188
|
-
*/
|
|
189
|
-
public async initialize(_request?: InitializeApplicationRequest): Promise<void> {
|
|
190
|
-
// No network handshake to perform
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
/**
|
|
194
|
-
* Mirrors popup token acquisition using the mock login flow.
|
|
195
|
-
* @param request - Popup token request.
|
|
196
|
-
* @returns A mock authentication result.
|
|
197
|
-
*/
|
|
198
|
-
public async acquireTokenPopup(request: PopupRequest): Promise<AuthenticationResult> {
|
|
199
|
-
return this.loginPopup(request);
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
/**
|
|
203
|
-
* Mirrors redirect token acquisition without browser navigation.
|
|
204
|
-
* @param request - Redirect token request.
|
|
205
|
-
*/
|
|
206
|
-
public async acquireTokenRedirect(request: RedirectRequest): Promise<void> {
|
|
207
|
-
return this.loginRedirect(request);
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
/**
|
|
211
|
-
* Mirrors silent token acquisition for the cached mock account.
|
|
212
|
-
* @param request - Silent token request.
|
|
213
|
-
* @returns A mock authentication result.
|
|
214
|
-
* @throws When no account is cached.
|
|
215
|
-
*/
|
|
216
|
-
public async acquireTokenSilent(request: SilentRequest): Promise<AuthenticationResult> {
|
|
217
|
-
// Silent acquisition must fail without a session, matching the real MSAL boundary.
|
|
218
|
-
if (!this.#account) {
|
|
219
|
-
throw new Error('MsalMockClient: no cached account for silent sign-in');
|
|
220
|
-
}
|
|
221
|
-
return this.#createResult(request.scopes);
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
/**
|
|
225
|
-
* Mirrors MSAL event registration; events are intentionally not emitted by the mock.
|
|
226
|
-
* @param _callback - Event handler, accepted for interface compatibility.
|
|
227
|
-
* @param _eventTypes - Event types, accepted for interface compatibility.
|
|
228
|
-
* @returns Always `null`, because the mock registers no callback.
|
|
229
|
-
*/
|
|
230
|
-
public addEventCallback(
|
|
231
|
-
_callback: EventCallbackFunction,
|
|
232
|
-
_eventTypes?: EventType[],
|
|
233
|
-
): string | null {
|
|
234
|
-
return null;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
/**
|
|
238
|
-
* Mirrors event removal as a no-op because this mock registers no callbacks.
|
|
239
|
-
* @param _callbackId - Callback identifier, accepted for interface compatibility.
|
|
240
|
-
*/
|
|
241
|
-
public removeEventCallback(_callbackId: string): void {
|
|
242
|
-
// No-op for mock
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
/**
|
|
246
|
-
* Mirrors performance callback registration with a stable mock identifier.
|
|
247
|
-
* @param _callback - Performance handler, accepted for interface compatibility.
|
|
248
|
-
* @returns A stable mock callback identifier.
|
|
249
|
-
*/
|
|
250
|
-
public addPerformanceCallback(_callback: PerformanceCallbackFunction): string {
|
|
251
|
-
return 'mock-performance-callback';
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
/**
|
|
255
|
-
* Mirrors performance callback removal and reports successful mock removal.
|
|
256
|
-
* @param _callbackId - Callback identifier, accepted for interface compatibility.
|
|
257
|
-
* @returns Always `true` because no callback state is retained.
|
|
258
|
-
*/
|
|
259
|
-
public removePerformanceCallback(_callbackId: string): boolean {
|
|
260
|
-
return true;
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
/**
|
|
264
|
-
* Mirrors MSAL account lookup against the mock cache.
|
|
265
|
-
* @param accountFilter - Account fields to match.
|
|
266
|
-
* @returns The first matching account, or `null`.
|
|
267
|
-
*/
|
|
268
|
-
public getAccount(accountFilter: unknown): AccountInfo | null {
|
|
269
|
-
const filter = (accountFilter ?? {}) as Partial<
|
|
270
|
-
Pick<AccountInfo, 'homeAccountId' | 'localAccountId' | 'username' | 'tenantId'>
|
|
271
|
-
>;
|
|
272
|
-
|
|
273
|
-
// Filter the cache so callers observe the same account-selection semantics as MSAL.
|
|
274
|
-
const matches = this.getAllAccounts().filter(
|
|
275
|
-
(account) =>
|
|
276
|
-
(filter.homeAccountId === undefined || filter.homeAccountId === account.homeAccountId) &&
|
|
277
|
-
(filter.localAccountId === undefined || filter.localAccountId === account.localAccountId) &&
|
|
278
|
-
(filter.username === undefined || filter.username === account.username) &&
|
|
279
|
-
(filter.tenantId === undefined || filter.tenantId === account.tenantId),
|
|
280
|
-
);
|
|
281
|
-
|
|
282
|
-
return matches[0] ?? null;
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
/**
|
|
286
|
-
* Mirrors MSAL account enumeration using the mock cache.
|
|
287
|
-
* @param _accountFilter - Account filter, accepted for interface compatibility.
|
|
288
|
-
* @returns All accounts currently in the mock cache.
|
|
289
|
-
*/
|
|
290
|
-
public getAllAccounts(_accountFilter?: unknown): AccountInfo[] {
|
|
291
|
-
return [...this.#cache.values()];
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
/**
|
|
295
|
-
* Mirrors redirect logout without navigating in the mock environment.
|
|
296
|
-
* @param _request - Logout options, accepted for interface compatibility.
|
|
297
|
-
*/
|
|
298
|
-
public async logoutRedirect(_request?: EndSessionRequest): Promise<void> {
|
|
299
|
-
this.#signOut();
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
/**
|
|
303
|
-
* Mirrors popup logout without opening a browser window.
|
|
304
|
-
* @param _request - Logout options, accepted for interface compatibility.
|
|
305
|
-
*/
|
|
306
|
-
public async logoutPopup(_request?: EndSessionPopupRequest): Promise<void> {
|
|
307
|
-
this.#signOut();
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
/**
|
|
311
|
-
* Mirrors MSAL logger access; the mock does not retain a logger.
|
|
312
|
-
* @returns An interface-compatible empty logger value.
|
|
313
|
-
*/
|
|
314
|
-
public getLogger(): Logger {
|
|
315
|
-
// MSAL's Logger has a large internal surface with no mock consumers depend on; callers only pass it through
|
|
316
|
-
return undefined as unknown as Logger;
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
/**
|
|
320
|
-
* Mirrors logger configuration as a no-op for the mock.
|
|
321
|
-
* @param _logger - Logger, accepted for interface compatibility.
|
|
322
|
-
*/
|
|
323
|
-
public setLogger(_logger: unknown): void {
|
|
324
|
-
// No-op for mock
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
/**
|
|
328
|
-
* Mirrors wrapper metadata initialization as a no-op for the mock.
|
|
329
|
-
* @param _sku - Wrapper identifier, accepted for interface compatibility.
|
|
330
|
-
* @param _version - Wrapper version, accepted for interface compatibility.
|
|
331
|
-
*/
|
|
332
|
-
public initializeWrapperLibrary(_sku: WrapperSKU, _version: string): void {
|
|
333
|
-
// No-op for mock wrapper
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
/**
|
|
337
|
-
* Mirrors navigation-client configuration as a no-op because no navigation occurs.
|
|
338
|
-
* @param _navigationClient - Navigation client, accepted for interface compatibility.
|
|
339
|
-
*/
|
|
340
|
-
public setNavigationClient(_navigationClient: INavigationClient): void {
|
|
341
|
-
// No-op for mock
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
/**
|
|
345
|
-
* Mirrors configuration access and rejects it because the mock has no browser config.
|
|
346
|
-
* @returns Never; this mock does not expose browser configuration.
|
|
347
|
-
* @throws Always, because browser configuration is unsupported.
|
|
348
|
-
*/
|
|
349
|
-
public getConfiguration(): BrowserConfiguration {
|
|
350
|
-
throw new Error('MsalMockClient: getConfiguration is not supported in the mock client');
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
/**
|
|
354
|
-
* Mirrors cache hydration as a no-op because mock tokens are created in-process.
|
|
355
|
-
* @param _result - Authentication result, accepted for interface compatibility.
|
|
356
|
-
* @param _request - Original token request, accepted for interface compatibility.
|
|
357
|
-
*/
|
|
358
|
-
public async hydrateCache(
|
|
359
|
-
_result: AuthenticationResult,
|
|
360
|
-
_request: SilentRequest | SsoSilentRequest | RedirectRequest | PopupRequest,
|
|
361
|
-
): Promise<void> {
|
|
362
|
-
// No-op for mock
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
/**
|
|
366
|
-
* Mirrors MSAL cache clearing by removing every mock account.
|
|
367
|
-
* @param _request - Cache-clear options, accepted for interface compatibility.
|
|
368
|
-
*/
|
|
369
|
-
public async clearCache(_request?: ClearCacheRequest): Promise<void> {
|
|
370
|
-
this.#cache.clear();
|
|
371
|
-
this.#activeAccountId = null;
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
/**
|
|
375
|
-
* Mirrors the generic token acquisition entry point for the active mock account.
|
|
376
|
-
* @param options - Token acquisition options.
|
|
377
|
-
* @returns A mock result, or `null` without an active account.
|
|
378
|
-
*/
|
|
379
|
-
public async acquireToken(options: AcquireTokenOptions): Promise<AcquireTokenResult> {
|
|
380
|
-
// Generic acquisition returns no result when no account is active, matching MSAL's nullable result.
|
|
381
|
-
if (!this.#account) {
|
|
382
|
-
return null;
|
|
383
|
-
}
|
|
384
|
-
return this.#createResult(options.request?.scopes);
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
/**
|
|
388
|
-
* Mirrors authorization-code exchange by signing in and returning a mock result.
|
|
389
|
-
* @param request - Authorization-code request.
|
|
390
|
-
* @returns A mock authentication result.
|
|
391
|
-
*/
|
|
392
|
-
public async acquireTokenByCode(
|
|
393
|
-
request: AuthorizationCodeRequest,
|
|
394
|
-
): Promise<AuthenticationResult> {
|
|
395
|
-
this.#signIn(this.#createAccount());
|
|
396
|
-
return this.#createResult((request as { scopes?: string[] })?.scopes);
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
/**
|
|
400
|
-
* Creates a mock client for the services the given configuration points at.
|
|
401
|
-
*
|
|
402
|
-
* @remarks
|
|
403
|
-
* Takes the same argument as {@link MsalClient}. A user named `Test User` is
|
|
404
|
-
* already in the account cache, so a provider built on this client boots the
|
|
405
|
-
* way one does for a returning user with a live session — no sign-in runs, and
|
|
406
|
-
* the provider's start-up path sees the state it would see in production. Use
|
|
407
|
-
* {@link MsalMockClient.setUser | setUser} to say who that user is.
|
|
408
|
-
*
|
|
409
|
-
* @param config - The same client configuration the real client is built from.
|
|
410
|
-
*/
|
|
411
|
-
public constructor(config: MsalClientConfig) {
|
|
412
|
-
const tenantId = config.auth.tenantId ?? MsalMockClient.#tenantFromAuthority(config.auth);
|
|
413
|
-
|
|
414
|
-
this.#user = {
|
|
415
|
-
name: 'Test User',
|
|
416
|
-
username: 'test.user@equinor.com',
|
|
417
|
-
userId: 'fusion-mock-user',
|
|
418
|
-
tenantId: tenantId ?? 'fusion-mock-tenant',
|
|
419
|
-
scopes: ['fusion-mock-scope'],
|
|
420
|
-
clientId: config.auth.clientId,
|
|
421
|
-
};
|
|
422
|
-
|
|
423
|
-
this.#signIn(this.#createAccount());
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
/**
|
|
427
|
-
* Reads the tenant out of an authority URL.
|
|
428
|
-
*
|
|
429
|
-
* @remarks
|
|
430
|
-
* A configuration may carry only `authority`, in which case the tenant still
|
|
431
|
-
* has to end up on the tokens this client mints for the account to look like
|
|
432
|
-
* the one a real sign-in would have produced.
|
|
433
|
-
*
|
|
434
|
-
* @param auth - The auth section of the client configuration.
|
|
435
|
-
* @returns The tenant, or `undefined` when the authority carries none.
|
|
436
|
-
*/
|
|
437
|
-
static #tenantFromAuthority(auth: MsalClientConfig['auth']): string | undefined {
|
|
438
|
-
// An explicit tenant takes precedence; only parse authority when configuration omitted it.
|
|
439
|
-
if (!auth.authority) {
|
|
440
|
-
return undefined;
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
try {
|
|
444
|
-
// Remove empty URL path segments to identify the authority's tenant consistently.
|
|
445
|
-
const segments = new URL(auth.authority).pathname.split('/').filter(Boolean);
|
|
446
|
-
return segments.at(-1);
|
|
447
|
-
} catch {
|
|
448
|
-
return undefined;
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
/**
|
|
453
|
-
* Returns the client identifier used by tokens minted by this mock.
|
|
454
|
-
* @returns The configured client identifier.
|
|
455
|
-
*/
|
|
456
|
-
public get clientId(): string | undefined {
|
|
457
|
-
return this.#user.clientId;
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
/**
|
|
461
|
-
* Returns the tenant identifier used by tokens minted by this mock.
|
|
462
|
-
* @returns The configured tenant identifier.
|
|
463
|
-
*/
|
|
464
|
-
public get tenantId(): string | undefined {
|
|
465
|
-
return this.#user.tenantId;
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
/**
|
|
469
|
-
* Reports whether the mock currently has an active account.
|
|
470
|
-
* @returns Whether an account is active.
|
|
471
|
-
*/
|
|
472
|
-
public get hasValidClaims(): boolean {
|
|
473
|
-
return this.#account !== null;
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
/**
|
|
477
|
-
* Mirrors MSAL active-account access using the mock's single active account.
|
|
478
|
-
* @returns The active account, or `null`.
|
|
479
|
-
*/
|
|
480
|
-
public getActiveAccount(): AccountInfo | null {
|
|
481
|
-
return this.#account;
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
/**
|
|
485
|
-
* Makes an account the active one, adding it to the cache if it is unknown.
|
|
486
|
-
*
|
|
487
|
-
* @remarks
|
|
488
|
-
* Real MSAL requires the account to already be cached. Accepting an unknown
|
|
489
|
-
* one is a deliberate concession to tests: it is the shortest way to swap the
|
|
490
|
-
* signed-in user between runs, without reconstructing the framework.
|
|
491
|
-
*
|
|
492
|
-
* @param next - The account to make active, or `null` to sign out.
|
|
493
|
-
*/
|
|
494
|
-
public setActiveAccount(next: AccountInfo | null): void {
|
|
495
|
-
// A null account is the MSAL sign-out signal, so clear the active mock session.
|
|
496
|
-
if (!next) {
|
|
497
|
-
this.#signOut();
|
|
498
|
-
return;
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
this.#signIn(next);
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
/**
|
|
505
|
-
* Declares who is signed in, replacing whoever was.
|
|
506
|
-
*
|
|
507
|
-
* @remarks
|
|
508
|
-
* This is the counterpart to a real sign-in: the client is configured with
|
|
509
|
-
* what it talks to, and learns the user separately. `MsalMockConfigurator`
|
|
510
|
-
* applies it as the configuration is assembled, so the account is in the cache
|
|
511
|
-
* before `MsalProvider.initialize` runs — the provider then behaves as it does
|
|
512
|
-
* for a returning user with a live session.
|
|
513
|
-
*
|
|
514
|
-
* Values left out keep whatever they were. Passing `null` signs out and
|
|
515
|
-
* forgets the identity, so the provider follows its unauthenticated path;
|
|
516
|
-
* `{ signedOut: true }` does the same but keeps the identity, so a later login
|
|
517
|
-
* resolves as that user.
|
|
518
|
-
*
|
|
519
|
-
* @param user - The user to sign in, or `null` when nobody is.
|
|
520
|
-
*/
|
|
521
|
-
public setUser(user: MsalMockUser | null): void {
|
|
522
|
-
// Declaring a user replaces the session rather than adding to it, so a test
|
|
523
|
-
// that names a second user does not silently end up with two cached accounts
|
|
524
|
-
this.#cache.clear();
|
|
525
|
-
this.#activeAccountId = null;
|
|
526
|
-
|
|
527
|
-
// A null user explicitly clears the session and identity supplied to the mock.
|
|
528
|
-
if (!user) {
|
|
529
|
-
return;
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
const { account, signedOut, ...rest } = user;
|
|
533
|
-
|
|
534
|
-
// Merge overrides while retaining defaults for fields omitted by the test.
|
|
535
|
-
this.#user = {
|
|
536
|
-
...this.#user,
|
|
537
|
-
...rest,
|
|
538
|
-
name: rest.name ?? account?.name ?? this.#user.name,
|
|
539
|
-
username: rest.username ?? account?.username ?? this.#user.username,
|
|
540
|
-
userId: rest.userId ?? account?.localAccountId ?? this.#user.userId,
|
|
541
|
-
tenantId: rest.tenantId ?? account?.tenantId ?? this.#user.tenantId,
|
|
542
|
-
scopes: rest.scopes ?? this.#user.scopes,
|
|
543
|
-
};
|
|
544
|
-
|
|
545
|
-
// Keep identity data without caching an account when the test starts signed out.
|
|
546
|
-
if (signedOut) {
|
|
547
|
-
return;
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
this.#signIn(account ?? this.#createAccount());
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
/**
|
|
554
|
-
* Overrides the token returned by future results, independent of who is signed in.
|
|
555
|
-
*
|
|
556
|
-
* @remarks
|
|
557
|
-
* Use this when a backend mock validates its own tokens (specific claims, an
|
|
558
|
-
* audience, or a signature) — supplying the exact token here means that
|
|
559
|
-
* backend sees the token it issued, rather than a mock-shaped substitute this
|
|
560
|
-
* client would otherwise fabricate from the signed-in user's fields.
|
|
561
|
-
*
|
|
562
|
-
* @param token - The token to return verbatim, or `null` to resume generating one.
|
|
563
|
-
*/
|
|
564
|
-
public setToken(token: string | null): void {
|
|
565
|
-
this.#token = token;
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
/**
|
|
569
|
-
* Creates the one account represented by this mock's configured identity.
|
|
570
|
-
* @returns An MSAL-shaped account for the configured user.
|
|
571
|
-
*/
|
|
572
|
-
#createAccount(): AccountInfo {
|
|
573
|
-
return {
|
|
574
|
-
homeAccountId: `${this.#user.userId}.${this.#user.tenantId}`,
|
|
575
|
-
localAccountId: this.#user.userId,
|
|
576
|
-
environment: 'login.microsoftonline.com',
|
|
577
|
-
tenantId: this.#user.tenantId,
|
|
578
|
-
username: this.#user.username,
|
|
579
|
-
name: this.#user.name,
|
|
580
|
-
} as AccountInfo;
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
/**
|
|
584
|
-
* Creates an MSAL-shaped token result for the requested or default scopes.
|
|
585
|
-
* @param scopes - Requested scopes, or the user's configured defaults.
|
|
586
|
-
* @returns An MSAL-shaped mock authentication result.
|
|
587
|
-
*/
|
|
588
|
-
#createResult(scopes?: string[]): AuthenticationResult {
|
|
589
|
-
const granted = scopes?.length ? scopes : this.#user.scopes;
|
|
590
|
-
// a caller-supplied token is sent verbatim so a backend mock validating it sees what it expects
|
|
591
|
-
const token =
|
|
592
|
-
this.#token ??
|
|
593
|
-
createMockToken({
|
|
594
|
-
name: this.#user.name,
|
|
595
|
-
preferred_username: this.#user.username,
|
|
596
|
-
oid: this.#user.userId,
|
|
597
|
-
tid: this.#user.tenantId,
|
|
598
|
-
aud: this.#user.clientId,
|
|
599
|
-
scp: granted.join(' '),
|
|
600
|
-
});
|
|
601
|
-
|
|
602
|
-
// Object shape matches AuthenticationResult's fields consumers rely on; the real
|
|
603
|
-
// type also carries browser-only fields (e.g. `familyId`) this mock intentionally omits
|
|
604
|
-
return {
|
|
605
|
-
account: this.#account ?? this.#createAccount(),
|
|
606
|
-
accessToken: token,
|
|
607
|
-
idToken: token,
|
|
608
|
-
scopes: granted,
|
|
609
|
-
tokenType: 'Bearer',
|
|
610
|
-
expiresOn: new Date('2033-11-14T22:13:20.000Z'),
|
|
611
|
-
authority: `https://login.microsoftonline.com/${this.#user.tenantId}`,
|
|
612
|
-
uniqueId: this.#user.userId,
|
|
613
|
-
tenantId: this.#user.tenantId,
|
|
614
|
-
fromCache: false,
|
|
615
|
-
correlationId: 'fusion-mock-correlation',
|
|
616
|
-
} as unknown as AuthenticationResult;
|
|
617
|
-
}
|
|
618
|
-
}
|