← 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,
  });
}