@equinor/fusion-framework-module-msal 11.0.0-next.0 → 11.0.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.
Files changed (32) hide show
  1. package/CHANGELOG.md +67 -120
  2. package/dist/esm/__tests__/mock/create-mock-user-from-token.test.js +39 -0
  3. package/dist/esm/__tests__/mock/create-mock-user-from-token.test.js.map +1 -0
  4. package/dist/esm/__tests__/mock/msal-mock.test.js +53 -1
  5. package/dist/esm/__tests__/mock/msal-mock.test.js.map +1 -1
  6. package/dist/esm/mock/MsalMockClient.js +25 -8
  7. package/dist/esm/mock/MsalMockClient.js.map +1 -1
  8. package/dist/esm/mock/MsalMockConfigurator.js +63 -11
  9. package/dist/esm/mock/MsalMockConfigurator.js.map +1 -1
  10. package/dist/esm/mock/create-mock-user-from-token.js +40 -0
  11. package/dist/esm/mock/create-mock-user-from-token.js.map +1 -0
  12. package/dist/esm/mock/index.js +1 -0
  13. package/dist/esm/mock/index.js.map +1 -1
  14. package/dist/esm/version.js +1 -1
  15. package/dist/esm/version.js.map +1 -1
  16. package/dist/tsconfig.tsbuildinfo +1 -1
  17. package/dist/types/__tests__/mock/create-mock-user-from-token.test.d.ts +1 -0
  18. package/dist/types/mock/MsalMockClient.d.ts +12 -0
  19. package/dist/types/mock/MsalMockConfigurator.d.ts +30 -0
  20. package/dist/types/mock/create-mock-user-from-token.d.ts +25 -0
  21. package/dist/types/mock/index.d.ts +1 -0
  22. package/dist/types/version.d.ts +1 -1
  23. package/docs/testing.md +24 -0
  24. package/package.json +5 -5
  25. package/src/__tests__/mock/create-mock-user-from-token.test.ts +46 -0
  26. package/src/__tests__/mock/msal-mock.test.ts +69 -0
  27. package/src/mock/MsalMockClient.ts +27 -8
  28. package/src/mock/MsalMockConfigurator.ts +78 -14
  29. package/src/mock/create-mock-user-from-token.ts +46 -0
  30. package/src/mock/index.ts +1 -0
  31. package/src/version.ts +1 -1
  32. package/.changeset/msal-auth-provider-fix.md +0 -7
@@ -267,4 +267,16 @@ export declare class MsalMockClient implements IMsalClient {
267
267
  * @param user - The user to sign in, or `null` when nobody is.
268
268
  */
269
269
  setUser(user: MsalMockUser | null): void;
270
+ /**
271
+ * Overrides the token returned by future results, independent of who is signed in.
272
+ *
273
+ * @remarks
274
+ * Use this when a backend mock validates its own tokens (specific claims, an
275
+ * audience, or a signature) — supplying the exact token here means that
276
+ * backend sees the token it issued, rather than a mock-shaped substitute this
277
+ * client would otherwise fabricate from the signed-in user's fields.
278
+ *
279
+ * @param token - The token to return verbatim, or `null` to resume generating one.
280
+ */
281
+ setToken(token: string | null): void;
270
282
  }
@@ -23,6 +23,11 @@ declare module '../msal-config-schema' {
23
23
  * `null` when nobody is signed in.
24
24
  */
25
25
  account?: MsalMockUser | null;
26
+ /**
27
+ * The token to return verbatim instead of one generated from the
28
+ * signed-in user's fields.
29
+ */
30
+ token?: string;
26
31
  };
27
32
  }
28
33
  }
