UNPKG

@fastmcp-oauth/rest-api-delegation

Version:

REST API delegation module for FastMCP OAuth framework - HTTP/JSON API integration with token exchange support

218 lines 7.25 kB
// src/rest-api-module.ts var RestAPIDelegationModule = class { name; type = "api"; config = null; tokenExchangeService; tokenExchangeConfig; /** * Create a new REST API delegation module * * @param name - Module name (e.g., 'rest-api', 'rest-api1', 'rest-api2') * Defaults to 'rest-api' for backward compatibility */ constructor(name = "rest-api") { this.name = name; } /** * Initialize module with configuration */ async initialize(config) { this.config = config; console.log(`[RestAPI:${this.name}] Module initialized: ${config.baseUrl}`); console.log(`[RestAPI:${this.name}] Token exchange: ${config.useTokenExchange ? "enabled" : "disabled"}`); if (!config.useTokenExchange && !config.apiKey) { console.warn(`[RestAPI:${this.name}] WARNING: Neither token exchange nor API key configured - authentication may fail`); } } /** * Set token exchange service (called by ConfigOrchestrator) */ setTokenExchangeService(service, config) { console.log(`[RestAPI:${this.name}] Token exchange service configured`); this.tokenExchangeService = service; this.tokenExchangeConfig = config; } /** * Delegate action to REST API * * @param session - User session from authentication * @param action - Action name (used as endpoint if not overridden in params) * @param params - Parameters for the API request * @param params.endpoint - API endpoint (overrides action) * @param params.method - HTTP method (default: 'POST') * @param params.data - Request body data (for POST/PUT/PATCH) * @param params.query - Query parameters (for GET) * @param params.headers - Additional headers * @param context - Request context (sessionId, coreContext) * * @example * ```typescript * const result = await module.delegate(session, 'users/profile', { * endpoint: 'users/123/profile', * method: 'GET' * }, { sessionId, coreContext }); * ``` */ async delegate(session, action, params, context) { if (!this.config) { throw new Error("RestAPIDelegationModule not initialized"); } const auditEntry = { timestamp: /* @__PURE__ */ new Date(), source: `delegation:${this.name}`, userId: session.userId, action: `${this.name}:${action}`, success: false, metadata: { action, params } }; try { let authHeader; if (this.config.useTokenExchange && this.tokenExchangeService) { console.log(`[RestAPI:${this.name}] Using token exchange for authentication`); const delegationToken = await this.performTokenExchange(session, context); authHeader = `Bearer ${delegationToken}`; auditEntry.metadata = { ...auditEntry.metadata, authMethod: "token-exchange" }; } else if (this.config.apiKey) { console.log(`[RestAPI:${this.name}] Using API key for authentication`); authHeader = `Bearer ${this.config.apiKey}`; auditEntry.metadata = { ...auditEntry.metadata, authMethod: "api-key" }; } else { throw new Error("No authentication method configured (need token exchange or API key)"); } const endpoint = params.endpoint || action; const method = params.method || "POST"; const url = `${this.config.baseUrl}/${endpoint}`; console.log(`[RestAPI:${this.name}] ${method} ${url}`); const headers = { "Content-Type": "application/json", "Authorization": authHeader, "X-User-ID": session.userId, "X-User-Role": session.role, ...this.config.defaultHeaders, ...params.headers }; const requestOptions = { method, headers }; if (method !== "GET" && params.data) { requestOptions.body = JSON.stringify(params.data); } if (this.config.timeout) { const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), this.config.timeout); requestOptions.signal = controller.signal; try { const response = await fetch(url, requestOptions); clearTimeout(timeoutId); return await this.handleResponse(response, auditEntry, endpoint, method); } catch (error) { clearTimeout(timeoutId); throw error; } } else { const response = await fetch(url, requestOptions); return await this.handleResponse(response, auditEntry, endpoint, method); } } catch (error) { auditEntry.error = error instanceof Error ? error.message : "Unknown error"; console.error(`[RestAPI:${this.name}] Error:`, auditEntry.error); return { success: false, error: auditEntry.error, auditTrail: auditEntry }; } } /** * Handle API response */ async handleResponse(response, auditEntry, endpoint, method) { if (!response.ok) { const errorBody = await response.text(); throw new Error(`API error: ${response.status} ${response.statusText} - ${errorBody}`); } const contentType = response.headers.get("content-type"); let data; if (contentType?.includes("application/json")) { data = await response.json(); } else if (response.status === 204) { data = null; } else { data = await response.text(); } auditEntry.success = true; auditEntry.metadata = { ...auditEntry.metadata, statusCode: response.status, endpoint, method }; return { success: true, data, auditTrail: auditEntry }; } /** * Perform token exchange to get API-specific JWT */ async performTokenExchange(session, context) { if (!this.tokenExchangeService) { throw new Error("TokenExchangeService not available"); } const requestorJWT = session.claims?.access_token; if (!requestorJWT) { throw new Error("Session missing access_token for token exchange"); } const delegationToken = await this.tokenExchangeService.performExchange({ requestorJWT, audience: this.config?.tokenExchangeAudience || "urn:api:rest", scope: "api:read api:write", sessionId: context?.sessionId // Enable token caching }); return delegationToken; } /** * Validate that a session has access to this module */ async validateAccess(session) { if (!session || !session.userId) { return false; } return true; } /** * Health check - verify API is accessible */ async healthCheck() { if (!this.config) { return false; } try { const response = await fetch(`${this.config.baseUrl}/health`, { method: "GET", headers: this.config.apiKey ? { "Authorization": `Bearer ${this.config.apiKey}` } : {} }); return response.ok; } catch (error) { console.error(`[RestAPI:${this.name}] Health check failed:`, error); return false; } } /** * Cleanup resources */ async destroy() { console.log(`[RestAPI:${this.name}] Module destroyed`); this.config = null; this.tokenExchangeService = void 0; this.tokenExchangeConfig = void 0; } }; export { RestAPIDelegationModule }; //# sourceMappingURL=index.js.map