/** * Manage user's third-party API keys (OpenAI, GitHub, etc.) */ import { NextResponse } from 'next/server'; import { getAdminAuth, getAdminDb } from '@/lib/firebase/admin'; import { FieldValue } from 'firebase-admin/firestore'; import * as crypto from 'crypto'; // Encryption helpers const ENCRYPTION_KEY = process.env.ENCRYPTION_KEY || 'vibn-default-encryption-key-change-me!!'; const ALGORITHM = 'aes-256-cbc'; function encrypt(text: string): { encrypted: string; iv: string } { const key = crypto.createHash('sha256').update(ENCRYPTION_KEY).digest(); const iv = crypto.randomBytes(16); const cipher = crypto.createCipheriv(ALGORITHM, key, iv); let encrypted = cipher.update(text, 'utf8', 'hex'); encrypted += cipher.final('hex'); return { encrypted, iv: iv.toString('hex') }; } function decrypt(encrypted: string, ivHex: string): string { const key = crypto.createHash('sha256').update(ENCRYPTION_KEY).digest(); const iv = Buffer.from(ivHex, 'hex'); const decipher = crypto.createDecipheriv(ALGORITHM, key, iv); let decrypted = decipher.update(encrypted, 'hex', 'utf8'); decrypted += decipher.final('utf8'); return decrypted; } // GET - List all keys (metadata only, not actual values) export async function GET(request: Request) { try { const authHeader = request.headers.get('Authorization'); if (!authHeader?.startsWith('Bearer ')) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); } const idToken = authHeader.split('Bearer ')[1]; const adminAuth = getAdminAuth(); const adminDb = getAdminDb(); let userId: string; try { const decodedToken = await adminAuth.verifyIdToken(idToken); userId = decodedToken.uid; } catch (error) { return NextResponse.json({ error: 'Invalid token' }, { status: 401 }); } const keysSnapshot = await adminDb .collection('userKeys') .where('userId', '==', userId) .get(); const keys = keysSnapshot.docs.map(doc => { const data = doc.data(); return { id: doc.id, service: data.service, name: data.name, createdAt: data.createdAt, lastUsed: data.lastUsed, // Don't send the actual key }; }); return NextResponse.json({ keys }); } catch (error) { console.error('Error fetching keys:', error); return NextResponse.json( { error: 'Failed to fetch keys', details: error instanceof Error ? error.message : String(error) }, { status: 500 } ); } } // POST - Add or update a key export async function POST(request: Request) { try { const authHeader = request.headers.get('Authorization'); if (!authHeader?.startsWith('Bearer ')) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); } const idToken = authHeader.split('Bearer ')[1]; const adminAuth = getAdminAuth(); const adminDb = getAdminDb(); let userId: string; try { const decodedToken = await adminAuth.verifyIdToken(idToken); userId = decodedToken.uid; } catch (error) { return NextResponse.json({ error: 'Invalid token' }, { status: 401 }); } const { service, name, keyValue } = await request.json(); if (!service || !keyValue) { return NextResponse.json({ error: 'Service and key value are required' }, { status: 400 }); } // Encrypt the key const { encrypted, iv } = encrypt(keyValue); // Check if key already exists for this service const existingKeysSnapshot = await adminDb .collection('userKeys') .where('userId', '==', userId) .where('service', '==', service) .limit(1) .get(); if (!existingKeysSnapshot.empty) { // Update existing key const keyDoc = existingKeysSnapshot.docs[0]; await keyDoc.ref.update({ name: name || service, encryptedKey: encrypted, iv, updatedAt: FieldValue.serverTimestamp(), }); return NextResponse.json({ success: true, message: `${service} key updated`, id: keyDoc.id, }); } else { // Create new key const keyRef = await adminDb.collection('userKeys').add({ userId, service, name: name || service, encryptedKey: encrypted, iv, createdAt: FieldValue.serverTimestamp(), updatedAt: FieldValue.serverTimestamp(), lastUsed: null, }); return NextResponse.json({ success: true, message: `${service} key added`, id: keyRef.id, }); } } catch (error) { console.error('Error saving key:', error); return NextResponse.json( { error: 'Failed to save key', details: error instanceof Error ? error.message : String(error) }, { status: 500 } ); } } // DELETE - Remove a key export async function DELETE(request: Request) { try { const authHeader = request.headers.get('Authorization'); if (!authHeader?.startsWith('Bearer ')) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); } const idToken = authHeader.split('Bearer ')[1]; const adminAuth = getAdminAuth(); const adminDb = getAdminDb(); let userId: string; try { const decodedToken = await adminAuth.verifyIdToken(idToken); userId = decodedToken.uid; } catch (error) { return NextResponse.json({ error: 'Invalid token' }, { status: 401 }); } const { service } = await request.json(); if (!service) { return NextResponse.json({ error: 'Service is required' }, { status: 400 }); } // Find and delete the key const keysSnapshot = await adminDb .collection('userKeys') .where('userId', '==', userId) .where('service', '==', service) .get(); if (keysSnapshot.empty) { return NextResponse.json({ error: 'Key not found' }, { status: 404 }); } const batch = adminDb.batch(); keysSnapshot.docs.forEach(doc => { batch.delete(doc.ref); }); await batch.commit(); return NextResponse.json({ success: true, message: `${service} key deleted`, }); } catch (error) { console.error('Error deleting key:', error); return NextResponse.json( { error: 'Failed to delete key', details: error instanceof Error ? error.message : String(error) }, { status: 500 } ); } }