Skip to main content
Glama
jakedx6
by jakedx6
auth.ts5.96 kB
import { apiClient } from './api-client.js' import { logger } from './logger.js' import { UnauthorizedError } from './api-client.js' export interface AuthContext { userId: string tenantId: string | null profile: any authenticated: boolean } export class AuthManager { private static instance: AuthManager private authContext: AuthContext | null = null private constructor() {} static getInstance(): AuthManager { if (!AuthManager.instance) { AuthManager.instance = new AuthManager() } return AuthManager.instance } /** * Authenticate with various methods */ async authenticate(method: 'token' | 'api_key', credentials: string): Promise<AuthContext> { try { switch (method) { case 'token': return await this.authenticateWithToken(credentials) case 'api_key': return await this.authenticateWithApiKey(credentials) default: throw new UnauthorizedError('Invalid authentication method') } } catch (error) { logger.error('Authentication failed:', error) throw error } } /** * Authenticate with user session token (legacy - not used with API) */ private async authenticateWithToken(accessToken: string): Promise<AuthContext> { throw new UnauthorizedError('Token authentication not supported in API mode. Use API key authentication.') } /** * Authenticate with API key via Helios-9 API */ private async authenticateWithApiKey(apiKey: string): Promise<AuthContext> { // Set the API key in the client process.env.HELIOS_API_KEY = apiKey // For MCP API keys (hel9_*), we skip the auth/validate endpoint // since MCP endpoints handle their own authentication if (apiKey.startsWith('hel9_')) { logger.info('MCP API key detected, using MCP authentication mode') // Create a context that will be populated from API responses this.authContext = { userId: 'mcp-pending', // Will be updated from first API response tenantId: null, profile: { id: 'mcp-pending', email: 'mcp@helios9.app', full_name: 'MCP Service Account', avatar_url: null, created_at: new Date().toISOString(), updated_at: new Date().toISOString(), username: 'mcp-service', tenant_id: null } as any, authenticated: true } logger.info('MCP authentication context created') return this.authContext! } // For other API keys, validate normally const profile = await apiClient.authenticate() this.authContext = { userId: profile.id, tenantId: profile.tenant_id || null, profile, authenticated: true } logger.info(`Service authenticated via API key for user: ${profile.email}`) return this.authContext! } /** * Get current auth context */ getAuthContext(): AuthContext { if (!this.authContext || !this.authContext.authenticated) { throw new UnauthorizedError('No authenticated session') } return this.authContext } /** * Check if authenticated */ isAuthenticated(): boolean { return this.authContext?.authenticated ?? false } /** * Ensure authenticated before API calls */ async ensureAuthenticated(): Promise<AuthContext> { if (this.isAuthenticated()) { return this.authContext! } // Try to authenticate with API key if available const apiKey = process.env.HELIOS_API_KEY if (!apiKey) { throw new UnauthorizedError('HELIOS_API_KEY environment variable is required') } return await this.authenticate('api_key', apiKey) } /** * Clear authentication */ clearAuth(): void { this.authContext = null logger.info('Authentication cleared') } /** * Middleware for MCP operations that require authentication */ requireAuth<T extends any[], R>( operation: (...args: T) => Promise<R> ): (...args: T) => Promise<R> { return async (...args: T): Promise<R> => { if (!this.isAuthenticated()) { throw new UnauthorizedError('Authentication required for this operation') } return operation(...args) } } /** * Get rate limiting info for current user */ getRateLimitInfo(): { limit: number; window: number; current: number } { // Basic rate limiting - in production, you'd implement proper rate limiting const isServiceAccount = this.authContext?.profile?.email?.includes('mcp-service') return { limit: isServiceAccount ? 1000 : 100, // requests per window window: 3600, // 1 hour in seconds current: 0 // would track actual usage } } /** * Validate permissions for specific operations */ async validatePermission(resource: string, action: string, resourceId?: string): Promise<boolean> { const context = this.getAuthContext() // Admin role can do everything if (context.profile.role === 'admin') { return true } // Basic permission checks switch (resource) { case 'project': return action === 'read' || action === 'create' || context.userId === resourceId case 'task': case 'document': // For tasks and documents, check project ownership through the API service return true // Simplified - actual checks happen in API service default: return false } } } // Export singleton instance export const authManager = AuthManager.getInstance() // Helper function for authentication export async function authenticate(method: 'token' | 'api_key', credentials: string): Promise<AuthContext> { return authManager.authenticate(method, credentials) } // Helper function to require authentication export function requireAuth<T extends any[], R>( operation: (...args: T) => Promise<R> ): (...args: T) => Promise<R> { return authManager.requireAuth(operation) }

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/jakedx6/helios9-MCP-Server'

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