@@ -105,6 +110,31 @@ export declare class MsalMockConfigurator extends MsalConfigurator {
105
110
  * ```
106
111
  */
107
112
  setAccount(account: MsalMockUser | null | ConfigBuilderCallback<MsalMockUser | null>): this;
113
+ /**
114
+ * Declares the token to return, independent of who is signed in.
115
+ *
116
+ * @remarks
117
+ * Use this when a backend mock validates its own tokens (specific claims, an
118
+ * audience, or a signature) — the client then returns this token verbatim
119
+ * instead of fabricating one from the signed-in user's fields.
120
+ *
121
+ * @param token - A JWT (e.g. from `createMockToken`, or issued by an external mock).
122
+ * @param skipResolve - When `true`, override only the token and leave an account
123
+ * declared through {@link setAccount} untouched. Defaults to `false`, which also signs
124
+ * in the user described by the token's claims, via {@link createMockUserFromToken}.
125
+ * @returns The builder, for chaining.
126
+ *
127
+ * @example Sign in as whoever the token names
128
+ * ```typescript
129
+ * builder.setToken(token);
130
+ * ```
131
+ *
132
+ * @example Keep a separately declared account, but return this exact token
133
+ * ```typescript
134
+ * builder.setAccount({ name: 'Ada Lovelace' }).setToken(token, true);
135
+ * ```
136
+ */
137
+ setToken(token: string, skipResolve?: boolean): this;
108
138
  /**
109
139
  * Assembles the configuration, then signs the declared user in.
110
140
  *
@@ -0,0 +1,25 @@
1
+ import type { MsalMockUser } from './MsalMockClient';
2
+ /**
3
+ * Derives a {@link MsalMockUser} from a JWT's payload claims, so a token minted
4
+ * outside this module (e.g. by a backend's own mock) can drive who the mock
5
+ * signs in as.
6
+ *
7
+ * @remarks
8
+ * Maps the standard Entra ID claims Fusion applications read — `name`,
9
+ * `preferred_username`, `oid`, `tid`, `scp` — onto the matching
10
+ * {@link MsalMockUser} fields. Identity only: it does not affect which token
11
+ * the client returns — use {@link MsalMockConfigurator.setToken} for that.
12
+ *
13
+ * @param token - A JWT (e.g. from {@link createMockToken}, or issued by an
14
+ * external mock) with a base64url-encoded payload segment.
15
+ * @returns A mock user built from the token's claims.
16
+ * @throws When the token has no payload segment (`header.payload.signature`).
17
+ *
18
+ * @example
19
+ * ```typescript
20
+ * enableMsalMock(configurator, (builder) => {
21
+ * builder.setAccount(createMockUserFromToken(token));
22
+ * });
23
+ * ```
24
+ */
25
+ export declare const createMockUserFromToken: (token: string) => MsalMockUser;
@@ -26,4 +26,5 @@ export { createMsalMockClient } from './create-msal-mock-client';
26
26
  export { MsalMockConfigurator } from './MsalMockConfigurator';
27
27
  export { enableMsalMock, msalMockModule, type AuthConfigMockFn } from './module';
28
28
  export { createMockToken, type MockTokenClaims } from './create-mock-token';
29
+ export { createMockUserFromToken } from './create-mock-user-from-token';
29
30
  export { decodeJwtSegment } from './decode-jwt-segment';
@@ -1 +1 @@
1
- export declare const version = "11.0.0-next.0";
1
+ export declare const version = "11.0.0";
package/docs/testing.md CHANGED
@@ -69,6 +69,24 @@ The user is in place **before** `MsalProvider.initialize()` runs, so the provide
69
69
 
70
70
  `setClient` replaces the client, but not the rule: the declared user is signed in on whichever client the module authenticates through, so a mock client supplied that way receives it too.
71
71
 
72
+ ## Returning an exact token
73
+
74
+ Most tests only care who is signed in and let the mock fabricate a token from that user's fields. When a backend mock validates the token itself — specific claims, an audience, or a signature — it needs to see the exact token it expects instead:
75
+
76
+ ```typescript
77
+ enableMsalMock(configurator, (builder) => {
78
+ builder.setToken(token);
79
+ });
80
+ ```
81
+
82
+ `setToken` also signs in the user the token's claims describe, via `createMockUserFromToken` — so `acquireAccessToken` returns this token, and the account APIs agree with it. Pass `true` as the second argument to keep a separately declared account instead:
83
+
84
+ ```typescript
85
+ enableMsalMock(configurator, (builder) => {
86
+ builder.setAccount({ name: 'Ada Lovelace' }).setToken(token, true);
87
+ });
88
+ ```
89
+
72
90
  | Option | Default | Purpose |
73
91
  | --- | --- | --- |
74
92
  | `name` | `Test User` | Display name |
@@ -150,6 +168,12 @@ import { createMockToken } from '@equinor/fusion-framework-module-msal/mock';
150
168
  const token = createMockToken({ oid: 'fusion-mock-user' });
151
169
  ```
152
170
 
171
+ To use a generated token as the signed-in user for `ffc app dev --mock`, `ffc app serve --mock`,
172
+ or a Vite SPA, see
173
+ [Generate a mock user and update `.env`](../../../vite-plugins/spa/README.md#generate-a-mock-user-and-update-env).
174
+ That workflow documents persistent mock-auth configuration, identity claims, and custom `scp`
175
+ token scopes.
176
+
153
177
  ## Exports
154
178
 
155
179
  | Export | Purpose |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@equinor/fusion-framework-module-msal",
3
- "version": "11.0.0-next.0",
3
+ "version": "11.0.0",
4
4
  "description": "Microsoft Authentication Library (MSAL) integration module for Fusion Framework",
5
5
  "main": "dist/esm/index.js",
6
6
  "types": "dist/types/index.d.ts",
@@ -57,16 +57,16 @@
57
57
  "semver": "^7.7.4",
58
58
  "typescript": "^7.0.2",
59
59
  "zod": "^4.4.3",
60
- "@equinor/fusion-framework-module": "^6.1.3-next.0",
61
- "@equinor/fusion-framework-module-telemetry": "^8.0.0-next.0"
60
+ "@equinor/fusion-framework-module": "^6.1.3",
61
+ "@equinor/fusion-framework-module-telemetry": "^7.1.0"
62
62
  },
63
63
  "peerDependencies": {
64
64
  "@types/semver": "^7.0.0",
65
65
  "semver": "^7.0.0",
66
66
  "typescript": ">=5.0.0",
67
67
  "zod": "^4.0.0",
68
- "@equinor/fusion-framework-module": "^6.1.3-next.0",
69
- "@equinor/fusion-framework-module-telemetry": "^8.0.0-next.0"
68
+ "@equinor/fusion-framework-module": "^6.1.3",
69
+ "@equinor/fusion-framework-module-telemetry": "^7.1.0"
70
70
  },
71
71
  "peerDependenciesMeta": {
72
72
  "@equinor/fusion-framework-module-telemetry": {
@@ -0,0 +1,46 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { createMockToken } from '../../mock/create-mock-token';
4
+ import { createMockUserFromToken } from '../../mock/create-mock-user-from-token';
5
+
6
+ describe('createMockUserFromToken', () => {
7
+ it('maps identity claims onto the matching MsalMockUser fields', () => {
8
+ const token = createMockToken({
9
+ name: 'Ada Lovelace',
10
+ preferred_username: 'ada@equinor.com',
11
+ oid: 'ada-object-id',
12
+ tid: 'ada-tenant-id',
13
+ scp: 'User.Read Files.Read',
14
+ });
15
+
16
+ expect(createMockUserFromToken(token)).toEqual({
17
+ name: 'Ada Lovelace',
18
+ username: 'ada@equinor.com',
19
+ userId: 'ada-object-id',
20
+ tenantId: 'ada-tenant-id',
21
+ scopes: ['User.Read', 'Files.Read'],
22
+ });
23
+ });
24
+
25
+ it('leaves a field undefined rather than fabricating one, when a claim is absent', () => {
26
+ const token = createMockToken({
27
+ name: undefined,
28
+ preferred_username: undefined,
29
+ scp: undefined,
30
+ });
31
+
32
+ const user = createMockUserFromToken(token);
33
+
34
+ expect(user.name).toBeUndefined();
35
+ expect(user.username).toBeUndefined();
36
+ expect(user.scopes).toBeUndefined();
37
+ });
38
+
39
+ it('throws for a token with no payload segment', () => {
40
+ expect(() => createMockUserFromToken('not-a-jwt')).toThrow(/payload segment/);
41
+ });
42
+
43
+ it('throws for an empty string', () => {
44
+ expect(() => createMockUserFromToken('')).toThrow(/payload segment/);
45
+ });
46
+ });
@@ -12,6 +12,7 @@ import type { IMsalProvider } from '../../MsalProvider.interface';
12
12
  import { enableMSAL, module as realModule } from '../../module';
13
13
  import {
14
14
  MsalMockClient,
15
+ createMockToken,
15
16
  createMsalMockClient,
16
17
  enableMsalMock,
17
18
  msalMockModule,
@@ -425,6 +426,74 @@ describe('MsalMockClient', () => {
425
426
 
426
427
  expect(result?.accessToken).toBe('mock-token');
427
428
  });
429
+
430
+ it('returns a token set directly on the client verbatim', async () => {
431
+ const client = new MsalMockClient(clientConfig());
432
+ const token = createMockToken({ name: 'Direct Token' });
433
+
434
+ client.setToken(token);
435
+ const result = await client.acquireToken({ request: { scopes: ['X'] } });
436
+
437
+ expect(result?.accessToken).toBe(token);
438
+ expect(result?.idToken).toBe(token);
439
+ });
440
+
441
+ it('resumes generating tokens once setToken(null) clears the override', async () => {
442
+ const client = new MsalMockClient(clientConfig());
443
+ const token = createMockToken({ name: 'Direct Token' });
444
+ client.setToken(token);
445
+
446
+ client.setToken(null);
447
+ const result = await client.acquireToken({ request: { scopes: ['X'] } });
448
+
449
+ expect(result?.accessToken).not.toBe(token);
450
+ });
451
+ });
452
+
453
+ describe('MsalMockConfigurator.setToken', () => {
454
+ it('returns the exact token instead of one generated from the account', async () => {
455
+ const token = createMockToken({ name: 'Token User' });
456
+ const provider = await initializeMockWith((builder) => builder.setToken(token));
457
+
458
+ const result = await provider.client.acquireToken({ request: { scopes: ['X'] } });
459
+
460
+ expect(result?.accessToken).toBe(token);
461
+ expect(result?.idToken).toBe(token);
462
+ });
463
+
464
+ it('signs in the account named by the token claims by default', async () => {
465
+ const token = createMockToken({
466
+ name: 'Token User',
467
+ preferred_username: 'token.user@equinor.com',
468
+ });
469
+ const provider = await initializeMockWith((builder) => builder.setToken(token));
470
+
471
+ expect(provider.account?.name).toBe('Token User');
472
+ expect(provider.account?.username).toBe('token.user@equinor.com');
473
+ });
474
+
475
+ it('keeps a separately declared account when skipResolve is true', async () => {
476
+ // skipResolve overrides only the returned token, leaving setAccount's declaration alone
477
+ const token = createMockToken({ name: 'Token User' });
478
+ const provider = await initializeMockWith((builder) => {
479
+ builder.setAccount({ name: 'Ada Lovelace' });
480
+ builder.setToken(token, true);
481
+ });
482
+
483
+ expect(provider.account?.name).toBe('Ada Lovelace');
484
+ const result = await provider.client.acquireToken({ request: { scopes: ['X'] } });
485
+ expect(result?.accessToken).toBe(token);
486
+ });
487
+
488
+ it('resumes generating tokens once setToken(null) is called on the client', async () => {
489
+ const token = createMockToken({ name: 'Token User' });
490
+ const provider = await initializeMockWith((builder) => builder.setToken(token));
491
+
492
+ (provider.client as MsalMockClient).setToken(null);
493
+ const result = await provider.client.acquireToken({ request: { scopes: ['X'] } });
494
+
495
+ expect(result?.accessToken).not.toBe(token);
496
+ });
428
497
  });
429
498
 
430
499
  describe('setAccount(null)', () => {
@@ -88,6 +88,7 @@ export class MsalMockClient implements IMsalClient {
88
88
  };
89
89
  #cache = new Map<string, AccountInfo>();
90
90
  #activeAccountId: string | null = null;
91
+ #token: string | null = null;
91
92
 
92
93
  /**
93
94
  * The account currently signed in, or `null`.
@@ -549,6 +550,21 @@ export class MsalMockClient implements IMsalClient {
549
550
  this.#signIn(account ?? this.#createAccount());
550
551
  }
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
+
552
568
  /**
553
569
  * Creates the one account represented by this mock's configured identity.
554
570
  * @returns An MSAL-shaped account for the configured user.
@@ -571,14 +587,17 @@ export class MsalMockClient implements IMsalClient {
571
587
  */
572
588
  #createResult(scopes?: string[]): AuthenticationResult {
573
589
  const granted = scopes?.length ? scopes : this.#user.scopes;
574
- const token = createMockToken({
575
- name: this.#user.name,
576
- preferred_username: this.#user.username,
577
- oid: this.#user.userId,
578
- tid: this.#user.tenantId,
579
- aud: this.#user.clientId,
580
- scp: granted.join(' '),
581
- });
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
+ });
582
601
 
583
602
  // Object shape matches AuthenticationResult's fields consumers rely on; the real
584
603
  // type also carries browser-only fields (e.g. `familyId`) this mock intentionally omits
@@ -9,6 +9,7 @@ import type { MsalClientConfig } from '../MsalClient';
9
9
  import { MsalConfigurator, type MsalConfig } from '../MsalConfigurator';
10
10
 
11
11
  import { MsalMockClient, type MsalMockUser } from './MsalMockClient';
12
+ import { createMockUserFromToken } from './create-mock-user-from-token';
12
13
 
13
14
  /**
14
15
  * Declares the mock's own branch of the MSAL configuration.
@@ -31,6 +32,11 @@ declare module '../msal-config-schema' {
31
32
  * `null` when nobody is signed in.
32
33
  */
33
34
  account?: MsalMockUser | null;
35
+ /**
36
+ * The token to return verbatim instead of one generated from the
37
+ * signed-in user's fields.
38
+ */
39
+ token?: string;
34
40
  };
35
41
  }
