import crypto from "crypto"; const API_KEY_SECRET = process.env.API_KEY_SECRET || "endpoint-proxy-api-key-secret"; /** * Generate 6-char random keyId */ function generateKeyId() { const chars = "abcdefghijklmnopqrstuvwxyz0123456789"; let result = ""; for (let i = 0; i < 6; i++) { result += chars.charAt(Math.floor(Math.random() * chars.length)); } return result; } /** * Generate CRC (8-char HMAC) */ function generateCrc(machineId, keyId) { return crypto .createHmac("sha256", API_KEY_SECRET) .update(machineId + keyId) .digest("hex") .slice(0, 8); } /** * Generate API key with machineId embedded * Format: sk-{machineId}-{keyId}-{crc8} * @param {string} machineId - 16-char machine ID * @returns {{ key: string, keyId: string }} */ export function generateApiKeyWithMachine(machineId) { const keyId = generateKeyId(); const crc = generateCrc(machineId, keyId); const key = `sk-${machineId}-${keyId}-${crc}`; return { key, keyId }; } /** * Parse API key and extract machineId + keyId * Supports both formats: * - New: sk-{machineId}-{keyId}-{crc8} * - Old: sk-{random8} * @param {string} apiKey * @returns {{ machineId: string, keyId: string, isNewFormat: boolean } | null} */ export function parseApiKey(apiKey) { if (!apiKey || !apiKey.startsWith("sk-")) return null; const parts = apiKey.split("-"); // New format: sk-{machineId}-{keyId}-{crc8} = 4 parts if (parts.length === 4) { const [, machineId, keyId, crc] = parts; // Validate CRC const expectedCrc = generateCrc(machineId, keyId); if (crc !== expectedCrc) return null; return { machineId, keyId, isNewFormat: true }; } // Old format: sk-{random8} = 2 parts if (parts.length === 2) { return { machineId: null, keyId: parts[1], isNewFormat: false }; } return null; } /** * Verify API key CRC (only for new format) * @param {string} apiKey * @returns {boolean} */ export function verifyApiKeyCrc(apiKey) { const parsed = parseApiKey(apiKey); if (!parsed) return false; // Old format doesn't have CRC, always valid if parsed if (!parsed.isNewFormat) return true; // New format already verified in parseApiKey return true; } /** * Check if API key is new format (contains machineId) * @param {string} apiKey * @returns {boolean} */ export function isNewFormatKey(apiKey) { const parsed = parseApiKey(apiKey); return parsed?.isNewFormat === true; }