Skip to main content
Glama
auth.ts5.59 kB
import axios, { AxiosInstance } from 'axios'; import { AuthCredentials, AuthToken, AuthCredentialsSchema, AuthenticationResponse } from './types.js'; export class UmbrellaAuth { private baseURL: string; private axiosInstance: AxiosInstance; private token: string | null = null; private userApiKey: string | null = null; private tokenExpiry: number = 0; constructor(baseURL: string) { this.baseURL = baseURL; this.axiosInstance = axios.create({ baseURL: this.baseURL, timeout: 30000, headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' } }); } async authenticate(credentials: AuthCredentials): Promise<AuthToken> { // Validate credentials const validatedCredentials = AuthCredentialsSchema.parse(credentials); try { // Try the authentication endpoint that matches the frontend-app-server const response = await this.axiosInstance.post('/authentication/token/generate', { username: validatedCredentials.username, password: validatedCredentials.password }); if (response.status === 200 && response.data.Authorization && response.data.apikey) { this.token = response.data.Authorization; // CRITICAL FIX: Build proper API key format by getting user's account data const tempApiKey = response.data.apikey; // This is userKey:-1 const userKey = tempApiKey.split(':')[0]; // Get user's actual account information const properApiKey = await this.buildProperApiKey(userKey, response.data.Authorization); this.userApiKey = properApiKey; // Set token expiry to 1 hour from now (typical JWT expiry) this.tokenExpiry = Date.now() + (60 * 60 * 1000); return { Authorization: response.data.Authorization }; } else { throw new Error('Invalid response from authentication endpoint'); } } catch (error: any) { if (error.response) { // Server responded with error status const status = error.response.status; const message = error.response.data?.message || error.response.statusText || 'Authentication failed'; if (status === 401) { throw new Error(`Authentication failed: Invalid credentials. ${message}`); } else if (status === 429) { throw new Error(`Authentication failed: Too many requests. Please wait and try again. ${message}`); } else { throw new Error(`Authentication failed: Server error (${status}). ${message}`); } } else if (error.request) { // No response received throw new Error('Authentication failed: Unable to connect to Umbrella Cost API. Please check your network connection.'); } else { // Request setup error throw new Error(`Authentication failed: ${error.message}`); } } } /** * Build proper API key format by getting user's account data * Format: userKey:accountKey:divisionId */ private async buildProperApiKey(userKey: string, jwtToken: string): Promise<string> { try { // Get user's accounts using the temporary API key with JWT const tempApiKey = `${userKey}:-1`; // Use temp key to get accounts const accountsResponse = await this.axiosInstance.get('/user-management/accounts', { headers: { 'Authorization': jwtToken, 'apikey': tempApiKey, 'Content-Type': 'application/json', 'Accept': 'application/json' } }); if (accountsResponse.status === 200 && accountsResponse.data && accountsResponse.data.length > 0) { // Use the first account (primary account) const firstAccount = accountsResponse.data[0]; const accountKey = firstAccount.accountKey || firstAccount.accountId; const divisionId = 0; // Default division as seen in frontend code const properApiKey = `${userKey}:${accountKey}:${divisionId}`; console.error(`[AUTH] Built proper API key: ${properApiKey}`); return properApiKey; } else { console.error('[AUTH] No accounts found, using temporary API key'); return tempApiKey; // Fallback to temp key } } catch (error: any) { console.error(`[AUTH] Failed to get account data: ${error.message}`); return `${userKey}:-1`; // Fallback to original format } } /** * 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 // Raw JWT token, not Bearer }; } /** * Check if the current token is still valid */ isAuthenticated(): boolean { return !!(this.token && this.userApiKey && Date.now() < this.tokenExpiry); } /** * Clear the stored authentication data */ clearAuth(): void { this.token = null; this.userApiKey = null; this.tokenExpiry = 0; } async validateToken(token: string): Promise<boolean> { try { // Try to make a simple request with the token to validate it const response = await this.axiosInstance.get('/health', { headers: { 'Authorization': token // Raw JWT token } }); return response.status === 200; } catch { return false; } } }

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