← back to Norma
app/api/settings/api-keys/route.ts
56 lines
import { NextRequest, NextResponse } from 'next/server';
import { requireRole } from '@/lib/require-role';
import { generateApiKey, listApiKeys } from '@/lib/credentials';
import { auditLog } from '@/lib/audit';
/**
* GET /api/settings/api-keys
* List all Norma API keys (no secrets exposed).
*/
export async function GET(request: NextRequest) {
const auth = requireRole(request, 'admin');
if (auth instanceof NextResponse) return auth;
const keys = await listApiKeys();
return NextResponse.json({ keys });
}
/**
* POST /api/settings/api-keys
* Generate a new Norma API key. Returns the plaintext key ONCE.
*/
export async function POST(request: NextRequest) {
const auth = requireRole(request, 'admin');
if (auth instanceof NextResponse) return auth;
const body = await request.json().catch(() => ({}));
const { name, scopes, expires_in_days } = body;
if (!name || typeof name !== 'string' || name.trim().length === 0) {
return NextResponse.json({ error: 'name is required' }, { status: 400 });
}
const validScopes = ['read', 'write', 'admin'];
const scopeList = Array.isArray(scopes) ? scopes.filter((s: string) => validScopes.includes(s)) : ['read'];
if (scopeList.length === 0) scopeList.push('read');
const expiryDays = typeof expires_in_days === 'number' && expires_in_days > 0 ? expires_in_days : undefined;
const result = await generateApiKey(name.trim(), scopeList, expiryDays, auth.username);
await auditLog('apikey.created', 'norma_api_key', result.id, {
name: name.trim(),
scopes: scopeList,
expiresInDays: expiryDays ?? null,
createdBy: auth.username,
});
return NextResponse.json({
id: result.id,
key: result.key, // shown ONCE — never returned again
prefix: result.prefix,
name: name.trim(),
scopes: scopeList,
});
}