tpmjs/apps/web/src/lib/crypto/api-keys.ts
Ajax Davis 552f319583 feat: add AI Agents feature with multi-provider support and documentation
- 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
2026-01-02 20:08:52 +10:00

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);
}