import { IRequest } from 'itty-router'; import { ApiKeyManager } from '../modules/ApiKeyManager'; import { Env } from '../types'; import { getAuthContext } from '../middleware/auth'; import { libx } from 'libx.js/build/bundles/essentials.js'; // CORS headers const CORS_HEADERS = { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS', 'Access-Control-Allow-Headers': 'Content-Type, Authorization' }; export const apiKeysController = { /** * Create a new API key * POST /v1/apikeys */ async createApiKey(req: IRequest, env: Env): Promise { try { const body = await req.json(); const { userId, name, description, providerKeys, rateLimitRpm, rateLimitTpm, metadata } = body; if (!userId) { return new Response(JSON.stringify({ error: 'userId is required' }), { status: 400, headers: CORS_HEADERS }); } if (!env.ASK_DB) { return new Response(JSON.stringify({ error: 'Database not configured' }), { status: 500, headers: CORS_HEADERS }); } const encryptionKey = env.API_KEY_ENCRYPTION_KEY || 'default-encryption-key-change-in-production'; const keyManager = new ApiKeyManager(env.ASK_DB, encryptionKey); const apiKeyData = await keyManager.createApiKey({ userId, name, description, providerKeys, rateLimitRpm, rateLimitTpm, metadata }); // Don't return provider keys in response for security return new Response(JSON.stringify({ success: true, data: { apiKey: apiKeyData.apiKey, id: apiKeyData.id, userId: apiKeyData.userId, name: apiKeyData.name, description: apiKeyData.description, createdAt: apiKeyData.createdAt, rateLimitRpm: apiKeyData.rateLimitRpm, rateLimitTpm: apiKeyData.rateLimitTpm, providers: Object.keys(providerKeys || {}) } }), { status: 201, headers: CORS_HEADERS }); } catch (error: any) { libx.log.e('ApiKeysController: Error creating API key', error); return new Response(JSON.stringify({ error: error.message || 'Failed to create API key' }), { status: 500, headers: CORS_HEADERS }); } }, /** * List all API keys for a user * GET /v1/apikeys?userId= */ async listApiKeys(req: IRequest, env: Env): Promise { try { const url = new URL(req.url); const userId = url.searchParams.get('userId'); if (!userId) { return new Response(JSON.stringify({ error: 'userId query parameter is required' }), { status: 400, headers: CORS_HEADERS }); } if (!env.ASK_DB) { return new Response(JSON.stringify({ error: 'Database not configured' }), { status: 500, headers: CORS_HEADERS }); } const encryptionKey = env.API_KEY_ENCRYPTION_KEY || 'default-encryption-key-change-in-production'; const keyManager = new ApiKeyManager(env.ASK_DB, encryptionKey); const apiKeys = await keyManager.getApiKeysByUser(userId); // Redact sensitive information const safeKeys = apiKeys.map(key => ({ id: key.id, apiKey: key.apiKey.substring(0, 12) + '...', userId: key.userId, name: key.name, description: key.description, createdAt: key.createdAt, updatedAt: key.updatedAt, lastUsedAt: key.lastUsedAt, isActive: key.isActive, usageCount: key.usageCount, rateLimitRpm: key.rateLimitRpm, rateLimitTpm: key.rateLimitTpm })); return new Response(JSON.stringify({ success: true, data: safeKeys }), { status: 200, headers: CORS_HEADERS }); } catch (error: any) { libx.log.e('ApiKeysController: Error listing API keys', error); return new Response(JSON.stringify({ error: error.message || 'Failed to list API keys' }), { status: 500, headers: CORS_HEADERS }); } }, /** * Get API key details * GET /v1/apikeys/:apiKey */ async getApiKey(req: IRequest, env: Env): Promise { try { const apiKey = req.params?.apiKey; if (!apiKey) { return new Response(JSON.stringify({ error: 'API key parameter is required' }), { status: 400, headers: CORS_HEADERS }); } if (!env.ASK_DB) { return new Response(JSON.stringify({ error: 'Database not configured' }), { status: 500, headers: CORS_HEADERS }); } const encryptionKey = env.API_KEY_ENCRYPTION_KEY || 'default-encryption-key-change-in-production'; const keyManager = new ApiKeyManager(env.ASK_DB, encryptionKey); const apiKeyData = await keyManager.getApiKey(apiKey); if (!apiKeyData) { return new Response(JSON.stringify({ error: 'API key not found' }), { status: 404, headers: CORS_HEADERS }); } // Get provider keys count (not the actual keys) const providerKeys = await keyManager.getProviderKeys(apiKey); return new Response(JSON.stringify({ success: true, data: { id: apiKeyData.id, apiKey: apiKey.substring(0, 12) + '...', userId: apiKeyData.userId, name: apiKeyData.name, description: apiKeyData.description, createdAt: apiKeyData.createdAt, updatedAt: apiKeyData.updatedAt, lastUsedAt: apiKeyData.lastUsedAt, isActive: apiKeyData.isActive, usageCount: apiKeyData.usageCount, rateLimitRpm: apiKeyData.rateLimitRpm, rateLimitTpm: apiKeyData.rateLimitTpm, providers: Object.keys(providerKeys) } }), { status: 200, headers: CORS_HEADERS }); } catch (error: any) { libx.log.e('ApiKeysController: Error getting API key', error); return new Response(JSON.stringify({ error: error.message || 'Failed to get API key' }), { status: 500, headers: CORS_HEADERS }); } }, /** * Revoke (deactivate) an API key * DELETE /v1/apikeys/:apiKey */ async revokeApiKey(req: IRequest, env: Env): Promise { try { const apiKey = req.params?.apiKey; const body = await req.json(); const { userId } = body; if (!apiKey || !userId) { return new Response(JSON.stringify({ error: 'API key and userId are required' }), { status: 400, headers: CORS_HEADERS }); } if (!env.ASK_DB) { return new Response(JSON.stringify({ error: 'Database not configured' }), { status: 500, headers: CORS_HEADERS }); } const encryptionKey = env.API_KEY_ENCRYPTION_KEY || 'default-encryption-key-change-in-production'; const keyManager = new ApiKeyManager(env.ASK_DB, encryptionKey); const success = await keyManager.revokeApiKey(apiKey, userId); if (!success) { return new Response(JSON.stringify({ error: 'API key not found or already revoked' }), { status: 404, headers: CORS_HEADERS }); } return new Response(JSON.stringify({ success: true, message: 'API key revoked successfully' }), { status: 200, headers: CORS_HEADERS }); } catch (error: any) { libx.log.e('ApiKeysController: Error revoking API key', error); return new Response(JSON.stringify({ error: error.message || 'Failed to revoke API key' }), { status: 500, headers: CORS_HEADERS }); } }, /** * Add or update a provider key * POST /v1/apikeys/:apiKey/providers */ async addProviderKey(req: IRequest, env: Env): Promise { try { const apiKey = req.params?.apiKey; const body = await req.json(); const { userId, provider, providerApiKey } = body; if (!apiKey || !userId || !provider || !providerApiKey) { return new Response(JSON.stringify({ error: 'API key, userId, provider, and providerApiKey are required' }), { status: 400, headers: CORS_HEADERS }); } if (!env.ASK_DB) { return new Response(JSON.stringify({ error: 'Database not configured' }), { status: 500, headers: CORS_HEADERS }); } const encryptionKey = env.API_KEY_ENCRYPTION_KEY || 'default-encryption-key-change-in-production'; const keyManager = new ApiKeyManager(env.ASK_DB, encryptionKey); // Verify the API key belongs to the user const apiKeyData = await keyManager.getApiKey(apiKey); if (!apiKeyData || apiKeyData.userId !== userId) { return new Response(JSON.stringify({ error: 'API key not found or access denied' }), { status: 403, headers: CORS_HEADERS }); } await keyManager.addProviderKey(apiKeyData.id, provider, providerApiKey); return new Response(JSON.stringify({ success: true, message: `Provider key for ${provider} added successfully` }), { status: 200, headers: CORS_HEADERS }); } catch (error: any) { libx.log.e('ApiKeysController: Error adding provider key', error); return new Response(JSON.stringify({ error: error.message || 'Failed to add provider key' }), { status: 500, headers: CORS_HEADERS }); } }, /** * Remove a provider key * DELETE /v1/apikeys/:apiKey/providers/:provider */ async removeProviderKey(req: IRequest, env: Env): Promise { try { const apiKey = req.params?.apiKey; const provider = req.params?.provider; const body = await req.json(); const { userId } = body; if (!apiKey || !provider || !userId) { return new Response(JSON.stringify({ error: 'API key, provider, and userId are required' }), { status: 400, headers: CORS_HEADERS }); } if (!env.ASK_DB) { return new Response(JSON.stringify({ error: 'Database not configured' }), { status: 500, headers: CORS_HEADERS }); } const encryptionKey = env.API_KEY_ENCRYPTION_KEY || 'default-encryption-key-change-in-production'; const keyManager = new ApiKeyManager(env.ASK_DB, encryptionKey); const success = await keyManager.removeProviderKey(apiKey, provider, userId); if (!success) { return new Response(JSON.stringify({ error: 'Provider key not found' }), { status: 404, headers: CORS_HEADERS }); } return new Response(JSON.stringify({ success: true, message: `Provider key for ${provider} removed successfully` }), { status: 200, headers: CORS_HEADERS }); } catch (error: any) { libx.log.e('ApiKeysController: Error removing provider key', error); return new Response(JSON.stringify({ error: error.message || 'Failed to remove provider key' }), { status: 500, headers: CORS_HEADERS }); } }, /** * Get usage statistics * GET /v1/apikeys/:apiKey/usage */ async getUsageStats(req: IRequest, env: Env): Promise { try { const apiKey = req.params?.apiKey; const url = new URL(req.url); const startTime = url.searchParams.get('startTime'); const endTime = url.searchParams.get('endTime'); if (!apiKey) { return new Response(JSON.stringify({ error: 'API key parameter is required' }), { status: 400, headers: CORS_HEADERS }); } if (!env.ASK_DB) { return new Response(JSON.stringify({ error: 'Database not configured' }), { status: 500, headers: CORS_HEADERS }); } const encryptionKey = env.API_KEY_ENCRYPTION_KEY || 'default-encryption-key-change-in-production'; const keyManager = new ApiKeyManager(env.ASK_DB, encryptionKey); const stats = await keyManager.getUsageStats( apiKey, startTime ? parseInt(startTime) : undefined, endTime ? parseInt(endTime) : undefined ); if (!stats) { return new Response(JSON.stringify({ error: 'API key not found' }), { status: 404, headers: CORS_HEADERS }); } return new Response(JSON.stringify({ success: true, data: stats }), { status: 200, headers: CORS_HEADERS }); } catch (error: any) { libx.log.e('ApiKeysController: Error getting usage stats', error); return new Response(JSON.stringify({ error: error.message || 'Failed to get usage statistics' }), { status: 500, headers: CORS_HEADERS }); } } };