UNPKG

tryaii-mcp-server

Version:

TryAII MCP Server - 15+ AI models with comparison, cost tracking, and collective intelligence

199 lines 8.4 kB
import { databaseService } from './database.js'; import { logger } from '../utils/logger.js'; import { ObjectId } from 'mongodb'; import { verifyApiKey, isValidApiKeyFormat, sanitizeApiKeyForLog } from '../utils/apiKeyUtils.js'; export class CrossDatabaseService { /** * Validate API key and return authenticated user information */ async validateApiKey(apiKey) { try { const startTime = Date.now(); // Validate API key format first if (!isValidApiKeyFormat(apiKey)) { logger.warn('CrossDatabaseService: Invalid API key format', { keyPrefix: sanitizeApiKeyForLog(apiKey) }); return null; } // Extract key prefix for faster lookup const keyPrefix = apiKey.substring(0, 8); // Get connection to main database (tryaii_dev) const mainConnection = databaseService.getMainConnection(); // Find ALL active API keys with matching prefix const ApiKeyCollection = mainConnection.collection('userapikeys'); const potentialKeys = await ApiKeyCollection.find({ keyPrefix, isActive: true, $or: [ { expiresAt: { $exists: false } }, { expiresAt: { $gt: new Date() } } ] }).toArray(); if (potentialKeys.length === 0) { logger.warn('CrossDatabaseService: No active API keys found with prefix', { keyPrefix }); return null; } // Verify hash against each potential key (there should normally be only one) let authenticatedKey = null; for (const keyDoc of potentialKeys) { if (verifyApiKey(apiKey, keyDoc.keyHash)) { authenticatedKey = keyDoc; break; } } if (!authenticatedKey) { logger.warn('CrossDatabaseService: API key hash verification failed', { keyPrefix, potentialMatches: potentialKeys.length }); return null; } // Update last used timestamp (fire and forget) ApiKeyCollection.updateOne({ _id: authenticatedKey._id }, { $set: { lastUsed: new Date() } }).catch((error) => logger.warn('CrossDatabaseService: Failed to update lastUsed', { error, keyId: authenticatedKey.keyId })); // Get user details from the users collection const UserCollection = mainConnection.collection('users'); const user = await UserCollection.findOne({ _id: authenticatedKey.userId }); if (!user) { logger.warn('CrossDatabaseService: User not found', { userId: authenticatedKey.userId }); return null; } const authTime = Date.now() - startTime; logger.debug('CrossDatabaseService: API key authenticated successfully', { userId: authenticatedKey.userId, keyId: authenticatedKey.keyId, keyPrefix, authTime: `${authTime}ms` }); return { keyId: authenticatedKey.keyId, userId: authenticatedKey.userId, keyPrefix: authenticatedKey.keyPrefix, permissions: authenticatedKey.permissions || [], isActive: authenticatedKey.isActive, userName: user?.name, userEmail: user?.email, keyDocument: authenticatedKey }; } catch (error) { logger.error('CrossDatabaseService: Error validating API key', { error: error instanceof Error ? error.message : String(error), keyPrefix: sanitizeApiKeyForLog(apiKey) }); return null; } } /** * Update usage statistics for an API key */ async updateUsageStats(keyId, updates) { try { const mainConnection = databaseService.getMainConnection(); const ApiKeyCollection = mainConnection.collection('userapikeys'); const updateDoc = { updatedAt: new Date() }; const incDoc = {}; if (updates.totalRequests !== undefined) { incDoc['usageStats.totalRequests'] = updates.totalRequests; } if (updates.totalCost !== undefined) { incDoc['usageStats.totalCost'] = updates.totalCost; } if (updates.lastUsed !== undefined) { updateDoc.lastUsed = updates.lastUsed; } const result = await ApiKeyCollection.updateOne({ keyId }, { ...(Object.keys(incDoc).length > 0 ? { $inc: incDoc } : {}), $set: updateDoc }); if (result.matchedCount === 0) { logger.warn('CrossDatabaseService: API key not found for usage update', { keyId }); return false; } logger.debug('CrossDatabaseService: Usage stats updated', { keyId, updates }); return true; } catch (error) { logger.error('CrossDatabaseService: Error updating usage stats', { error, keyId }); return false; } } /** * Create or update session in MCP database with real user data */ async createOrUpdateSession(sessionData) { try { const mcpConnection = databaseService.getMcpConnection(); const collectionName = `mcp_${sessionData.sessionType}sessions`; const SessionCollection = mcpConnection.collection(collectionName); const sessionDoc = { userId: sessionData.userId, apiKeyId: sessionData.apiKeyId, createdAt: new Date(), updatedAt: new Date(), status: 'active', ...sessionData.metadata }; const result = await SessionCollection.insertOne(sessionDoc); logger.debug('CrossDatabaseService: Session created', { sessionId: result.insertedId, userId: sessionData.userId, apiKeyId: sessionData.apiKeyId, sessionType: sessionData.sessionType }); return result.insertedId.toString(); } catch (error) { logger.error('CrossDatabaseService: Error creating session', { error, sessionData }); return null; } } /** * Get session by ID from MCP database */ async getSession(sessionId, sessionType) { try { const mcpConnection = databaseService.getMcpConnection(); const collectionName = `mcp_${sessionType}sessions`; const SessionCollection = mcpConnection.collection(collectionName); const session = await SessionCollection.findOne({ _id: new ObjectId(sessionId) }); return session; } catch (error) { logger.error('CrossDatabaseService: Error getting session', { error, sessionId, sessionType }); return null; } } /** * Test connections to both databases */ async testConnections() { const results = { main: false, mcp: false }; try { const mainConnection = databaseService.getMainConnection(); await mainConnection.db.admin().ping(); results.main = true; logger.debug('CrossDatabaseService: Main database connection OK'); } catch (error) { logger.error('CrossDatabaseService: Main database connection failed', { error }); } try { const mcpConnection = databaseService.getMcpConnection(); await mcpConnection.db.admin().ping(); results.mcp = true; logger.debug('CrossDatabaseService: MCP database connection OK'); } catch (error) { logger.error('CrossDatabaseService: MCP database connection failed', { error }); } return results; } } // Create singleton instance export const crossDatabaseService = new CrossDatabaseService(); //# sourceMappingURL=CrossDatabaseService.js.map