/** * Per-workspace API keys for AI agents. * * GET /api/workspaces/[slug]/keys — list keys (no secrets) * POST /api/workspaces/[slug]/keys — mint a new key * * The full plaintext key is returned ONCE in the POST response and never * persisted; only its sha256 hash is stored. * * API-key principals can list their own workspace's keys but cannot mint * new ones (use the session UI for that). */ import { NextResponse } from 'next/server'; import { requireWorkspacePrincipal } from '@/lib/auth/workspace-auth'; import { listWorkspaceApiKeys, mintWorkspaceApiKey } from '@/lib/auth/workspace-auth'; export async function GET( request: Request, { params }: { params: Promise<{ slug: string }> } ) { const { slug } = await params; const principal = await requireWorkspacePrincipal(request, { targetSlug: slug }); if (principal instanceof NextResponse) return principal; const keys = await listWorkspaceApiKeys(principal.workspace.id); return NextResponse.json({ keys }); } export async function POST( request: Request, { params }: { params: Promise<{ slug: string }> } ) { const { slug } = await params; const principal = await requireWorkspacePrincipal(request, { targetSlug: slug }); if (principal instanceof NextResponse) return principal; if (principal.source !== 'session') { return NextResponse.json( { error: 'API keys can only be created from a signed-in session' }, { status: 403 } ); } let body: { name?: string; scopes?: string[] }; try { body = (await request.json()) as { name?: string; scopes?: string[] }; } catch { return NextResponse.json({ error: 'Invalid JSON body' }, { status: 400 }); } const name = (body.name ?? '').trim(); if (!name) { return NextResponse.json({ error: 'Field "name" is required' }, { status: 400 }); } const minted = await mintWorkspaceApiKey({ workspaceId: principal.workspace.id, name, createdBy: principal.userId, scopes: body.scopes, }); return NextResponse.json({ id: minted.id, name: minted.name, prefix: minted.prefix, createdAt: minted.created_at, // ↓ Only returned ONCE. Client must store this — we never see it again. token: minted.token, }); }