36
42
  }
@@ -135,36 +141,86 @@ export class MsalMockConfigurator extends MsalConfigurator {
135
141
  }
136
142
 
137
143
  /**
138
- * Signs the declared user in on the client the module authenticates through.
144
+ * Declares the token to return, independent of who is signed in.
139
145
  *
140
146
  * @remarks
141
- * Deliberately not done while the client is built: that would assume the scope
142
- * declaring the user is the scope building the client, which is exactly what
143
- * is not true when an application is tested inside a portal. The host built
144
- * that client, in a scope this builder never sees, so the client has to be
145
- * located rather than assumed.
147
+ * Use this when a backend mock validates its own tokens (specific claims, an
148
+ * audience, or a signature) the client then returns this token verbatim
149
+ * instead of fabricating one from the signed-in user's fields.
150
+ *
151
+ * @param token - A JWT (e.g. from `createMockToken`, or issued by an external mock).
152
+ * @param skipResolve - When `true`, override only the token and leave an account
153
+ * declared through {@link setAccount} untouched. Defaults to `false`, which also signs
154
+ * in the user described by the token's claims, via {@link createMockUserFromToken}.
155
+ * @returns The builder, for chaining.
156
+ *
157
+ * @example Sign in as whoever the token names
158
+ * ```typescript
159
+ * builder.setToken(token);
160
+ * ```
161
+ *
162
+ * @example Keep a separately declared account, but return this exact token
163
+ * ```typescript
164
+ * builder.setAccount({ name: 'Ada Lovelace' }).setToken(token, true);
165
+ * ```
166
+ */
167
+ public setToken(token: string, skipResolve = false): this {
168
+ this._set('mock.token', token);
169
+ // skipResolve defaults to false - most callers want the token's claims to name who is signed in
170
+ if (!skipResolve) {
171
+ this.setAccount(createMockUserFromToken(token));
172
+ }
173
+ return this;
174
+ }
175
+
176
+ /**
177
+ * Resolves the client the module authenticates through, wherever it was built.
178
+ *
179
+ * @remarks
180
+ * Shared by {@link setAccount} and {@link setToken} application: neither can
181
+ * assume the scope declaring mock state is the scope that built the client,
182
+ * which is exactly what is not true when an application is tested inside a
183
+ * portal. The host built that client, in a scope this builder never sees, so
184
+ * the client has to be located rather than assumed.
146
185
  *
147
- * @param account - The user to sign in, or `null` when nobody is.
148
186
  * @param config - The validated configuration, carrying the client when one was built.
149
187
  * @param init - The builder arguments, carrying the host reference when hoisted.
188
+ * @param action - Describes what could not be applied, for the thrown error.
189
+ * @returns The resolved mock client.
150
190
  * @throws When the resolved client is not a {@link MsalMockClient}.
151
191
  */
