UNPKG

@equinor/fusion-framework-module-msal-node

Version:

Fusion Framework module for secure Azure AD authentication in Node.js using MSAL. Supports interactive, silent, and token-only authentication modes with encrypted token storage.

177 lines 7.55 kB
import { PublicClientApplication } from '@azure/msal-node'; import { BaseConfigBuilder, } from '@equinor/fusion-framework-module'; /** * Internal builder for MSAL Node authentication configuration. * * This class provides the implementation for the fluent API exposed via the public interface. * Most consumer-facing documentation is in the interface; see {@link IAuthConfigurator} for usage details. * * @see IAuthConfigurator * @extends BaseConfigBuilder * * Maintainers: Extend or refactor this class to support new authentication modes or configuration options. * Ensure changes are reflected in the interface and validated in `_processConfig`. */ export class AuthConfigurator extends BaseConfigBuilder { /** * Creates the builder with the `interactive` authentication mode as the default. */ constructor() { super(); this.setMode('interactive'); } /** * Sets the authentication mode for the module. * * @param mode - The authentication mode: `'interactive'`, `'silent'`, or `'token_only'`. */ setMode(mode) { this._set('mode', mode); } /** * Sets a pre-configured MSAL `PublicClientApplication` instance. * * Use this when you need full control over the MSAL client configuration. * For most cases, prefer {@link AuthConfigurator.setClientConfig | setClientConfig}. * * @param client - The MSAL `PublicClientApplication` instance. */ setClient(client) { this._set('client', client); } /** * Configures the MSAL client using Azure AD tenant and client IDs. * * Lazily creates a `PublicClientApplication` with a secure persistence cache. * The dynamic import avoids requiring `libsecret` in environments that do not need it. * * @param tenantId - Azure AD tenant (directory) ID. * @param clientId - Azure AD application (client) ID. */ setClientConfig(tenantId, clientId) { this._set('client', async () => { // Dynamically import the createAuthClient function since the client uses `libsecret`` // which is not default installed in all environments. // This avoids installing `libsecret` in environments where it is not needed, like CI/CD pipelines. const { createAuthClient } = await import('./create-auth-client.js'); return createAuthClient(tenantId, clientId); }); } /** * Sets the port for the local HTTP callback server used in interactive mode. * * @param port - Port number for the local server. */ setServerPort(port) { this._set('server.port', port); } /** * Sets a callback invoked when the login URL is ready in interactive mode. * * Use this to display or log the authentication URL for the user. * * @param onOpen - Callback receiving the authentication URL, or `undefined` to disable. */ setServerOnOpen(onOpen) { this._set('server.onOpen', onOpen); } /** * Sets a pre-obtained access token for `token_only` mode. * * @param token - The static access token string. */ setAccessToken(token) { this._set('accessToken', token); } /** * Sets the callback invoked with the device code response during `device_code` authentication. * * If not set, the default behaviour is `console.log(response.message)`. * * @param callback - The callback, or `undefined` to restore the default handler. */ setDeviceCodeCallback(callback) { this._set('deviceCodeCallback', callback ?? undefined); } /** * Prepares and finalizes the authentication configuration before validation and use. * * This method injects the parent authentication provider reference (if available) * into the configuration. It is called before `_processConfig` and allows for * dynamic or contextual configuration adjustments based on the current module instance. * * Future maintainers: If additional contextual setup is needed (e.g., injecting * dependencies, environment-specific values, or chaining providers), extend this method. * * @inheritdoc * @param init - Initialization arguments, including module references. * @param initial - Optional initial configuration values. * @returns The prepared configuration object, ready for validation. */ _buildConfig(init, initial) { // Inject the parent auth provider from the current module instance, if present this._set('parent', init.ref?.auth); // Call the base builder to finalize the config return super._buildConfig(init, initial); } /** * Validates and processes the authentication configuration before use. * * This method ensures that all required properties are present and correctly typed * for the selected authentication mode. Throws descriptive errors if configuration * is incomplete or invalid, helping catch misconfigurations early. * * Future maintainers: Update this logic if new authentication modes or required * properties are introduced. Keep error messages clear to aid debugging. * * @inheritdoc * @param config - The authentication configuration object to validate. * @returns The validated configuration object. * @throws Error if required properties are missing or invalid for the selected mode. */ async _processConfig(config) { // Validate the required properties for the selected authentication mode switch (config.mode) { case 'interactive': { // Interactive mode requires a valid MSAL client instance if (config.client instanceof PublicClientApplication === false) { throw new Error('Client is required when mode is interactive'); } // Server configuration must be present if (!config.server) { throw new Error('Server is required when mode is interactive'); } // Server port must be a number if (typeof config.server.port !== 'number') { throw new Error('Server port must be a number when mode is interactive'); } break; } case 'silent': { // Silent mode requires a valid MSAL client instance if (config.client instanceof PublicClientApplication === false) { throw new Error('Client is required when mode is silent'); } break; } case 'device_code': { // Device code mode requires a valid MSAL client instance if (config.client instanceof PublicClientApplication === false) { throw new Error('Client is required when mode is device_code'); } break; } case 'token_only': { // Token only mode requires a string access token if (typeof config.accessToken !== 'string') { throw new Error('Access token is required when mode is token_only'); } break; } // If new modes are added, ensure validation is implemented here } // Return the validated config for use by the module return config; } } //# sourceMappingURL=AuthConfigurator.js.map