Skip to main content
Glama
auth-client.js13.6 kB
import { PublicClientApplication, ConfidentialClientApplication, LogLevel } from "@azure/msal-node"; import { createServer } from "http"; import { URL } from "url"; import { exec } from 'child_process'; import { promisify } from 'util'; const execAsync = promisify(exec); export var AuthMethod; (function (AuthMethod) { AuthMethod["BEARER_TOKEN"] = "bearer"; AuthMethod["AZURE_CLI"] = "azure_cli"; AuthMethod["SERVICE_PRINCIPAL"] = "service_principal"; AuthMethod["DEVICE_CODE"] = "device_code"; AuthMethod["INTERACTIVE"] = "interactive"; })(AuthMethod || (AuthMethod = {})); export class MicrosoftAuthClient { constructor(config) { this.defaultScope = ["https://api.fabric.microsoft.com/.default"]; this.config = { ...config, authority: config.authority || "https://login.microsoftonline.com/common", scope: config.scope || this.defaultScope }; } /** * Initialize the appropriate MSAL client based on auth method */ initializeClient(method) { const clientConfig = { auth: { clientId: this.config.clientId, authority: this.config.authority, }, system: { loggerOptions: { loggerCallback: (level, message, containsPii) => { if (level === LogLevel.Error) { console.error("MSAL Error:", message); } }, piiLoggingEnabled: false, logLevel: LogLevel.Warning, }, }, }; if (method === AuthMethod.SERVICE_PRINCIPAL && this.config.clientSecret) { // Use confidential client for service principal this.confidentialClient = new ConfidentialClientApplication({ ...clientConfig, auth: { ...clientConfig.auth, clientSecret: this.config.clientSecret, }, }); } else { // Use public client for device code and interactive flows this.publicClient = new PublicClientApplication(clientConfig); } } /** * Authenticate using bearer token (direct token provision) */ async authenticateWithBearerToken(token) { // For bearer token, we don't need MSAL - just validate and return if (!token || !token.startsWith('Bearer ')) { throw new Error("Invalid bearer token format. Expected format: 'Bearer <token>'"); } const accessToken = token.substring(7); // Remove 'Bearer ' prefix // Basic token validation (check if it's a JWT-like structure) const parts = accessToken.split('.'); if (parts.length !== 3) { throw new Error("Invalid bearer token format. Token does not appear to be a valid JWT."); } return { accessToken, expiresOn: new Date(Date.now() + 3600000), // Default 1 hour expiry }; } /** * Authenticate using service principal (client credentials flow) */ async authenticateWithServicePrincipal(clientId, clientSecret, tenantId) { this.config.clientId = clientId; this.config.clientSecret = clientSecret; this.config.authority = `https://login.microsoftonline.com/${tenantId}`; this.initializeClient(AuthMethod.SERVICE_PRINCIPAL); if (!this.confidentialClient) { throw new Error("Failed to initialize confidential client for service principal authentication"); } const clientCredentialRequest = { scopes: this.config.scope, skipCache: false, }; try { const response = await this.confidentialClient.acquireTokenByClientCredential(clientCredentialRequest); if (!response || !response.accessToken) { throw new Error("Failed to acquire token using service principal"); } return { accessToken: response.accessToken, expiresOn: response.expiresOn || new Date(Date.now() + 3600000), account: response.account || undefined, }; } catch (error) { throw new Error(`Service principal authentication failed: ${error instanceof Error ? error.message : String(error)}`); } } /** * Authenticate using device code flow (for headless environments) */ async authenticateWithDeviceCode(clientId, tenantId) { this.config.clientId = clientId; if (tenantId) { this.config.authority = `https://login.microsoftonline.com/${tenantId}`; } this.initializeClient(AuthMethod.DEVICE_CODE); if (!this.publicClient) { throw new Error("Failed to initialize public client for device code authentication"); } const deviceCodeRequest = { scopes: this.config.scope, deviceCodeCallback: (response) => { console.log("\n=== DEVICE CODE AUTHENTICATION ==="); console.log(`Please visit: ${response.verificationUri}`); console.log(`And enter the code: ${response.userCode}`); console.log(`Expires in: ${response.expiresIn} seconds`); console.log("Waiting for authentication...\n"); }, }; try { const response = await this.publicClient.acquireTokenByDeviceCode(deviceCodeRequest); if (!response || !response.accessToken) { throw new Error("Failed to acquire token using device code"); } return { accessToken: response.accessToken, expiresOn: response.expiresOn || new Date(Date.now() + 3600000), account: response.account || undefined, }; } catch (error) { throw new Error(`Device code authentication failed: ${error instanceof Error ? error.message : String(error)}`); } } /** * Authenticate using interactive flow (opens browser) */ async authenticateInteractively(clientId, tenantId) { this.config.clientId = clientId; this.config.redirectUri = "http://localhost:8080"; if (tenantId) { this.config.authority = `https://login.microsoftonline.com/${tenantId}`; } this.initializeClient(AuthMethod.INTERACTIVE); if (!this.publicClient) { throw new Error("Failed to initialize public client for interactive authentication"); } return new Promise((resolve, reject) => { const server = createServer((req, res) => { if (req.url && req.url.startsWith('/?code=')) { const url = new URL(req.url, 'http://localhost:8080'); const code = url.searchParams.get('code'); res.writeHead(200, { 'Content-Type': 'text/html' }); res.end(` <html> <body> <h2>Authentication successful!</h2> <p>You can close this window and return to the application.</p> </body> </html> `); server.close(); if (code) { this.handleAuthorizationCode(code).then(resolve).catch(reject); } else { reject(new Error("No authorization code received")); } } }); server.listen(8080, () => { const authUrl = this.buildAuthUrl(); console.log("\n=== INTERACTIVE AUTHENTICATION ==="); console.log(`Please visit: ${authUrl}`); console.log("Waiting for authentication...\n"); // Auto-open browser if possible import('child_process').then(({ exec }) => { exec(`start ${authUrl}`, (error) => { if (error) { console.log("Could not auto-open browser. Please manually visit the URL above."); } }); }); }); // Timeout after 5 minutes setTimeout(() => { server.close(); reject(new Error("Authentication timeout")); }, 300000); }); } buildAuthUrl() { const params = new URLSearchParams({ client_id: this.config.clientId, response_type: 'code', redirect_uri: this.config.redirectUri, scope: this.config.scope.join(' '), response_mode: 'query', }); return `${this.config.authority}/oauth2/v2.0/authorize?${params.toString()}`; } async handleAuthorizationCode(code) { // This is a simplified implementation - in a real scenario, you'd exchange the code for tokens // For now, we'll use device code flow as a fallback console.log("Received authorization code, exchanging for tokens..."); // Fallback to device code flow return this.authenticateWithDeviceCode(this.config.clientId); } /** * Authenticate using Azure CLI * Uses the current Azure CLI login session */ async authenticateWithAzureCli(resource = "https://api.fabric.microsoft.com") { try { // Get access token using Azure CLI const { stdout } = await execAsync(`az account get-access-token --resource "${resource}" --query "accessToken" --output tsv`); const accessToken = stdout.trim(); if (!accessToken) { console.error("Failed to get access token from Azure CLI"); return null; } // Parse the token to get expiration (optional) let expiresOn = new Date(Date.now() + 3600000); // Default 1 hour try { // Try to get token details including expiration const { stdout: tokenDetails } = await execAsync(`az account get-access-token --resource "${resource}" --output json`); const tokenInfo = JSON.parse(tokenDetails); if (tokenInfo.expiresOn) { expiresOn = new Date(tokenInfo.expiresOn); } } catch (e) { // If parsing fails, use default expiration console.warn("Could not parse token expiration, using default"); } return { accessToken, expiresOn, account: undefined }; } catch (error) { console.error("Azure CLI authentication failed:", error); console.error("Make sure you are logged in with 'az login' and have access to Microsoft Fabric"); return null; } } /** * Get token silently (from cache) */ async getTokenSilently(account) { if (!this.publicClient) { return null; } const silentRequest = { scopes: this.config.scope, account: account, }; try { const response = await this.publicClient.acquireTokenSilent(silentRequest); if (!response || !response.accessToken) { return null; } return { accessToken: response.accessToken, expiresOn: response.expiresOn || new Date(Date.now() + 3600000), account: response.account || undefined, }; } catch (error) { return null; // Silent acquisition failed, need interactive flow } } /** * Get all cached accounts */ async getCachedAccounts() { if (!this.publicClient) { return []; } return this.publicClient.getTokenCache().getAllAccounts(); } /** * Clear token cache */ async clearCache() { if (this.publicClient) { const accounts = await this.getCachedAccounts(); for (const account of accounts) { await this.publicClient.getTokenCache().removeAccount(account); } } } /** * Validate if token is still valid */ isTokenValid(authResult) { return authResult.expiresOn > new Date(); } /** * Get authentication method prompt for CLI */ static getAuthMethodPrompt() { return ` Choose authentication method: 1. Bearer Token (provide your own token) 2. Azure CLI (use current az login session) 3. Service Principal (client ID + secret + tenant) 4. Device Code (sign in with browser on another device) 5. Interactive (sign in with browser - opens automatically) Enter choice (1-5): `; } /** * Parse authentication method from user input */ static parseAuthMethod(input) { switch (input.trim()) { case "1": return AuthMethod.BEARER_TOKEN; case "2": return AuthMethod.AZURE_CLI; case "3": return AuthMethod.SERVICE_PRINCIPAL; case "4": return AuthMethod.DEVICE_CODE; case "5": return AuthMethod.INTERACTIVE; default: throw new Error("Invalid authentication method selection"); } } } export default MicrosoftAuthClient; //# sourceMappingURL=auth-client.js.map

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/santhoshravindran7/Fabric-Analytics-MCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server