152
- #signIn(
153
- account: MsalMockUser | null,
192
+ #getClient(
154
193
  config: MsalConfig,
155
- init?: ConfigBuilderCallbackArgs,
156
- ): void {
194
+ init: ConfigBuilderCallbackArgs | undefined,
195
+ action: string,
196
+ ): MsalMockClient {
157
197
  const host = (init?.ref as { auth?: IMsalProvider } | undefined)?.auth;
158
198
  const client = config.client ?? host?.client;
159
199
 
160
- // Reject a real client because mock account state cannot be applied to it.
200
+ // Reject a real client because mock state cannot be applied to it.
161
201
  if (!(client instanceof MsalMockClient)) {
162
202
  throw new Error(
163
- 'MsalMockConfigurator: cannot sign a user in, because this module does not authenticate through a mock client. Declare the user where that client is configured instead.',
203
+ `MsalMockConfigurator: cannot ${action}, because this module does not authenticate through a mock client. Declare it where that client is configured instead.`,
164
204
  );
165
205
  }
166
206
 
167
- client.setUser(account);
207
+ return client;
208
+ }
209
+
210
+ /**
211
+ * Signs the declared user in on the client the module authenticates through.
212
+ *
213
+ * @param account - The user to sign in, or `null` when nobody is.
214
+ * @param config - The validated configuration, carrying the client when one was built.
215
+ * @param init - The builder arguments, carrying the host reference when hoisted.
216
+ * @throws When the resolved client is not a {@link MsalMockClient}.
217
+ */
218
+ #signIn(
219
+ account: MsalMockUser | null,
220
+ config: MsalConfig,
221
+ init?: ConfigBuilderCallbackArgs,
222
+ ): void {
223
+ this.#getClient(config, init, 'sign a user in').setUser(account);
168
224
  }
