Skip to main content
Glama
marco-looy
by marco-looy
oauth2-client.js6.23 kB
import { config } from '../config.js'; export class OAuth2Client { constructor(sessionConfig = null) { // Use session config if provided, otherwise fall back to environment config this.config = sessionConfig || config; // Determine authentication mode this.authMode = this.determineAuthMode(); // Token management this.accessToken = null; this.tokenExpiry = null; this.tokenExpiryBuffer = 5 * 60 * 1000; // 5 minutes buffer // Session-specific token cache key (for multi-session support) this.cacheKey = this.generateCacheKey(); // Initialize token for direct token mode if (this.authMode === 'token') { this.initializeDirectToken(); } } /** * Determine authentication mode based on configuration * @returns {string} 'oauth' or 'token' */ determineAuthMode() { const { pega } = this.config; // Check if we have a direct access token if (pega.accessToken) { return 'token'; } // Check if we have OAuth credentials if (pega.clientId && pega.clientSecret) { return 'oauth'; } // Default to oauth mode (for backward compatibility) return 'oauth'; } /** * Generate cache key for session-specific token storage * @returns {string} Cache key */ generateCacheKey() { if (this.config._sessionMeta) { return `session_${this.config._sessionMeta.sessionId}`; } return 'global'; // For environment-based authentication } /** * Initialize direct token from configuration */ initializeDirectToken() { const { pega } = this.config; if (pega.accessToken) { this.accessToken = pega.accessToken; // Set expiry if provided if (pega.tokenExpiry) { this.tokenExpiry = pega.tokenExpiry; } console.log(`🔐 Direct token initialized for ${this.cacheKey}`); } } /** * Get valid access token, refreshing if necessary */ async getAccessToken() { // For direct token mode, return the token directly if (this.authMode === 'token') { return this.getDirectToken(); } // For OAuth mode, check cache and refresh if needed if (this.accessToken && this.tokenExpiry && Date.now() < this.tokenExpiry - this.tokenExpiryBuffer) { return this.accessToken; } // Need to get a new token via OAuth return await this.fetchAccessToken(); } /** * Get direct access token with expiry validation * @returns {string} Access token * @throws {Error} If token is missing or expired */ getDirectToken() { if (!this.accessToken) { throw new Error('Direct access token not available'); } // Check token expiry if provided if (this.tokenExpiry && Date.now() > this.tokenExpiry) { throw new Error('Direct access token has expired'); } return this.accessToken; } /** * Fetch a new access token using OAuth2 Client Credentials flow */ async fetchAccessToken() { if (this.authMode === 'token') { throw new Error('Cannot fetch token in direct token mode'); } try { const { pega } = this.config; const tokenUrl = pega.tokenUrl; const clientId = pega.clientId; const clientSecret = pega.clientSecret; if (!tokenUrl || !clientId || !clientSecret) { const configSource = this.config._sessionMeta ? 'session configuration' : 'environment variables'; throw new Error(`OAuth2 configuration incomplete in ${configSource}. Need baseUrl, clientId, and clientSecret.`); } const response = await fetch(tokenUrl, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Authorization': `Basic ${Buffer.from(`${clientId}:${clientSecret}`).toString('base64')}` }, body: 'grant_type=client_credentials', timeout: 30000 }); if (!response.ok) { const errorText = await response.text(); throw new Error(`OAuth2 token request failed: ${response.status} ${response.statusText} - ${errorText}`); } const tokenData = await response.json(); if (!tokenData.access_token) { throw new Error('OAuth2 response missing access_token'); } // Cache the token this.accessToken = tokenData.access_token; // Calculate expiry time (default to 1 hour if not provided) const expiresIn = tokenData.expires_in || 3600; this.tokenExpiry = Date.now() + (expiresIn * 1000); return this.accessToken; } catch (error) { // Clear cached token on error this.clearTokenCache(); if (error.name === 'TypeError' && error.message.includes('fetch')) { throw new Error(`Failed to connect to OAuth2 endpoint: ${this.config.pega.tokenUrl}`); } throw error; } } /** * Clear cached token (used when token becomes invalid) */ clearTokenCache() { this.accessToken = null; this.tokenExpiry = null; } /** * Explicitly set access token (useful for direct token provisioning) * @param {string} token - Access token to store * @param {number} expiresInSeconds - Token expiry in seconds from now (default: 3600) */ setAccessToken(token, expiresInSeconds = 3600) { if (!token || typeof token !== 'string') { throw new Error('Invalid token: must be a non-empty string'); } this.accessToken = token; this.tokenExpiry = Date.now() + (expiresInSeconds * 1000); // Update auth mode to token if not already set if (this.authMode !== 'token') { this.authMode = 'token'; } console.log(`🔐 Access token explicitly set for ${this.cacheKey} (expires in ${expiresInSeconds}s)`); } /** * Get token info for debugging */ getTokenInfo() { return { authMode: this.authMode, cacheKey: this.cacheKey, hasToken: !!this.accessToken, tokenExpiry: this.tokenExpiry, isExpired: this.tokenExpiry ? Date.now() > this.tokenExpiry : (this.authMode === 'token'), expiresInMinutes: this.tokenExpiry ? Math.round((this.tokenExpiry - Date.now()) / (1000 * 60)) : 0, configSource: this.config._sessionMeta ? 'session' : 'environment' }; } }

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/marco-looy/pega-dx-mcp'

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