- Add Agent, AgentCollection, AgentTool, UserApiKey, Conversation, Message models to Prisma schema - Create agent types and Zod schemas in @tpmjs/types - Implement AES-256 API key encryption utilities - Add CRUD API endpoints for agents, tools, collections, and user API keys - Create conversation streaming endpoint with SSE events - Build agent tool builder to merge collections and individual tools - Add dashboard pages: agents list, new agent form, agent detail/edit, chat interface - Add API keys settings page for managing provider keys - Add comprehensive Agents documentation section to /docs - Update navigation to include Agents link in header and mobile menu - Add new icons: terminal, puzzle, message, key, info, send Supported providers: OpenAI, Anthropic, Google, Groq, Mistral
70 lines
1.9 KiB
TypeScript
70 lines
1.9 KiB
TypeScript
import { createCipheriv, createDecipheriv, createHash, randomBytes } from 'node:crypto';
|
|
|
|
const ALGORITHM = 'aes-256-gcm';
|
|
|
|
function getEncryptionKey(): Buffer {
|
|
const secret = process.env.API_KEY_ENCRYPTION_SECRET;
|
|
if (!secret) {
|
|
throw new Error('API_KEY_ENCRYPTION_SECRET environment variable is not set');
|
|
}
|
|
return createHash('sha256').update(secret).digest();
|
|
}
|
|
|
|
/**
|
|
* Encrypts an API key using AES-256-GCM
|
|
*/
|
|
export function encryptApiKey(apiKey: string): { encrypted: string; iv: string } {
|
|
const key = getEncryptionKey();
|
|
const iv = randomBytes(16);
|
|
const cipher = createCipheriv(ALGORITHM, key, iv);
|
|
|
|
let encrypted = cipher.update(apiKey, 'utf8', 'hex');
|
|
encrypted += cipher.final('hex');
|
|
|
|
// Append the auth tag to the encrypted data
|
|
const authTag = cipher.getAuthTag().toString('hex');
|
|
encrypted += authTag;
|
|
|
|
return {
|
|
encrypted,
|
|
iv: iv.toString('hex'),
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Decrypts an API key using AES-256-GCM
|
|
*/
|
|
export function decryptApiKey(encrypted: string, iv: string): string {
|
|
const key = getEncryptionKey();
|
|
|
|
// Extract the auth tag (last 32 hex chars = 16 bytes)
|
|
const authTag = Buffer.from(encrypted.slice(-32), 'hex');
|
|
const encryptedData = encrypted.slice(0, -32);
|
|
|
|
const decipher = createDecipheriv(ALGORITHM, key, Buffer.from(iv, 'hex'));
|
|
decipher.setAuthTag(authTag);
|
|
|
|
let decrypted = decipher.update(encryptedData, 'hex', 'utf8');
|
|
decrypted += decipher.final('utf8');
|
|
|
|
return decrypted;
|
|
}
|
|
|
|
/**
|
|
* Creates a masked version of an API key for display (e.g., "sk-...XXXX")
|
|
*/
|
|
export function maskApiKey(apiKey: string): string {
|
|
if (apiKey.length <= 8) return '****';
|
|
|
|
const prefix = apiKey.slice(0, 4);
|
|
const suffix = apiKey.slice(-4);
|
|
return `${prefix}...${suffix}`;
|
|
}
|
|
|
|
/**
|
|
* Gets the last 4 characters of an API key for identification
|
|
*/
|
|
export function getKeyHint(apiKey: string): string {
|
|
if (apiKey.length < 4) return apiKey;
|
|
return apiKey.slice(-4);
|
|
}
|