import fs from "fs"; import path from "path"; import crypto from "crypto"; const DATA_DIR = path.join(process.cwd(), "data"); const KEYS_FILE = path.join(DATA_DIR, "api-keys.json"); export interface ApiKey { key: string; name: string; createdAt: string; active: boolean; usageCount: number; lastUsed: string | null; } function ensureDataDir() { if (!fs.existsSync(DATA_DIR)) { fs.mkdirSync(DATA_DIR, { recursive: true }); } } function readKeys(): ApiKey[] { ensureDataDir(); if (!fs.existsSync(KEYS_FILE)) return []; try { return JSON.parse(fs.readFileSync(KEYS_FILE, "utf-8")); } catch { return []; } } function writeKeys(keys: ApiKey[]) { ensureDataDir(); fs.writeFileSync(KEYS_FILE, JSON.stringify(keys, null, 2), "utf-8"); } export function generateKey(name: string): ApiKey { const keys = readKeys(); const newKey: ApiKey = { key: `neo1-${crypto.randomBytes(20).toString("hex")}`, name, createdAt: new Date().toISOString(), active: true, usageCount: 0, lastUsed: null, }; keys.push(newKey); writeKeys(keys); return newKey; } export function listKeys(): Omit[] { return readKeys().map(({ key, ...rest }) => ({ ...rest, keyPreview: `${key.slice(0, 10)}...${key.slice(-4)}`, })) as Omit[]; } export function validateKey(key: string): ApiKey | null { const keys = readKeys(); const found = keys.find((k) => k.key === key && k.active); if (!found) return null; found.usageCount += 1; found.lastUsed = new Date().toISOString(); writeKeys(keys); return found; } export function revokeKey(keyOrName: string): boolean { const keys = readKeys(); const idx = keys.findIndex( (k) => k.key === keyOrName || k.name === keyOrName ); if (idx === -1) return false; keys[idx].active = false; writeKeys(keys); return true; } export function getAdminSecret(): string { return process.env["NEO_ADMIN_SECRET"] ?? "neo-admin-change-me"; }