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.

132 lines (120 loc) 4.86 kB
import { PublicClientApplication } from '@azure/msal-node'; import { BaseConfigBuilder, type ConfigBuilderCallbackArgs, type ModulesInstance, } from '@equinor/fusion-framework-module'; import { createAuthClient } from './create-auth-client'; import type { MsalNodeModule } from './module.js'; import type { AuthConfig } from './AuthConfigurator.interface.js'; /** * 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<AuthConfig> { constructor() { super(); this.setMode('interactive'); } setMode(mode: AuthConfig['mode']) { this._set('mode', mode); } setClient(client: AuthConfig['client']) { this._set('client', client); } setClientConfig(tenantId: string, clientId: string): void { this._set('client', () => createAuthClient(tenantId, clientId)); } setServerPort(port: number) { this._set('server.port', port); } setServerOnOpen(onOpen: ((url: string) => void) | undefined) { this._set('server.onOpen', onOpen); } setAccessToken(token: string) { this._set('accessToken', token); } /** * 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. */ protected _buildConfig( init: ConfigBuilderCallbackArgs, initial?: Partial<AuthConfig> | undefined, ) { // Inject the parent auth provider from the current module instance, if present this._set('parent', (init.ref as ModulesInstance<[MsalNodeModule]>)?.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: AuthConfig): Promise<AuthConfig> { 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 '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; } }