VIBN Frontend for Coolify deployment

This commit is contained in:
2026-02-15 19:25:52 -08:00
commit 40bf8428cd
398 changed files with 76513 additions and 0 deletions

214
app/api/keys/route.ts Normal file
View File

@@ -0,0 +1,214 @@
/**
* 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 }
);
}
}