neo-2 / artifacts /api-server /src /lib /apiKeys.ts
bentosmau
Implement API key authentication for enhanced NeoAPI security
bc2ac28
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";
}