| import crypto from "crypto"; |
|
|
| |
| if (!process.env.API_KEY_SECRET) { |
| console.error("[SECURITY] API_KEY_SECRET is not set. API key CRC validation is disabled."); |
| } |
|
|
| function getApiKeySecret(): string { |
| const secret = process.env.API_KEY_SECRET || "omniroute-default-insecure-api-key-secret"; |
| if (!secret || secret.trim() === "") { |
| throw new Error("API_KEY_SECRET is required for API key CRC operations"); |
| } |
| return secret; |
| } |
|
|
| |
| |
| |
| function generateKeyId(): string { |
| const chars = "abcdefghijklmnopqrstuvwxyz0123456789"; |
| let result = ""; |
| result = crypto.randomBytes(3).toString("hex"); |
| return result; |
| } |
|
|
| |
| |
| |
| function generateCrc(machineId: string, keyId: string): string { |
| const secret = getApiKeySecret(); |
| |
| |
| return crypto |
| .pbkdf2Sync(machineId + keyId, secret, 1000, 32, "sha256") |
| .toString("hex") |
| .slice(0, 8); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| export function generateApiKeyWithMachine(machineId: string): { key: string; keyId: string } { |
| const keyId = generateKeyId(); |
| const crc = generateCrc(machineId, keyId); |
| const key = `sk-${machineId}-${keyId}-${crc}`; |
| return { key, keyId }; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| export function parseApiKey( |
| apiKey: string |
| ): { machineId: string | null; keyId: string; isNewFormat: boolean } | null { |
| if (!apiKey || !apiKey.startsWith("sk-")) return null; |
|
|
| const parts = apiKey.split("-"); |
|
|
| |
| if (parts.length === 4) { |
| const [, machineId, keyId, crc] = parts; |
|
|
| |
| let expectedCrc; |
| try { |
| expectedCrc = generateCrc(machineId, keyId); |
| } catch { |
| return null; |
| } |
| if (crc !== expectedCrc) return null; |
|
|
| return { machineId, keyId, isNewFormat: true }; |
| } |
|
|
| |
| if (parts.length === 2) { |
| return { machineId: null, keyId: parts[1], isNewFormat: false }; |
| } |
|
|
| return null; |
| } |
|
|
| |
| |
| |
| |
| |
| export function verifyApiKeyCrc(apiKey: string): boolean { |
| const parsed = parseApiKey(apiKey); |
| if (!parsed) return false; |
|
|
| |
| if (!parsed.isNewFormat) return true; |
|
|
| |
| return true; |
| } |
|
|
| |
| |
| |
| |
| |
| export function isNewFormatKey(apiKey: string): boolean { |
| const parsed = parseApiKey(apiKey); |
| return parsed?.isNewFormat === true; |
| } |
|
|