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