UNPKG

azureai-optimizer

Version:

AI-Powered Azure Infrastructure Optimization via Model Context Protocol

156 lines 6.65 kB
/** * Azure Authentication Provider * Handles Azure authentication using DefaultAzureCredential */ import { DefaultAzureCredential } from '@azure/identity'; import { SubscriptionClient } from '@azure/arm-subscriptions'; import { Logger } from '../utils/logger.js'; import { MCPError, ErrorCode } from '../utils/errors.js'; export class AzureAuthProvider { credential; config; logger; authContext = null; tokenCache = new Map(); constructor(config) { this.config = config; this.logger = new Logger('AzureAuth'); // Initialize Azure credential with configuration const options = {}; if (config.managedIdentityClientId) { options.managedIdentityClientId = config.managedIdentityClientId; } if (config.tenantId) { options.tenantId = config.tenantId; } this.credential = new DefaultAzureCredential(options); } async initialize() { try { this.logger.info('🔐 Initializing Azure authentication...'); // Test authentication by getting a token const token = await this.credential.getToken([ 'https://management.azure.com/.default' ]); if (!token) { throw new MCPError(ErrorCode.UNAUTHORIZED, 'Failed to obtain Azure access token'); } // Verify subscription access await this.verifySubscriptionAccess(); // Cache the authentication context this.authContext = { credential: this.credential, subscriptionId: this.config.subscriptionId, tenantId: this.config.tenantId || 'auto-detected', accessToken: token.token, expiresOn: token.expiresOnTimestamp ? new Date(token.expiresOnTimestamp) : new Date(), permissions: await this.getPermissions() }; this.logger.info('✅ Azure authentication successful'); this.logger.info(`📋 Subscription: ${this.config.subscriptionId}`); this.logger.info(`🏢 Tenant: ${this.authContext.tenantId}`); } catch (error) { this.logger.error('❌ Azure authentication failed:', error); throw new MCPError(ErrorCode.UNAUTHORIZED, `Azure authentication failed: ${error instanceof Error ? error.message : 'Unknown error'}`); } } async verifySubscriptionAccess() { try { const subscriptionClient = new SubscriptionClient(this.credential); const subscription = await subscriptionClient.subscriptions.get(this.config.subscriptionId); if (!subscription) { throw new Error(`Subscription ${this.config.subscriptionId} not found or not accessible`); } this.logger.info(`✅ Subscription access verified: ${subscription.displayName}`); } catch (error) { throw new MCPError(ErrorCode.FORBIDDEN, `Cannot access subscription ${this.config.subscriptionId}: ${error instanceof Error ? error.message : 'Unknown error'}`); } } async getPermissions() { // This would typically query Azure RBAC to get the user's permissions // For now, we'll return a basic set based on successful authentication const permissions = [ 'Microsoft.CostManagement/*/read', 'Microsoft.Advisor/*/read', 'Microsoft.Resources/*/read', 'Microsoft.Security/*/read', 'Microsoft.Insights/*/read' ]; this.logger.debug('📋 Detected permissions:', permissions); return permissions; } async getAuthContext() { if (!this.authContext) { this.logger.warn('⚠️ Authentication context not available, reinitializing...'); await this.initialize(); } // Check if token is expired and refresh if needed if (this.authContext && this.isTokenExpired(this.authContext.expiresOn)) { this.logger.info('🔄 Access token expired, refreshing...'); await this.refreshToken(); } return this.authContext; } isTokenExpired(expiresOn) { // Add 5 minute buffer before expiration const bufferTime = 5 * 60 * 1000; // 5 minutes in milliseconds return new Date().getTime() > (expiresOn.getTime() - bufferTime); } async refreshToken() { try { const token = await this.credential.getToken([ 'https://management.azure.com/.default' ]); if (!token || !this.authContext) { throw new Error('Failed to refresh token'); } this.authContext.accessToken = token.token; this.authContext.expiresOn = token.expiresOnTimestamp ? new Date(token.expiresOnTimestamp) : new Date(); this.logger.info('✅ Access token refreshed successfully'); } catch (error) { this.logger.error('❌ Failed to refresh access token:', error); throw new MCPError(ErrorCode.UNAUTHORIZED, 'Failed to refresh Azure access token'); } } async getTokenForScope(scope) { try { // Check cache first const cached = this.tokenCache.get(scope); if (cached && !this.isTokenExpired(cached.expiresOn)) { return cached.token; } // Get new token const token = await this.credential.getToken([scope]); if (!token) { throw new Error(`Failed to get token for scope: ${scope}`); } // Cache the token this.tokenCache.set(scope, { token: token.token, expiresOn: token.expiresOnTimestamp ? new Date(token.expiresOnTimestamp) : new Date() }); return token.token; } catch (error) { this.logger.error(`❌ Failed to get token for scope ${scope}:`, error); throw new MCPError(ErrorCode.UNAUTHORIZED, `Failed to get token for scope: ${scope}`); } } getCredential() { return this.credential; } getSubscriptionId() { return this.config.subscriptionId; } hasPermission(permission) { if (!this.authContext) { return false; } // Simple permission check - in production, this would be more sophisticated return this.authContext.permissions.some(p => p === permission || p.endsWith('/*') && permission.startsWith(p.slice(0, -1))); } } //# sourceMappingURL=azure-auth.js.map