@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.
- package/CHANGELOG.md +16 -0
- package/README.md +10 -1
- package/dist/esm/MsalConfigurator.js +7 -2
- package/dist/esm/MsalConfigurator.js.map +1 -1
- package/dist/esm/MsalProvider.js +1 -1
- package/dist/esm/MsalProvider.js.map +1 -1
- package/dist/esm/__tests__/MsalConfigurator.test.js +39 -0
- package/dist/esm/__tests__/MsalConfigurator.test.js.map +1 -0
- package/dist/esm/__tests__/MsalProvider.test.js +53 -0
- package/dist/esm/__tests__/MsalProvider.test.js.map +1 -0
- package/dist/esm/module.js.map +1 -1
- package/dist/esm/version.js +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/MsalConfigurator.d.ts +6 -2
- package/dist/types/__tests__/MsalConfigurator.test.d.ts +1 -0
- package/dist/types/__tests__/MsalProvider.test.d.ts +1 -0
- package/dist/types/module.d.ts +3 -20
- package/dist/types/version.d.ts +1 -1
- package/package.json +3 -3
- package/src/MsalConfigurator.ts +8 -3
- package/src/MsalProvider.ts +1 -1
- package/src/__tests__/MsalConfigurator.test.ts +64 -0
- package/src/__tests__/MsalProvider.test.ts +74 -0
- package/src/module.ts +5 -18
- package/src/version.ts +1 -1
|
@@ -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
|
|
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 {};
|
package/dist/types/module.d.ts
CHANGED
|
@@ -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 = (
|
|
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 {
|
package/dist/types/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const version = "7.
|
|
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.
|
|
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
|
|
62
|
-
"@equinor/fusion-framework-module": "^
|
|
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": {
|
package/src/MsalConfigurator.ts
CHANGED
|
@@ -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
|
|
156
|
-
|
|
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
|
|
package/src/MsalProvider.ts
CHANGED
|
@@ -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 = (
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
+
export const version = '7.3.0';
|