@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
JavaScript
// 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