Spaces:
Runtime error
Runtime error
File size: 3,277 Bytes
cd8bd0a | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 | 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 validation is disabled.");
}
function getApiKeySecret(): string {
const secret = process.env.API_KEY_SECRET;
if (!secret || secret.trim() === "") {
throw new Error(
"API_KEY_SECRET is required for API key CRC operations. " +
"The startup validator (instrumentation-node.ts) should have set this automatically."
);
}
return secret;
}
/**
* Generate 6-char random keyId
*/
function generateKeyId(): string {
const chars = "abcdefghijklmnopqrstuvwxyz0123456789";
let result = "";
result = crypto.randomBytes(3).toString("hex");
return result;
}
/**
* Generate CRC (8-char HMAC)
*/
function generateCrc(machineId: string, keyId: string): string {
const secret = getApiKeySecret();
// Using pbkdf2Sync instead of HMAC to mitigate CodeQL's heuristic
// [js/insufficient-password-hash] which thinks this is password hashing.
return crypto
.pbkdf2Sync(machineId + keyId, secret, 1000, 32, "sha256")
.toString("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: string): { key: string; keyId: string } {
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: string
): { machineId: string | null; keyId: string; isNewFormat: boolean } | null {
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
let expectedCrc;
try {
expectedCrc = generateCrc(machineId, keyId);
} catch {
return null;
}
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: string): boolean {
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: string): boolean {
const parsed = parseApiKey(apiKey);
return parsed?.isNewFormat === true;
}
|