mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-01 12:52:11 +03:00
OmniRoute is an intelligent API gateway that unifies 20+ AI providers behind a single OpenAI-compatible endpoint. Features include intelligent routing with 6 strategies, multi-format translation (OpenAI/Claude/Gemini/Responses API), circuit breakers, semantic caching, combo fallback chains, real-time health monitoring, and a full dashboard with provider management, analytics, and CLI tool integration. Key highlights: - 20+ providers (Claude Code, Codex, Gemini CLI, GitHub Copilot, iFlow, Qwen, Kiro, etc.) - 6 routing strategies (Fill First, Round Robin, P2C, Random, Least Used, Cost Optimized) - Export/Import database backup with full archive support - Translator Playground with 4 modes (Playground, Chat Tester, Test Bench, Live Monitor) - 100% TypeScript across src/ and open-sse/ - Docker support with multi-stage builds - Comprehensive documentation and 9 dashboard screenshots
102 lines
2.6 KiB
TypeScript
102 lines
2.6 KiB
TypeScript
import crypto from "crypto";
|
|
|
|
// FASE-01: No hardcoded fallback — enforced by secretsValidator at startup
|
|
if (!process.env.API_KEY_SECRET) {
|
|
console.error("[SECURITY] API_KEY_SECRET is not set. API key CRC will be insecure.");
|
|
}
|
|
const API_KEY_SECRET = process.env.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;
|
|
}
|