@equinor/fusion-framework-module-msal 7.2.1 → 7.3.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.
@@ -76,7 +76,7 @@ export declare class MsalConfigurator extends BaseConfigBuilder<MsalConfig> {
76
76
  * This follows Microsoft's standard SPA Auth Code Flow pattern and is compatible with
77
77
  * MSAL Browser's acquireTokenByCode() method.
78
78
  *
79
- * @param authCode - The authorization code issued by the backend
79
+ * @param authCode - The authorization code issued by the backend, or undefined to clear/reset it
80
80
  * @returns The configurator instance for method chaining
81
81
  *
82
82
  * @example
@@ -84,15 +84,19 @@ export declare class MsalConfigurator extends BaseConfigBuilder<MsalConfig> {
84
84
  * // Backend provides auth code in HTML/config
85
85
  * const config = { auth: { code: getAuthCodeFromBackend() } };
86
86
  * configurator.setAuthCode(config.auth.code);
87
+ *
88
+ * // Clear previously configured auth code
89
+ * configurator.setAuthCode(undefined);
87
90
  * ```
88
91
  *
89
92
  * @remarks
90
93
  * - Auth codes are single-use and short-lived (typically 5-10 minutes)
91
94
  * - The exchange happens during module initialization before requiresAuth check
92
95
  * - If exchange fails, the provider falls back to standard MSAL authentication flows
96
+ * - Passing undefined, empty, or whitespace-only values clears the configured auth code
93
97
  * - Requires backend to be configured with SPA Auth Code support
94
98
  */
95
- setAuthCode(authCode: string): this;
99
+ setAuthCode(authCode?: string): this;
96
100
  /**
97
101
  * Sets whether authentication is required for the application.
98
102
  *
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -1,7 +1,6 @@
1
- import { type Module, type IModulesConfigurator } from '@equinor/fusion-framework-module';
1
+ import { type Module, type IModulesConfigurator, type ModuleConfigType } from '@equinor/fusion-framework-module';
2
2
  import { MsalConfigurator } from './MsalConfigurator';
3
3
  import { type IMsalProvider } from './MsalProvider';
4
- import type { MsalClientConfig } from './MsalClient';
5
4
  /**
6
5
  * MSAL authentication module configuration.
7
6
  *
@@ -28,23 +27,7 @@ export declare const module: MsalModule;
28
27
  * This function receives a builder object with methods to configure the MSAL client
29
28
  * and authentication requirements.
30
29
  */
31
- export type AuthConfigFn = (builder: {
32
- /**
33
- * Set MSAL client configuration
34
- * @param config - Client configuration with tenant ID, client ID, etc.
35
- */
36
- setClientConfig: (config: MsalClientConfig) => void;
37
- /**
38
- * Set whether authentication is required for the application
39
- * @param requiresAuth - If true, app will attempt automatic login on initialization
40
- */
41
- setRequiresAuth: (requiresAuth: boolean) => void;
42
- /**
43
- * Set a default login hint used for silent SSO and pre-filled usernames
44
- * @param loginHint - Preferred username/email to use for login hint
45
- */
46
- setLoginHint: (loginHint: string) => void;
47
- }) => void;
30
+ export type AuthConfigFn<TRef = unknown> = (configurator: ModuleConfigType<MsalModule>, ref?: TRef) => void;
48
31
  /**
49
32
  * Enables MSAL authentication module in the framework.
50
33
  *
@@ -81,7 +64,7 @@ export declare const enableMSAL: (configurator: IModulesConfigurator<any, any>,
81
64
  */
82
65
  export declare const configureMsal: (configure: AuthConfigFn) => {
83
66
  module: MsalModule;
84
- configure: AuthConfigFn;
67
+ configure: AuthConfigFn<unknown>;
85
68
  };
86
69
  declare module '@equinor/fusion-framework-module' {
87
70
  interface Modules {
@@ -1 +1 @@
1
- export declare const version = "7.2.1";
1
+ export declare const version = "7.3.0";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@equinor/fusion-framework-module-msal",
3
- "version": "7.2.1",
3
+ "version": "7.3.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",
@@ -58,8 +58,8 @@
58
58
  "semver": "^7.5.4",
59
59
  "typescript": "^5.8.2",
60
60
  "zod": "^4.1.8",
61
- "@equinor/fusion-framework-module-telemetry": "^4.6.3",
62
- "@equinor/fusion-framework-module": "^5.0.5"
61
+ "@equinor/fusion-framework-module": "^5.0.5",
62
+ "@equinor/fusion-framework-module-telemetry": "^4.6.3"
63
63
  },
64
64
  "peerDependenciesMeta": {
65
65
  "@equinor/fusion-framework-module-telemetry": {
@@ -136,7 +136,7 @@ export class MsalConfigurator extends BaseConfigBuilder<MsalConfig> {
136
136
  * This follows Microsoft's standard SPA Auth Code Flow pattern and is compatible with
137
137
  * MSAL Browser's acquireTokenByCode() method.
138
138
  *
139
- * @param authCode - The authorization code issued by the backend
139
+ * @param authCode - The authorization code issued by the backend, or undefined to clear/reset it
140
140
  * @returns The configurator instance for method chaining
141
141
  *
142
142
  * @example
@@ -144,16 +144,21 @@ export class MsalConfigurator extends BaseConfigBuilder<MsalConfig> {
144
144
  * // Backend provides auth code in HTML/config
145
145
  * const config = { auth: { code: getAuthCodeFromBackend() } };
146
146
  * configurator.setAuthCode(config.auth.code);
147
+ *
148
+ * // Clear previously configured auth code
149
+ * configurator.setAuthCode(undefined);
147
150
  * ```
148
151
  *
149
152
  * @remarks
150
153
  * - Auth codes are single-use and short-lived (typically 5-10 minutes)
151
154
  * - The exchange happens during module initialization before requiresAuth check
152
155
  * - If exchange fails, the provider falls back to standard MSAL authentication flows
156
+ * - Passing undefined, empty, or whitespace-only values clears the configured auth code
153
157
  * - Requires backend to be configured with SPA Auth Code support
154
158
  */
155
- setAuthCode(authCode: string): this {
156
- this._set('authCode', async () => authCode);
159
+ setAuthCode(authCode?: string): this {
160
+ const normalizedAuthCode = authCode?.trim() || undefined;
161
+ this._set('authCode', async () => normalizedAuthCode);
157
162
  return this;
158
163
  }
159
164
 
@@ -142,7 +142,7 @@ export class MsalProvider extends BaseModuleProvider<MsalConfig> implements IMsa
142
142
 
143
143
  // Extract auth code from config if present
144
144
  // This will be used during initialize to exchange for tokens
145
- this.#authCode = config.authCode;
145
+ this.#authCode = config.authCode?.trim() || undefined;
146
146
 
147
147
  // Validate required client configuration
148
148
  if (!config.client) {
@@ -0,0 +1,64 @@
1
+ import type { ConfigBuilderCallbackArgs } from '@equinor/fusion-framework-module';
2
+ import { describe, expect, it, vi } from 'vitest';
3
+ import type { IMsalClient } from '../MsalClient.interface';
4
+ import { type MsalConfig, MsalConfigurator } from '../MsalConfigurator';
5
+
6
+ const createConfigCallbackArgs = (): ConfigBuilderCallbackArgs => ({
7
+ config: {},
8
+ hasModule: vi.fn().mockReturnValue(false),
9
+ requireInstance: vi.fn(),
10
+ });
11
+
12
+ const createClient = (): IMsalClient => ({}) as IMsalClient;
13
+
14
+ const createInitialConfig = (): Pick<MsalConfig, 'telemetry'> => ({
15
+ telemetry: {
16
+ metadata: {},
17
+ scope: [],
18
+ },
19
+ });
20
+
21
+ describe('MsalConfigurator', () => {
22
+ it('setAuthCode should normalize surrounding whitespace', async () => {
23
+ const configurator = new MsalConfigurator();
24
+
25
+ configurator.setClient(createClient());
26
+ configurator.setAuthCode(' auth-code ');
27
+
28
+ const config = await configurator.createConfigAsync(
29
+ createConfigCallbackArgs(),
30
+ createInitialConfig(),
31
+ );
32
+
33
+ expect(config.authCode).toBe('auth-code');
34
+ });
35
+
36
+ it('setAuthCode should allow clearing with undefined', async () => {
37
+ const configurator = new MsalConfigurator();
38
+
39
+ configurator.setClient(createClient());
40
+ configurator.setAuthCode('auth-code');
41
+ configurator.setAuthCode(undefined);
42
+
43
+ const config = await configurator.createConfigAsync(
44
+ createConfigCallbackArgs(),
45
+ createInitialConfig(),
46
+ );
47
+
48
+ expect(config.authCode).toBeUndefined();
49
+ });
50
+
51
+ it('setAuthCode should treat whitespace-only values as undefined', async () => {
52
+ const configurator = new MsalConfigurator();
53
+
54
+ configurator.setClient(createClient());
55
+ configurator.setAuthCode(' ');
56
+
57
+ const config = await configurator.createConfigAsync(
58
+ createConfigCallbackArgs(),
59
+ createInitialConfig(),
60
+ );
61
+
62
+ expect(config.authCode).toBeUndefined();
63
+ });
64
+ });
@@ -0,0 +1,74 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+ import type { IMsalClient } from '../MsalClient.interface';
3
+ import type { MsalConfig } from '../MsalConfigurator';
4
+ import { MsalProvider } from '../MsalProvider';
5
+ import type { AuthenticationResult } from '../types';
6
+
7
+ type MockMsalClient = {
8
+ client: IMsalClient;
9
+ acquireTokenByCode: ReturnType<typeof vi.fn>;
10
+ initialize: ReturnType<typeof vi.fn>;
11
+ };
12
+
13
+ const createClient = (): MockMsalClient => {
14
+ const initialize = vi.fn(async () => undefined);
15
+ const acquireTokenByCode = vi.fn(async () => ({}) as AuthenticationResult);
16
+
17
+ return {
18
+ client: {
19
+ clientId: 'test-client-id',
20
+ initialize,
21
+ acquireTokenByCode,
22
+ setActiveAccount: vi.fn(),
23
+ } as unknown as IMsalClient,
24
+ acquireTokenByCode,
25
+ initialize,
26
+ };
27
+ };
28
+
29
+ const createConfig = (client: IMsalClient, authCode?: string): MsalConfig => ({
30
+ client,
31
+ version: '7.0.0',
32
+ requiresAuth: false,
33
+ authCode,
34
+ telemetry: {
35
+ metadata: { module: 'msal', version: '7.0.0' },
36
+ scope: ['framework', 'authentication'],
37
+ },
38
+ });
39
+
40
+ describe('MsalProvider.initialize', () => {
41
+ it('should not attempt auth code exchange when auth code is undefined', async () => {
42
+ const mockClient = createClient();
43
+
44
+ const provider = new MsalProvider(createConfig(mockClient.client, undefined));
45
+ await provider.initialize();
46
+
47
+ expect(mockClient.initialize).toHaveBeenCalledTimes(1);
48
+ expect(mockClient.acquireTokenByCode).not.toHaveBeenCalled();
49
+ });
50
+
51
+ it('should not attempt auth code exchange when auth code is whitespace-only', async () => {
52
+ const mockClient = createClient();
53
+
54
+ const provider = new MsalProvider(createConfig(mockClient.client, ' '));
55
+ await provider.initialize();
56
+
57
+ expect(mockClient.acquireTokenByCode).not.toHaveBeenCalled();
58
+ });
59
+
60
+ it('should exchange auth code once and clear it afterwards', async () => {
61
+ const mockClient = createClient();
62
+
63
+ const provider = new MsalProvider(createConfig(mockClient.client, 'auth-code'));
64
+
65
+ await provider.initialize();
66
+ await provider.initialize();
67
+
68
+ expect(mockClient.acquireTokenByCode).toHaveBeenCalledTimes(1);
69
+ expect(mockClient.acquireTokenByCode).toHaveBeenCalledWith({
70
+ code: 'auth-code',
71
+ scopes: ['test-client-id/.default'],
72
+ });
73
+ });
74
+ });
package/src/module.ts CHANGED
@@ -1,12 +1,12 @@
1
1
  import {
2
2
  type Module,
3
3
  type IModulesConfigurator,
4
+ type ModuleConfigType,
4
5
  SemanticVersion,
5
6
  } from '@equinor/fusion-framework-module';
6
7
 
7
8
  import { MsalConfigurator } from './MsalConfigurator';
8
9
  import { MsalProvider, type IMsalProvider } from './MsalProvider';
9
- import type { MsalClientConfig } from './MsalClient';
10
10
 
11
11
  import { version } from './version';
12
12
 
@@ -80,23 +80,10 @@ export const module: MsalModule = {
80
80
  * This function receives a builder object with methods to configure the MSAL client
81
81
  * and authentication requirements.
82
82
  */
83
- export type AuthConfigFn = (builder: {
84
- /**
85
- * Set MSAL client configuration
86
- * @param config - Client configuration with tenant ID, client ID, etc.
87
- */
88
- setClientConfig: (config: MsalClientConfig) => void;
89
- /**
90
- * Set whether authentication is required for the application
91
- * @param requiresAuth - If true, app will attempt automatic login on initialization
92
- */
93
- setRequiresAuth: (requiresAuth: boolean) => void;
94
- /**
95
- * Set a default login hint used for silent SSO and pre-filled usernames
96
- * @param loginHint - Preferred username/email to use for login hint
97
- */
98
- setLoginHint: (loginHint: string) => void;
99
- }) => void;
83
+ export type AuthConfigFn<TRef = unknown> = (
84
+ configurator: ModuleConfigType<MsalModule>,
85
+ ref?: TRef,
86
+ ) => void;
100
87
 
101
88
  /**
102
89
  * Enables MSAL authentication module in the framework.
package/src/version.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  // Generated by genversion.
2
- export const version = '7.2.1';
2
+ export const version = '7.3.0';