169
225
 
170
226
  /**
@@ -211,6 +267,14 @@ export class MsalMockConfigurator extends MsalConfigurator {
211
267
  this.#signIn(account, config, init);
212
268
  }
213
269
 
270
+ // Applied after the account so a token declared alongside `skipResolve: true`
271
+ // overrides whatever `setUser` above just fabricated.
272
+ const token = rawConfig.mock?.token;
273
+ // absent means the test declared no token override; leave the client generating its own
274
+ if (token !== undefined) {
275
+ this.#getClient(config, init, 'set a token').setToken(token);
276
+ }
277
+
214
278
  return config;
215
279
  }
216
280
 
@@ -0,0 +1,46 @@
1
+ import type { MsalMockUser } from './MsalMockClient';
2
+ import { decodeJwtSegment } from './decode-jwt-segment';
3
+ import type { MockTokenClaims } from './create-mock-token';
4
+
5
+ /**
6
+ * Derives a {@link MsalMockUser} from a JWT's payload claims, so a token minted
7
+ * outside this module (e.g. by a backend's own mock) can drive who the mock
8
+ * signs in as.
9
+ *
10
+ * @remarks
11
+ * Maps the standard Entra ID claims Fusion applications read — `name`,
12
+ * `preferred_username`, `oid`, `tid`, `scp` — onto the matching
13
+ * {@link MsalMockUser} fields. Identity only: it does not affect which token
14
+ * the client returns — use {@link MsalMockConfigurator.setToken} for that.
15
+ *
16
+ * @param token - A JWT (e.g. from {@link createMockToken}, or issued by an
17
+ * external mock) with a base64url-encoded payload segment.
18
+ * @returns A mock user built from the token's claims.
19
+ * @throws When the token has no payload segment (`header.payload.signature`).
20
+ *
21
+ * @example
22
+ * ```typescript
23
+ * enableMsalMock(configurator, (builder) => {
24
+ * builder.setAccount(createMockUserFromToken(token));
25
+ * });
26
+ * ```
27
+ */
28
+ export const createMockUserFromToken = (token: string): MsalMockUser => {
29
+ const [, payload] = token.split('.');
30
+ // fail loudly rather than signing in an empty/garbage user from a malformed token
31
+ if (!payload) {
32
+ throw new Error(
33
+ 'createMockUserFromToken: expected a JWT with a payload segment (header.payload.signature)',
34
+ );
35
+ }
36
+
37
+ const claims: MockTokenClaims = JSON.parse(decodeJwtSegment(payload));
38
+
39
+ return {
40
+ name: claims.name,
41
+ username: claims.preferred_username,
42
+ userId: claims.oid,
43
+ tenantId: claims.tid,
44
+ scopes: claims.scp?.split(' '),
45
+ };
46
+ };
package/src/mock/index.ts CHANGED
@@ -26,4 +26,5 @@ export { createMsalMockClient } from './create-msal-mock-client';
26
26
  export { MsalMockConfigurator } from './MsalMockConfigurator';
27
27
  export { enableMsalMock, msalMockModule, type AuthConfigMockFn } from './module';
28
28
  export { createMockToken, type MockTokenClaims } from './create-mock-token';
29
+ export { createMockUserFromToken } from './create-mock-user-from-token';
29
30
  export { decodeJwtSegment } from './decode-jwt-segment';
package/src/version.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  // Generated by genversion.
2
- export const version = '11.0.0-next.0';
2
+ export const version = '11.0.0';
@@ -1,7 +0,0 @@
1
- ---
2
- "@equinor/fusion-framework-module-msal": patch
3
- ---
4
-
5
- Fixed AuthProvider to properly extend BaseModuleProvider, eliminating module initialization warnings about provider inheritance.
6
-
7
- The AuthProvider class now inherits the standard version property and dispose method from BaseModuleProvider, ensuring proper integration with the Fusion Framework's module system.