@equinor/fusion-framework-module-msal-node 0.1.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 +99 -0
- package/LICENSE +21 -0
- package/README.md +125 -0
- package/dist/esm/AuthConfigurator.interface.js +2 -0
- package/dist/esm/AuthConfigurator.interface.js.map +1 -0
- package/dist/esm/AuthConfigurator.js +112 -0
- package/dist/esm/AuthConfigurator.js.map +1 -0
- package/dist/esm/AuthProvider.interface.js +2 -0
- package/dist/esm/AuthProvider.interface.js.map +1 -0
- package/dist/esm/AuthProvider.js +109 -0
- package/dist/esm/AuthProvider.js.map +1 -0
- package/dist/esm/AuthProviderInteractive.js +88 -0
- package/dist/esm/AuthProviderInteractive.js.map +1 -0
- package/dist/esm/AuthTokenProvider.js +50 -0
- package/dist/esm/AuthTokenProvider.js.map +1 -0
- package/dist/esm/create-auth-cache.js +67 -0
- package/dist/esm/create-auth-cache.js.map +1 -0
- package/dist/esm/create-auth-client.js +35 -0
- package/dist/esm/create-auth-client.js.map +1 -0
- package/dist/esm/create-auth-server.js +81 -0
- package/dist/esm/create-auth-server.js.map +1 -0
- package/dist/esm/enable-module.js +31 -0
- package/dist/esm/enable-module.js.map +1 -0
- package/dist/esm/error.js +64 -0
- package/dist/esm/error.js.map +1 -0
- package/dist/esm/index.js +4 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/module.js +26 -0
- package/dist/esm/module.js.map +1 -0
- package/dist/esm/version.js +3 -0
- package/dist/esm/version.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/dist/types/AuthConfigurator.d.ts +55 -0
- package/dist/types/AuthConfigurator.interface.d.ts +153 -0
- package/dist/types/AuthProvider.d.ts +81 -0
- package/dist/types/AuthProvider.interface.d.ts +55 -0
- package/dist/types/AuthProviderInteractive.d.ts +73 -0
- package/dist/types/AuthTokenProvider.d.ts +43 -0
- package/dist/types/create-auth-cache.d.ts +32 -0
- package/dist/types/create-auth-client.d.ts +24 -0
- package/dist/types/create-auth-server.d.ts +34 -0
- package/dist/types/enable-module.d.ts +24 -0
- package/dist/types/error.d.ts +59 -0
- package/dist/types/index.d.ts +5 -0
- package/dist/types/module.d.ts +24 -0
- package/dist/types/version.d.ts +1 -0
- package/package.json +46 -0
- package/src/AuthConfigurator.interface.ts +163 -0
- package/src/AuthConfigurator.ts +131 -0
- package/src/AuthProvider.interface.ts +53 -0
- package/src/AuthProvider.ts +119 -0
- package/src/AuthProviderInteractive.ts +117 -0
- package/src/AuthTokenProvider.ts +56 -0
- package/src/create-auth-cache.ts +85 -0
- package/src/create-auth-client.ts +40 -0
- package/src/create-auth-server.ts +93 -0
- package/src/enable-module.ts +35 -0
- package/src/error.ts +66 -0
- package/src/index.ts +9 -0
- package/src/module.ts +52 -0
- package/src/version.ts +2 -0
- package/tsconfig.json +15 -0
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import type { PublicClientApplication } from '@azure/msal-node';
|
|
2
|
+
|
|
3
|
+
import type { IAuthProvider } from './AuthProvider.interface.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Represents the configuration for authentication in "token only" mode.
|
|
7
|
+
*
|
|
8
|
+
* @property mode - Specifies the authentication mode as 'token_only'.
|
|
9
|
+
* @property accessToken - The authentication token to be used.
|
|
10
|
+
* @property parent - An optional reference to a parent authentication provider.
|
|
11
|
+
*/
|
|
12
|
+
type AuthConfigTokenMode = {
|
|
13
|
+
mode: 'token_only';
|
|
14
|
+
accessToken: string;
|
|
15
|
+
client?: never;
|
|
16
|
+
server?: never;
|
|
17
|
+
parent?: IAuthProvider;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Configuration type for silent authentication mode.
|
|
22
|
+
*
|
|
23
|
+
* @property mode - Specifies the authentication mode as 'silent'.
|
|
24
|
+
* @property client - An instance of `PublicClientApplication` used for authentication.
|
|
25
|
+
* @property parent - An optional parent `IAuthProvider` instance for delegation.
|
|
26
|
+
*/
|
|
27
|
+
type AuthConfigSilentMode = {
|
|
28
|
+
mode: 'silent';
|
|
29
|
+
client: PublicClientApplication;
|
|
30
|
+
server?: never;
|
|
31
|
+
accessToken?: never;
|
|
32
|
+
parent?: IAuthProvider;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Configuration type for the interactive authentication mode.
|
|
37
|
+
*
|
|
38
|
+
* @property mode - Specifies the authentication mode as 'interactive'.
|
|
39
|
+
* @property client - An instance of `PublicClientApplication` used for authentication.
|
|
40
|
+
* @property server - Configuration for the local server used during the interactive authentication process.
|
|
41
|
+
* @property server.port - The port number on which the local server will run.
|
|
42
|
+
* @property server.onOpen - An optional callback function that is invoked with the authentication URL when the server starts.
|
|
43
|
+
* @property parent - An optional parent `IAuthProvider` instance for delegation or chaining of authentication providers.
|
|
44
|
+
*/
|
|
45
|
+
type AuthConfigInteractiveMode = {
|
|
46
|
+
mode: 'interactive';
|
|
47
|
+
client: PublicClientApplication;
|
|
48
|
+
server: {
|
|
49
|
+
port: number;
|
|
50
|
+
onOpen?: (url: string) => void;
|
|
51
|
+
};
|
|
52
|
+
accessToken?: never;
|
|
53
|
+
parent?: IAuthProvider;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Represents the configuration options for authentication.
|
|
58
|
+
*
|
|
59
|
+
* This type is a union of three different authentication modes:
|
|
60
|
+
* - `AuthConfigInteractiveMode`: Configuration for interactive authentication.
|
|
61
|
+
* - `AuthConfigSilentMode`: Configuration for silent authentication.
|
|
62
|
+
* - `AuthConfigTokenMode`: Configuration for token-based authentication.
|
|
63
|
+
*/
|
|
64
|
+
export type AuthConfig = AuthConfigInteractiveMode | AuthConfigSilentMode | AuthConfigTokenMode;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Interface for configuring authentication settings for the MSAL Node module.
|
|
68
|
+
*
|
|
69
|
+
* This interface is intended for both consumers (users integrating authentication into their Fusion Framework Node.js applications)
|
|
70
|
+
* and future maintainers or developers extending or refactoring the module.
|
|
71
|
+
*
|
|
72
|
+
* Each method allows for fine-grained control over the authentication setup, supporting multiple authentication modes:
|
|
73
|
+
* - `token_only`: Use a pre-obtained access token (e.g., for CI/CD or automation).
|
|
74
|
+
* - `silent`: Use MSAL's silent authentication with a configured client (for background services or cached tokens).
|
|
75
|
+
* - `interactive`: Use MSAL's interactive authentication, typically for CLI tools or development, with a local server for browser-based login.
|
|
76
|
+
*
|
|
77
|
+
* Consumers should use the provided methods to configure the module according to their use case.
|
|
78
|
+
* Maintainers should ensure that new authentication flows or configuration options are exposed via this interface for consistency.
|
|
79
|
+
*
|
|
80
|
+
* @example
|
|
81
|
+
* // --- Interactive mode (browser login, local server) ---
|
|
82
|
+
* ```ts
|
|
83
|
+
* builder.setMode('interactive');
|
|
84
|
+
* builder.setClientConfig('your-tenant-id', 'your-client-id');
|
|
85
|
+
* builder.setServerPort(3000);
|
|
86
|
+
* builder.setServerOnOpen((url) => {
|
|
87
|
+
* console.log(`Please navigate to: ${url}`);
|
|
88
|
+
* });
|
|
89
|
+
* ```
|
|
90
|
+
*
|
|
91
|
+
* // --- Silent mode (background, cached/refresh tokens) ---
|
|
92
|
+
* ```ts
|
|
93
|
+
* builder.setMode('silent');
|
|
94
|
+
* builder.setClientConfig('your-tenant-id', 'your-client-id');
|
|
95
|
+
* ```
|
|
96
|
+
*
|
|
97
|
+
* // --- Token only mode (pre-obtained token, CI/CD) ---
|
|
98
|
+
* ```ts
|
|
99
|
+
* builder.setMode('token_only');
|
|
100
|
+
* builder.setAccessToken('your-access-token');
|
|
101
|
+
*/
|
|
102
|
+
export interface IAuthConfigurator {
|
|
103
|
+
/**
|
|
104
|
+
* Sets the authentication mode for the module.
|
|
105
|
+
*
|
|
106
|
+
* @param mode - The authentication mode to use: 'token_only', 'silent', or 'interactive'.
|
|
107
|
+
*
|
|
108
|
+
* Consumers: Call this first to define the overall authentication strategy.
|
|
109
|
+
* Maintainers: Add new modes here if supporting additional auth flows.
|
|
110
|
+
*/
|
|
111
|
+
setMode(mode: AuthConfig['mode']): void;
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Sets the MSAL client instance for authentication.
|
|
115
|
+
*
|
|
116
|
+
* @param client - The MSAL PublicClientApplication instance to use for authentication.
|
|
117
|
+
*
|
|
118
|
+
* Consumers: Use this to provide a custom MSAL client if needed.
|
|
119
|
+
* Maintainers: Ensure compatibility with MSAL updates and custom client options.
|
|
120
|
+
*/
|
|
121
|
+
setClient(client: AuthConfig['client']): void;
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Configures the MSAL client using tenant and client IDs.
|
|
125
|
+
*
|
|
126
|
+
* @param tenantId - Azure AD tenant ID.
|
|
127
|
+
* @param clientId - Azure AD client/application ID.
|
|
128
|
+
*
|
|
129
|
+
* Consumers: Use this for quick setup without manually creating a client instance.
|
|
130
|
+
* Maintainers: Update this if the client configuration contract changes.
|
|
131
|
+
*/
|
|
132
|
+
setClientConfig(tenantId: string, clientId: string): void;
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Sets the port for the local server (used in interactive mode for auth callbacks).
|
|
136
|
+
*
|
|
137
|
+
* @param port - The port number for the local HTTP server.
|
|
138
|
+
*
|
|
139
|
+
* Consumers: Use this to avoid port conflicts or customize the callback endpoint.
|
|
140
|
+
* Maintainers: Ensure this is respected in server setup logic.
|
|
141
|
+
*/
|
|
142
|
+
setServerPort(port: number): void;
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Sets a callback to be invoked when the local server opens (interactive mode).
|
|
146
|
+
*
|
|
147
|
+
* @param onOpen - Callback receiving the server URL when ready, or undefined to disable.
|
|
148
|
+
*
|
|
149
|
+
* Consumers: Use this to display or log the login URL for users.
|
|
150
|
+
* Maintainers: Update this if the server startup flow changes.
|
|
151
|
+
*/
|
|
152
|
+
setServerOnOpen(onOpen: ((url: string) => void) | undefined): void;
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Sets a pre-obtained access token for token_only mode.
|
|
156
|
+
*
|
|
157
|
+
* @param token - The access token to use for authentication.
|
|
158
|
+
*
|
|
159
|
+
* Consumers: Use this for automation or CI/CD scenarios.
|
|
160
|
+
* Maintainers: Ensure this is securely handled and not mixed with other modes.
|
|
161
|
+
*/
|
|
162
|
+
setAccessToken(token: string): void;
|
|
163
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { PublicClientApplication } from '@azure/msal-node';
|
|
2
|
+
import {
|
|
3
|
+
BaseConfigBuilder,
|
|
4
|
+
type ConfigBuilderCallbackArgs,
|
|
5
|
+
type ModulesInstance,
|
|
6
|
+
} from '@equinor/fusion-framework-module';
|
|
7
|
+
|
|
8
|
+
import { createAuthClient } from './create-auth-client';
|
|
9
|
+
|
|
10
|
+
import type { MsalNodeModule } from './module.js';
|
|
11
|
+
import type { AuthConfig } from './AuthConfigurator.interface.js';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Internal builder for MSAL Node authentication configuration.
|
|
15
|
+
*
|
|
16
|
+
* This class provides the implementation for the fluent API exposed via the public interface.
|
|
17
|
+
* Most consumer-facing documentation is in the interface; see {@link IAuthConfigurator} for usage details.
|
|
18
|
+
*
|
|
19
|
+
* @see IAuthConfigurator
|
|
20
|
+
* @extends BaseConfigBuilder
|
|
21
|
+
*
|
|
22
|
+
* Maintainers: Extend or refactor this class to support new authentication modes or configuration options.
|
|
23
|
+
* Ensure changes are reflected in the interface and validated in `_processConfig`.
|
|
24
|
+
*/
|
|
25
|
+
export class AuthConfigurator extends BaseConfigBuilder<AuthConfig> {
|
|
26
|
+
constructor() {
|
|
27
|
+
super();
|
|
28
|
+
this.setMode('interactive');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
setMode(mode: AuthConfig['mode']) {
|
|
32
|
+
this._set('mode', mode);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
setClient(client: AuthConfig['client']) {
|
|
36
|
+
this._set('client', client);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
setClientConfig(tenantId: string, clientId: string): void {
|
|
40
|
+
this._set('client', () => createAuthClient(tenantId, clientId));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
setServerPort(port: number) {
|
|
44
|
+
this._set('server.port', port);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
setServerOnOpen(onOpen: ((url: string) => void) | undefined) {
|
|
48
|
+
this._set('server.onOpen', onOpen);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
setAccessToken(token: string) {
|
|
52
|
+
this._set('accessToken', token);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Prepares and finalizes the authentication configuration before validation and use.
|
|
57
|
+
*
|
|
58
|
+
* This method injects the parent authentication provider reference (if available)
|
|
59
|
+
* into the configuration. It is called before `_processConfig` and allows for
|
|
60
|
+
* dynamic or contextual configuration adjustments based on the current module instance.
|
|
61
|
+
*
|
|
62
|
+
* Future maintainers: If additional contextual setup is needed (e.g., injecting
|
|
63
|
+
* dependencies, environment-specific values, or chaining providers), extend this method.
|
|
64
|
+
*
|
|
65
|
+
* @inheritdoc
|
|
66
|
+
* @param init - Initialization arguments, including module references.
|
|
67
|
+
* @param initial - Optional initial configuration values.
|
|
68
|
+
* @returns The prepared configuration object, ready for validation.
|
|
69
|
+
*/
|
|
70
|
+
protected _buildConfig(
|
|
71
|
+
init: ConfigBuilderCallbackArgs,
|
|
72
|
+
initial?: Partial<AuthConfig> | undefined,
|
|
73
|
+
) {
|
|
74
|
+
// Inject the parent auth provider from the current module instance, if present
|
|
75
|
+
this._set('parent', (init.ref as ModulesInstance<[MsalNodeModule]>)?.auth);
|
|
76
|
+
// Call the base builder to finalize the config
|
|
77
|
+
return super._buildConfig(init, initial);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Validates and processes the authentication configuration before use.
|
|
82
|
+
*
|
|
83
|
+
* This method ensures that all required properties are present and correctly typed
|
|
84
|
+
* for the selected authentication mode. Throws descriptive errors if configuration
|
|
85
|
+
* is incomplete or invalid, helping catch misconfigurations early.
|
|
86
|
+
*
|
|
87
|
+
* Future maintainers: Update this logic if new authentication modes or required
|
|
88
|
+
* properties are introduced. Keep error messages clear to aid debugging.
|
|
89
|
+
*
|
|
90
|
+
* @inheritdoc
|
|
91
|
+
* @param config - The authentication configuration object to validate.
|
|
92
|
+
* @returns The validated configuration object.
|
|
93
|
+
* @throws Error if required properties are missing or invalid for the selected mode.
|
|
94
|
+
*/
|
|
95
|
+
async _processConfig(config: AuthConfig): Promise<AuthConfig> {
|
|
96
|
+
switch (config.mode) {
|
|
97
|
+
case 'interactive': {
|
|
98
|
+
// Interactive mode requires a valid MSAL client instance
|
|
99
|
+
if (config.client instanceof PublicClientApplication === false) {
|
|
100
|
+
throw new Error('Client is required when mode is interactive');
|
|
101
|
+
}
|
|
102
|
+
// Server configuration must be present
|
|
103
|
+
if (!config.server) {
|
|
104
|
+
throw new Error('Server is required when mode is interactive');
|
|
105
|
+
}
|
|
106
|
+
// Server port must be a number
|
|
107
|
+
if (typeof config.server.port !== 'number') {
|
|
108
|
+
throw new Error('Server port must be a number when mode is interactive');
|
|
109
|
+
}
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
case 'silent': {
|
|
113
|
+
// Silent mode requires a valid MSAL client instance
|
|
114
|
+
if (config.client instanceof PublicClientApplication === false) {
|
|
115
|
+
throw new Error('Client is required when mode is silent');
|
|
116
|
+
}
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
case 'token_only': {
|
|
120
|
+
// Token only mode requires a string access token
|
|
121
|
+
if (typeof config.accessToken !== 'string') {
|
|
122
|
+
throw new Error('Access token is required when mode is token_only');
|
|
123
|
+
}
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
// If new modes are added, ensure validation is implemented here
|
|
127
|
+
}
|
|
128
|
+
// Return the validated config for use by the module
|
|
129
|
+
return config;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { AuthenticationResult } from '@azure/msal-node';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Interface for authentication providers in the Fusion MSAL Node module.
|
|
5
|
+
*
|
|
6
|
+
* Provides a unified API for authentication operations, including acquiring access tokens.
|
|
7
|
+
* The actual behavior of each method depends on the configured authentication mode (interactive, silent, or token-only).
|
|
8
|
+
*
|
|
9
|
+
* When accessing `appModules.msal.auth`, you will interact with this interface to:
|
|
10
|
+
* - Acquire Azure AD access tokens for specified scopes
|
|
11
|
+
*
|
|
12
|
+
* All methods are asynchronous and return Promises. See method documentation for details.
|
|
13
|
+
*
|
|
14
|
+
* @remarks
|
|
15
|
+
* This provider assumes the user is already logged in. To support login and logout operations, the module must be configured in interactive mode. In other modes, `login` and `logout` are present for compatibility but will be no-ops or throw if called.
|
|
16
|
+
*
|
|
17
|
+
* @see IAuthProviderInteractive for interactive login/logout support
|
|
18
|
+
*/
|
|
19
|
+
export interface IAuthProvider {
|
|
20
|
+
/**
|
|
21
|
+
* This method is present for compatibility but will never trigger a user login flow unless interactive mode is configured.
|
|
22
|
+
*
|
|
23
|
+
* @param options - An object specifying the required scopes for authentication.
|
|
24
|
+
* @returns A Promise that will always reject or be a no-op, depending on implementation.
|
|
25
|
+
*
|
|
26
|
+
* @remarks
|
|
27
|
+
* This method is not supported and should not be used to initiate login unless interactive mode is enabled.
|
|
28
|
+
*/
|
|
29
|
+
login(options: { scopes: string[] }): Promise<AuthenticationResult>;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* This method is present for compatibility but will never trigger a user logout flow unless interactive mode is configured.
|
|
33
|
+
*
|
|
34
|
+
* @returns A Promise that will always resolve immediately or be a no-op, depending on implementation.
|
|
35
|
+
*
|
|
36
|
+
* @remarks
|
|
37
|
+
* This method is not supported and should not be used to initiate logout unless interactive mode is enabled.
|
|
38
|
+
*/
|
|
39
|
+
logout(): Promise<void>;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Acquires an access token for the specified scopes.
|
|
43
|
+
*
|
|
44
|
+
* @param options - An object specifying the required scopes and an optional `interactive` flag.
|
|
45
|
+
* - `scopes`: The scopes for which the token is requested.
|
|
46
|
+
* - `interactive`: If true, may trigger an interactive login if silent acquisition fails (not supported unless interactive mode is enabled).
|
|
47
|
+
* @returns A Promise that resolves to a string representing the acquired access token.
|
|
48
|
+
*
|
|
49
|
+
* @remarks
|
|
50
|
+
* This is the primary method for obtaining tokens for API calls or resource access.
|
|
51
|
+
*/
|
|
52
|
+
acquireAccessToken(options: { scopes: string[]; interactive?: boolean }): Promise<string>;
|
|
53
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import type { AccountInfo, AuthenticationResult, PublicClientApplication } from '@azure/msal-node';
|
|
2
|
+
|
|
3
|
+
import { AuthServerError, NoAccountsError, SilentTokenAcquisitionError } from './error.js';
|
|
4
|
+
|
|
5
|
+
import type { IAuthProvider } from './AuthProvider.interface.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Implementation of the authentication provider for the Fusion MSAL Node module.
|
|
9
|
+
*
|
|
10
|
+
* Implements {@link IAuthProvider} and provides methods for managing authentication
|
|
11
|
+
* and token acquisition using the MSAL (Microsoft Authentication Library) for Node.js.
|
|
12
|
+
*
|
|
13
|
+
* This implementation assumes the user is already logged in and does not support
|
|
14
|
+
* triggering interactive login or logout flows. The `login` method will always throw, and `logout`
|
|
15
|
+
* only clears the token cache.
|
|
16
|
+
*
|
|
17
|
+
* @see AuthProviderInteractive For interactive login/logout support (user-driven authentication flows).
|
|
18
|
+
* @see AuthProviderTokenOnly For scenarios where a pre-obtained token is used (automation, CI/CD, etc).
|
|
19
|
+
*
|
|
20
|
+
* Developers extending this class can add support for additional authentication flows or modify token
|
|
21
|
+
* acquisition logic. Ensure that any changes remain consistent with the interface contract.
|
|
22
|
+
*/
|
|
23
|
+
export class AuthProvider implements IAuthProvider {
|
|
24
|
+
constructor(protected _client: PublicClientApplication) {}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Retrieves the first account from the list of all accounts available in the MSAL client.
|
|
28
|
+
*
|
|
29
|
+
* @returns A promise that resolves to the first `AccountInfo` object if available, or `null` if no accounts exist.
|
|
30
|
+
*/
|
|
31
|
+
public async getAccount(): Promise<AccountInfo | null> {
|
|
32
|
+
const accounts = await this._client.getAllAccounts();
|
|
33
|
+
return accounts[0] ?? null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Acquires an access token for the specified scopes.
|
|
38
|
+
*
|
|
39
|
+
* @param options - An object containing the options for acquiring the token.
|
|
40
|
+
* @param options.scopes - An array of strings representing the scopes for which the access token is requested.
|
|
41
|
+
* @returns A promise that resolves to the acquired access token as a string.
|
|
42
|
+
* @throws An error if the token acquisition process fails.
|
|
43
|
+
*/
|
|
44
|
+
public async acquireAccessToken(options: {
|
|
45
|
+
scopes: string[];
|
|
46
|
+
}): Promise<string> {
|
|
47
|
+
const { accessToken } = await this.acquireToken(options);
|
|
48
|
+
return accessToken;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Initiates the login process with the specified options.
|
|
53
|
+
*
|
|
54
|
+
* @param _options - An object containing the scopes required for authentication.
|
|
55
|
+
* @returns A promise that resolves to an `AuthenticationResult` upon successful login.
|
|
56
|
+
* @throws `AuthServerError` - Always throws this error as login is not supported in this implementation.
|
|
57
|
+
*
|
|
58
|
+
* @remarks
|
|
59
|
+
* This method is not supported and is intended to be overridden by `AuthProviderInteractive`.
|
|
60
|
+
*/
|
|
61
|
+
public async login(_options: { scopes: string[] }): Promise<AuthenticationResult> {
|
|
62
|
+
throw new AuthServerError('Login not supported, use AuthProviderInteractive instead');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Logs out the user by clearing the token cache and removing all accounts.
|
|
67
|
+
*
|
|
68
|
+
* This method retrieves all accounts from the token cache and removes them
|
|
69
|
+
* individually. Afterward, it clears the entire cache to ensure no residual
|
|
70
|
+
* authentication data remains.
|
|
71
|
+
*
|
|
72
|
+
* @returns A promise that resolves when the logout process is complete.
|
|
73
|
+
*/
|
|
74
|
+
public async logout() {
|
|
75
|
+
const cache = this._client.getTokenCache();
|
|
76
|
+
const accounts = await cache.getAllAccounts();
|
|
77
|
+
for (const account of accounts) {
|
|
78
|
+
await cache.removeAccount(account);
|
|
79
|
+
}
|
|
80
|
+
this._client.clearCache();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Acquires an authentication token for the specified scopes.
|
|
85
|
+
*
|
|
86
|
+
* This method first attempts to acquire a token silently using the accounts
|
|
87
|
+
* available in the token cache. If no accounts are found and interactive login
|
|
88
|
+
* is allowed, it initiates an interactive login flow. If interactive login is
|
|
89
|
+
* not allowed and no accounts are found, an error is thrown.
|
|
90
|
+
*
|
|
91
|
+
* @param scopes - An array of strings representing the scopes for which the token is requested.
|
|
92
|
+
* @param options - Optional parameters for token acquisition.
|
|
93
|
+
* @param options.interactive - A boolean indicating whether interactive login is allowed
|
|
94
|
+
* if no accounts are found in the cache. Defaults to `false`.
|
|
95
|
+
* @returns A promise that resolves to an `AuthenticationResult` containing the acquired token.
|
|
96
|
+
* @throws {@link NoAccountsError} If no accounts are found in the cache and interactive login is not allowed.
|
|
97
|
+
* @throws {@link SilentTokenAcquisitionError} If an error occurs during silent token acquisition.
|
|
98
|
+
*/
|
|
99
|
+
public async acquireToken(options: {
|
|
100
|
+
scopes: string[];
|
|
101
|
+
}): Promise<AuthenticationResult> {
|
|
102
|
+
const account = await this.getAccount();
|
|
103
|
+
if (!account) {
|
|
104
|
+
throw new NoAccountsError('No accounts found in cache');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
try {
|
|
108
|
+
const tokenResponse = await this._client.acquireTokenSilent({
|
|
109
|
+
scopes: options.scopes,
|
|
110
|
+
account,
|
|
111
|
+
});
|
|
112
|
+
return tokenResponse;
|
|
113
|
+
} catch (error) {
|
|
114
|
+
throw new SilentTokenAcquisitionError('Error acquiring token', {
|
|
115
|
+
cause: error,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CryptoProvider,
|
|
3
|
+
type AuthenticationResult,
|
|
4
|
+
type PublicClientApplication,
|
|
5
|
+
} from '@azure/msal-node';
|
|
6
|
+
|
|
7
|
+
import openBrowser from 'open';
|
|
8
|
+
import { createAuthServer } from './create-auth-server.js';
|
|
9
|
+
import { AuthProvider } from './AuthProvider.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Options for configuring the interactive authentication provider.
|
|
13
|
+
*
|
|
14
|
+
* @property server - Configuration for the local server used to handle authentication callbacks.
|
|
15
|
+
* @property port - The port number on which the local server will listen for authentication responses.
|
|
16
|
+
* @property onOpen - Optional callback invoked with the authentication URL when the server is ready (e.g., to display or log the URL).
|
|
17
|
+
*
|
|
18
|
+
* Used when constructing an instance of {@link AuthProviderInteractive} to enable browser-based login flows.
|
|
19
|
+
*/
|
|
20
|
+
type AuthProviderOptions = {
|
|
21
|
+
server: {
|
|
22
|
+
port: number;
|
|
23
|
+
onOpen?: (url: string) => void;
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Implementation of an interactive authentication provider for the Fusion MSAL Node module.
|
|
29
|
+
*
|
|
30
|
+
* Extends {@link AuthProvider} to support user-driven authentication flows using the authorization code flow with PKCE.
|
|
31
|
+
* This class opens the user's default browser for authentication and handles the response via a local server.
|
|
32
|
+
*
|
|
33
|
+
* This implementation is intended for scenarios where interactive login is required, such as CLI tools or development utilities.
|
|
34
|
+
*
|
|
35
|
+
* Developers extending this provider can customize the interactive flow, server handling, or PKCE logic as needed.
|
|
36
|
+
* Ensure that any changes remain consistent with the expected interface and security best practices.
|
|
37
|
+
*
|
|
38
|
+
* @see AuthProvider for non-interactive (silent) authentication flows.
|
|
39
|
+
* @see AuthProviderTokenOnly for token-only scenarios.
|
|
40
|
+
*/
|
|
41
|
+
export class AuthProviderInteractive extends AuthProvider {
|
|
42
|
+
#options: AuthProviderOptions;
|
|
43
|
+
|
|
44
|
+
constructor(client: PublicClientApplication, options: AuthProviderOptions) {
|
|
45
|
+
super(client);
|
|
46
|
+
this.#options = options;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Initiates the login process using the authorization code flow with PKCE.
|
|
51
|
+
*
|
|
52
|
+
* This method generates a PKCE code verifier and challenge to enhance security
|
|
53
|
+
* and prevent authorization code interception attacks. It constructs an
|
|
54
|
+
* authorization code URL, opens the default browser for user authentication,
|
|
55
|
+
* and starts a local server to handle the authentication response.
|
|
56
|
+
*
|
|
57
|
+
* @param scopes - An array of scopes that specify the permissions being requested.
|
|
58
|
+
* @returns A promise that resolves to an `AuthenticationResult` containing the
|
|
59
|
+
* authentication details upon successful login.
|
|
60
|
+
*
|
|
61
|
+
* @throws Will throw an error if the PKCE code generation, browser opening, or
|
|
62
|
+
* authentication server setup fails.
|
|
63
|
+
*/
|
|
64
|
+
public async login(options: { scopes: string[] }): Promise<AuthenticationResult> {
|
|
65
|
+
const { scopes } = options;
|
|
66
|
+
const { port, onOpen } = this.#options.server;
|
|
67
|
+
|
|
68
|
+
// Generate a new PKCE code verifier and challenge
|
|
69
|
+
// This is used to enhance security in the authorization code flow
|
|
70
|
+
// by preventing authorization code interception attacks.
|
|
71
|
+
const cryptoProvider = new CryptoProvider();
|
|
72
|
+
const { verifier, challenge } = await cryptoProvider.generatePkceCodes();
|
|
73
|
+
const authCodeUrl = await this._client.getAuthCodeUrl({
|
|
74
|
+
scopes,
|
|
75
|
+
redirectUri: `http://localhost:${port}`,
|
|
76
|
+
codeChallenge: challenge,
|
|
77
|
+
codeChallengeMethod: 'S256',
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// open default browser to authenticate
|
|
81
|
+
await openBrowser(authCodeUrl);
|
|
82
|
+
|
|
83
|
+
// callback to open the auth code url
|
|
84
|
+
if (onOpen) onOpen(authCodeUrl);
|
|
85
|
+
|
|
86
|
+
return createAuthServer(this._client, scopes, {
|
|
87
|
+
codeVerifier: verifier,
|
|
88
|
+
port,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Acquires an authentication token for the specified scopes.
|
|
94
|
+
*
|
|
95
|
+
* This method first attempts to acquire a token silently using the accounts
|
|
96
|
+
* available in the token cache. If no accounts are found and interactive login
|
|
97
|
+
* is allowed, it initiates an interactive login flow. If interactive login is
|
|
98
|
+
* not allowed and no accounts are found, an error is thrown.
|
|
99
|
+
*
|
|
100
|
+
* @param scopes - An array of strings representing the scopes for which the token is requested.
|
|
101
|
+
* @param options - Optional parameters for token acquisition.
|
|
102
|
+
* @param options.interactive - A boolean indicating whether interactive login is allowed
|
|
103
|
+
* if no accounts are found in the cache. Defaults to `false`.
|
|
104
|
+
* @returns A promise that resolves to an `AuthenticationResult` containing the acquired token.
|
|
105
|
+
* @throws {@link NoAccountsError} If no accounts are found in the cache and interactive login is not allowed.
|
|
106
|
+
* @throws {@link SilentTokenAcquisitionError} If an error occurs during silent token acquisition.
|
|
107
|
+
*/
|
|
108
|
+
public async acquireToken(options: {
|
|
109
|
+
scopes: string[];
|
|
110
|
+
}): Promise<AuthenticationResult> {
|
|
111
|
+
const { scopes } = options ?? { scopes: [] };
|
|
112
|
+
if ((await this.getAccount()) === null) {
|
|
113
|
+
return this.login({ scopes });
|
|
114
|
+
}
|
|
115
|
+
return super.acquireToken(options);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { AuthenticationResult } from '@azure/msal-node';
|
|
2
|
+
import type { IAuthProvider } from './AuthProvider.interface.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Implementation of an authentication provider that supplies a static, pre-obtained access token.
|
|
6
|
+
*
|
|
7
|
+
* This class implements {@link IAuthProvider} and is intended for scenarios where authentication
|
|
8
|
+
* is handled externally and a token is provided directly (e.g., CI/CD pipelines, automation, or service accounts).
|
|
9
|
+
*
|
|
10
|
+
* Login and logout operations are not supported and will always throw errors if called.
|
|
11
|
+
*
|
|
12
|
+
* @see AuthProvider for silent authentication using cached accounts and MSAL flows.
|
|
13
|
+
* @see AuthProviderInteractive for interactive, user-driven authentication flows.
|
|
14
|
+
*/
|
|
15
|
+
export class AuthTokenProvider implements IAuthProvider {
|
|
16
|
+
#accessToken: string;
|
|
17
|
+
constructor(token: string) {
|
|
18
|
+
this.#accessToken = token;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Not supported in token-only mode. Always throws an error if called.
|
|
23
|
+
*
|
|
24
|
+
* This provider is designed for scenarios where authentication is handled externally
|
|
25
|
+
* and a static token is supplied. Login flows are not possible in this context.
|
|
26
|
+
*
|
|
27
|
+
* @throws Error Always throws to indicate login is not supported.
|
|
28
|
+
*/
|
|
29
|
+
login(): Promise<AuthenticationResult> {
|
|
30
|
+
throw new Error('Method not supported in token mode');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Not supported in token-only mode. Always throws an error if called.
|
|
35
|
+
*
|
|
36
|
+
* Since this provider does not manage user sessions or accounts, logout is not applicable.
|
|
37
|
+
*
|
|
38
|
+
* @throws Error Always throws to indicate logout is not supported.
|
|
39
|
+
*/
|
|
40
|
+
logout(): Promise<void> {
|
|
41
|
+
throw new Error('Method not supported in token mode');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Returns the pre-obtained access token supplied to the provider.
|
|
46
|
+
*
|
|
47
|
+
* This is the only supported operation for this provider. No token refresh or acquisition logic is performed.
|
|
48
|
+
*
|
|
49
|
+
* @returns The static access token as a string.
|
|
50
|
+
*/
|
|
51
|
+
async acquireAccessToken(): Promise<string> {
|
|
52
|
+
return this.#accessToken;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export default AuthTokenProvider;
|