| 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<ApiKey, "key">[] { |
| return readKeys().map(({ key, ...rest }) => ({ |
| ...rest, |
| keyPreview: `${key.slice(0, 10)}...${key.slice(-4)}`, |
| })) as Omit<ApiKey, "key">[]; |
| } |
|
|
| 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"; |
| } |
|
|