Skip to main content
Glama
dual-auth.ts.backup17.3 kB
import axios, { AxiosInstance } from 'axios'; import { AuthCredentials, AuthToken, AuthCredentialsSchema } from './types.js'; export interface UserManagementInfo { isKeycloak: boolean; realm?: string; authMethod: 'keycloak' | 'cognito'; } export class UmbrellaDualAuth { private baseURL: string; private axiosInstance: AxiosInstance; private token: string | null = null; private userApiKey: string | null = null; private tokenExpiry: number = 0; private userManagementInfo: UserManagementInfo | null = null; private userKey: string | null = null; private availableAccounts: any[] = []; constructor(baseURL: string) { this.baseURL = baseURL; this.axiosInstance = axios.create({ baseURL: this.baseURL, timeout: 30000, headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' } }); } /** * Detect user management system (Keycloak UM 2.0 vs Cognito Old UM) */ async detectUserManagementSystem(username: string): Promise<UserManagementInfo> { try { console.error(`[DUAL-AUTH] Detecting user management system for: ${username}`); // Call the user-realm endpoint to check if user is on Keycloak const realmResponse = await this.axiosInstance.get(`/user-management/users/user-realm`, { params: { username: username.toLowerCase() } }); if (realmResponse.status === 200 && realmResponse.data?.realm) { console.error(`[DUAL-AUTH] User is on Keycloak UM 2.0, realm: ${realmResponse.data.realm}`); return { isKeycloak: true, realm: realmResponse.data.realm, authMethod: 'keycloak' }; } else { console.error(`[DUAL-AUTH] User is on Cognito Old UM (no realm found)`); return { isKeycloak: false, authMethod: 'cognito' }; } } catch (error: any) { console.error(`[DUAL-AUTH] Realm detection failed, defaulting to Cognito: ${error.message}`); // Default to old user management if detection fails return { isKeycloak: false, authMethod: 'cognito' }; } } async authenticate(credentials: AuthCredentials): Promise<AuthToken> { // Validate credentials const validatedCredentials = AuthCredentialsSchema.parse(credentials); // First, detect which user management system this user is on this.userManagementInfo = await this.detectUserManagementSystem(validatedCredentials.username); try { let authResponse; if (this.userManagementInfo.isKeycloak) { // Use new authentication endpoint for Keycloak users (UM 2.0) console.error(`[DUAL-AUTH] Authenticating Keycloak user with /authentication/token/generate...`); try { authResponse = await this.authenticateKeycloak(validatedCredentials); } catch (keycloakError: any) { // Fallback: If Keycloak fails with 500, try Cognito (some migrated accounts may still use Cognito) if (keycloakError.response?.status === 500) { console.error(`[DUAL-AUTH] Keycloak failed with 500, trying Cognito fallback...`); // Update the user management info to reflect we're actually using Cognito this.userManagementInfo = { isKeycloak: false, authMethod: 'cognito' }; authResponse = await this.authenticateCognito(validatedCredentials); } else { throw keycloakError; } } } else { // Use old signin endpoint for Cognito users (Old UM) console.error(`[DUAL-AUTH] Authenticating Cognito user with /users/signin...`); authResponse = await this.authenticateCognito(validatedCredentials); } return authResponse; } catch (error: any) { console.error(`[DUAL-AUTH] Authentication failed for ${this.userManagementInfo.authMethod}: ${error.message}`); if (error.response) { console.error(`[DUAL-AUTH] HTTP Status: ${error.response.status}`); console.error(`[DUAL-AUTH] Response Data:`, JSON.stringify(error.response.data, null, 2)); } throw error; } } /** * Authenticate using Keycloak (User Management 2.0) * Uses the new /authentication/token/generate endpoint with Basic Auth */ private async authenticateKeycloak(credentials: AuthCredentials): Promise<AuthToken> { // Create Basic Auth header - this is what the authentication endpoint expects! const basicAuth = Buffer.from(`${credentials.username}:${credentials.password}`).toString('base64'); const response = await this.axiosInstance.post('/authentication/token/generate', { username: credentials.username, password: credentials.password }, { headers: { 'Authorization': `Basic ${basicAuth}`, 'Content-Type': 'application/json' } }); if (response.status === 200 && response.data.Authorization && response.data.apikey) { this.token = response.data.Authorization; // Build proper API key for Keycloak users const tempApiKey = response.data.apikey; const userKey = tempApiKey.split(':')[0]; const properApiKey = await this.buildProperApiKey(userKey, response.data.Authorization); this.userApiKey = properApiKey; this.tokenExpiry = Date.now() + (60 * 60 * 1000); console.error(`[DUAL-AUTH] Keycloak authentication successful`); return { Authorization: response.data.Authorization, apikey: properApiKey }; } else { throw new Error('Invalid response from Keycloak authentication endpoint'); } } /** * Authenticate using Cognito (Old User Management) * Uses the old /users/signin endpoint */ private async authenticateCognito(credentials: AuthCredentials): Promise<AuthToken> { const response = await this.axiosInstance.post('/users/signin', { username: credentials.username, password: credentials.password }); if (response.status === 200 && response.data.jwtToken) { this.token = response.data.jwtToken; // For Cognito, the response format is different // Extract userKey from JWT token payload const tokenPayload = JSON.parse(Buffer.from(response.data.jwtToken.split('.')[1], 'base64').toString()); const userKey = tokenPayload.username || tokenPayload['custom:username'] || tokenPayload.sub; // Build proper API key for Cognito users const properApiKey = await this.buildProperApiKey(userKey, response.data.jwtToken); this.userApiKey = properApiKey; this.tokenExpiry = Date.now() + (60 * 60 * 1000); console.error(`[DUAL-AUTH] Cognito authentication successful`); return { Authorization: response.data.jwtToken, apikey: properApiKey }; } else { throw new Error('Invalid response from Cognito authentication endpoint'); } } /** * Build proper API key format by getting user's account data */ private async buildProperApiKey(userKey: string, jwtToken: string): Promise<string> { try { // Store userKey for dynamic account switching this.userKey = userKey; const tempApiKey = `${userKey}:-1`; // Use different endpoints for different user management systems const accountsEndpoint = this.userManagementInfo?.isKeycloak ? '/user-management/accounts' // Keycloak users : '/users'; // Cognito users console.error(`[DUAL-AUTH] Getting account data from ${accountsEndpoint} for ${this.userManagementInfo?.authMethod} user`); const accountsResponse = await this.axiosInstance.get(accountsEndpoint, { headers: { 'Authorization': jwtToken, 'apikey': tempApiKey, 'Content-Type': 'application/json', 'Accept': 'application/json' } }); if (accountsResponse.status === 200 && accountsResponse.data) { let accounts = []; if (this.userManagementInfo?.isKeycloak) { // Keycloak: response.data is direct array of accounts accounts = accountsResponse.data; } else { // Cognito: response.data is user object with accounts array accounts = accountsResponse.data.accounts || []; } // Store all available accounts for dynamic switching this.availableAccounts = accounts; if (accounts.length > 0) { // Debug: List all available accounts console.error(`[DUAL-AUTH] 🔍 Available accounts:`); accounts.forEach((acc: any, index: number) => { const accountKey = acc.accountKey || acc.account_key || acc.accountId || acc.account_id || 'N/A'; const accountName = acc.accountName || acc.account_name || acc.name || 'N/A'; console.error(`[DUAL-AUTH] ${index + 1}. Key: ${accountKey}, Name: ${accountName}, Type: ${acc.cloudType || 'N/A'}`); }); // Look for account with key 9350 (known to have anomaly data) let targetAccount = accounts.find((acc: any) => { const accountKey = acc.accountKey || acc.account_key || acc.accountId || acc.account_id; const keyString = String(accountKey); console.error(`[DUAL-AUTH] 🔍 Checking account: ${accountKey} (${typeof accountKey}) === '9350'?`); return keyString === '9350' || accountKey === 9350; }); // If account 9350 not found, use first account as fallback if (!targetAccount) { targetAccount = accounts[0]; console.error(`[DUAL-AUTH] 🔍 Account 9350 not found in available accounts, using: ${targetAccount.accountKey || targetAccount.account_key || targetAccount.accountId || 'unknown'}`); } const accountKey = targetAccount.accountKey || targetAccount.account_key || targetAccount.accountId || targetAccount.account_id; const divisionId = 0; const properApiKey = `${userKey}:${accountKey}:${divisionId}`; console.error(`[DUAL-AUTH] Built proper API key for ${this.userManagementInfo?.authMethod}: ${properApiKey}`); if (accountKey === '9350') { console.error(`[DUAL-AUTH] ✅ Using account 9350 which has anomaly data!`); } else { console.error(`[DUAL-AUTH] ⚠️ Using account ${accountKey} (could not find 9350)`); } return properApiKey; } else { console.error('[DUAL-AUTH] No accounts found, using temporary API key'); return tempApiKey; } } else { console.error('[DUAL-AUTH] Invalid response from accounts endpoint, using temporary API key'); return tempApiKey; } } catch (error: any) { console.error(`[DUAL-AUTH] Failed to get account data: ${error.message}`); return `${userKey}:-1`; } } /** * Get current authentication headers for API requests */ getAuthHeaders(): AuthToken { if (!this.token || !this.userApiKey) { throw new Error('Not authenticated. Call authenticate() first.'); } if (Date.now() > this.tokenExpiry) { throw new Error('Token expired. Please re-authenticate.'); } return { Authorization: this.token, apikey: this.userApiKey }; } /** * Get user management system info */ getUserManagementInfo(): UserManagementInfo | null { return this.userManagementInfo; } /** * Check if the current token is still valid */ isAuthenticated(): boolean { return !!(this.token && this.userApiKey && Date.now() < this.tokenExpiry); } /** * Build API key for specific cloud context */ buildCloudContextApiKey(cloudContext: string): string | null { if (!this.userKey || this.availableAccounts.length === 0) { console.error('[DUAL-AUTH] Cannot build cloud context API key: missing userKey or accounts'); return null; } const normalizedContext = cloudContext.toLowerCase(); let targetAccount = null; console.error(`[DUAL-AUTH] 🔍 Building API key for cloud context: ${normalizedContext}`); // Find the appropriate account based on cloud context if (normalizedContext === 'aws') { // AWS: Look for account 9350 targetAccount = this.availableAccounts.find((acc: any) => { const accountKey = acc.accountKey || acc.account_key || acc.accountId || acc.account_id; const keyString = String(accountKey); return keyString === '9350' || accountKey === 9350; }); } else if (normalizedContext === 'gcp') { // GCP: Look for MasterBilling account (contains "Master", "21112", or "59f88c") targetAccount = this.availableAccounts.find((acc: any) => { const accountKey = acc.accountKey || acc.account_key || acc.accountId || acc.account_id; const accountName = acc.accountName || acc.account_name || acc.name || ''; const keyString = String(accountKey); return keyString === '21112' || accountKey === 21112 || accountName.toLowerCase().includes('master') || keyString.includes('59f88c'); }); } else if (normalizedContext === 'azure') { // Azure: Prioritize AzureAmortized (23105) which has more recent data, then fallback to others targetAccount = this.availableAccounts.find((acc: any) => { const accountKey = acc.accountKey || acc.account_key || acc.accountId || acc.account_id; const keyString = String(accountKey); // First priority: AzureAmortized (has 2025 data) return keyString === '23105' || accountKey === 23105; }); // If AzureAmortized not found, look for other Azure accounts if (!targetAccount) { targetAccount = this.availableAccounts.find((acc: any) => { const accountKey = acc.accountKey || acc.account_key || acc.accountId || acc.account_id; const accountName = acc.accountName || acc.account_name || acc.name || ''; const keyString = String(accountKey); // Check for other Azure accounts or "Azure" in name return keyString === '9347' || accountKey === 9347 || // Azure-Pileus accountName.toLowerCase().includes('azure'); }); } } if (!targetAccount) { console.error(`[DUAL-AUTH] ⚠️ No ${normalizedContext.toUpperCase()} account found, using default AWS account`); // Fallback to AWS account 9350 targetAccount = this.availableAccounts.find((acc: any) => { const accountKey = acc.accountKey || acc.account_key || acc.accountId || acc.account_id; const keyString = String(accountKey); return keyString === '9350' || accountKey === 9350; }); } if (!targetAccount) { console.error(`[DUAL-AUTH] ⚠️ No suitable account found, using first available account`); targetAccount = this.availableAccounts[0]; } const accountKey = targetAccount.accountKey || targetAccount.account_key || targetAccount.accountId || targetAccount.account_id; const divisionId = 0; const cloudApiKey = `${this.userKey}:${accountKey}:${divisionId}`; console.error(`[DUAL-AUTH] ✅ Built ${normalizedContext.toUpperCase()} API key: ${cloudApiKey}`); return cloudApiKey; } buildCustomerApiKey(customerAccountKey: string): string | null { if (!this.userKey || this.availableAccounts.length === 0) { console.error('[DUAL-AUTH] Cannot build customer API key: missing userKey or accounts'); return null; } console.error(`[DUAL-AUTH] 🔍 Building API key for customer account: ${customerAccountKey}`); // Find the specific customer account const targetAccount = this.availableAccounts.find((acc: any) => { const accountKey = acc.accountKey || acc.account_key || acc.accountId || acc.account_id; const keyString = String(accountKey); return keyString === customerAccountKey || accountKey === customerAccountKey; }); if (!targetAccount) { console.error(`[DUAL-AUTH] ⚠️ Customer account ${customerAccountKey} not found in available accounts`); return null; } const accountKey = targetAccount.accountKey || targetAccount.account_key || targetAccount.accountId || targetAccount.account_id; const accountName = targetAccount.accountName || targetAccount.account_name || targetAccount.name || ''; const divisionId = 0; const customerApiKey = `${this.userKey}:${accountKey}:${divisionId}`; console.error(`[DUAL-AUTH] ✅ Built customer API key for "${accountName}": ${customerApiKey}`); return customerApiKey; } /** * Get available accounts (for debugging) */ getAvailableAccounts(): any[] { return this.availableAccounts; } /** * Clear the stored authentication data */ clearAuth(): void { this.token = null; this.userApiKey = null; this.tokenExpiry = 0; this.userManagementInfo = null; this.userKey = null; this.availableAccounts = []; } }

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/daviddraiumbrella/invoice-monitoring'

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