Spaces:
Runtime error
Runtime error
| import { NextResponse } from "next/server"; | |
| import { getApiKeys, createApiKey, isCloudEnabled, updateApiKeyPermissions } from "@/lib/localDb"; | |
| import { getConsistentMachineId } from "@/shared/utils/machineId"; | |
| import { syncToCloud } from "@/lib/cloudSync"; | |
| import { createKeySchema } from "@/shared/validation/schemas"; | |
| import { isValidationFailure, validateBody } from "@/shared/validation/helpers"; | |
| import { isApiKeyRevealEnabled, maskStoredApiKey } from "@/lib/apiKeyExposure"; | |
| import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; | |
| import { normalizeSelfServiceScopesForCreate } from "@/shared/constants/selfServiceScopes"; | |
| import * as log from "@/sse/utils/logger"; | |
| function parsePagination(request: Request) { | |
| const url = new URL(request.url); | |
| const limitValue = url.searchParams.get("limit"); | |
| const offsetValue = url.searchParams.get("offset"); | |
| const parsedLimit = limitValue ? Number.parseInt(limitValue, 10) : undefined; | |
| const parsedOffset = offsetValue ? Number.parseInt(offsetValue, 10) : 0; | |
| const limit = | |
| Number.isInteger(parsedLimit) && parsedLimit && parsedLimit > 0 ? parsedLimit : null; | |
| const offset = Number.isInteger(parsedOffset) && parsedOffset > 0 ? parsedOffset : 0; | |
| return { limit, offset }; | |
| } | |
| // GET /api/keys - List API keys | |
| export async function GET(request: Request) { | |
| const authError = await requireManagementAuth(request); | |
| if (authError) return authError; | |
| try { | |
| const keys = await getApiKeys(); | |
| const maskedKeys = keys.map((k) => ({ | |
| ...k, | |
| key: maskStoredApiKey(k.key), | |
| })); | |
| const { limit, offset } = parsePagination(request); | |
| const pagedKeys = | |
| limit === null ? maskedKeys.slice(offset) : maskedKeys.slice(offset, offset + limit); | |
| return NextResponse.json({ | |
| keys: pagedKeys, | |
| total: maskedKeys.length, | |
| allowKeyReveal: isApiKeyRevealEnabled(), | |
| }); | |
| } catch (error) { | |
| log.error("keys", "Error fetching keys", error); | |
| return NextResponse.json({ error: "Failed to fetch keys" }, { status: 500 }); | |
| } | |
| } | |
| // POST /api/keys - Create new API key | |
| export async function POST(request) { | |
| const authError = await requireManagementAuth(request); | |
| if (authError) return authError; | |
| try { | |
| const body = await request.json(); | |
| // Zod validation | |
| const validation = validateBody(createKeySchema, body); | |
| if (isValidationFailure(validation)) { | |
| return NextResponse.json({ error: validation.error }, { status: 400 }); | |
| } | |
| const { | |
| name, | |
| noLog, | |
| scopes, | |
| allowUsageCommand, | |
| usageLimitEnabled, | |
| dailyUsageLimitUsd, | |
| weeklyUsageLimitUsd, | |
| } = validation.data; | |
| // Always get machineId from server | |
| const machineId = await getConsistentMachineId(); | |
| const normalizedScopes = normalizeSelfServiceScopesForCreate(scopes); | |
| const apiKey = await createApiKey(name, machineId, normalizedScopes); | |
| if ( | |
| noLog === true || | |
| allowUsageCommand === true || | |
| usageLimitEnabled === true || | |
| dailyUsageLimitUsd !== undefined || | |
| weeklyUsageLimitUsd !== undefined | |
| ) { | |
| await updateApiKeyPermissions(apiKey.id, { | |
| ...(noLog === true && { noLog: true }), | |
| ...(allowUsageCommand === true && { allowUsageCommand: true }), | |
| ...(usageLimitEnabled === true && { usageLimitEnabled: true }), | |
| ...(dailyUsageLimitUsd !== undefined && { dailyUsageLimitUsd }), | |
| ...(weeklyUsageLimitUsd !== undefined && { weeklyUsageLimitUsd }), | |
| }); | |
| } | |
| // Auto sync to Cloud if enabled | |
| await syncKeysToCloudIfEnabled(); | |
| return NextResponse.json( | |
| { | |
| key: apiKey.key, | |
| name: apiKey.name, | |
| id: apiKey.id, | |
| machineId: apiKey.machineId, | |
| noLog: noLog === true, | |
| allowUsageCommand: allowUsageCommand === true, | |
| usageLimitEnabled: usageLimitEnabled === true, | |
| dailyUsageLimitUsd: dailyUsageLimitUsd ?? null, | |
| weeklyUsageLimitUsd: weeklyUsageLimitUsd ?? null, | |
| streamDefaultMode: "legacy", | |
| }, | |
| { status: 201 } | |
| ); | |
| } catch (error) { | |
| log.error("keys", "Error creating key", error); | |
| return NextResponse.json({ error: "Failed to create key" }, { status: 500 }); | |
| } | |
| } | |
| /** | |
| * Sync API keys to Cloud if enabled | |
| */ | |
| async function syncKeysToCloudIfEnabled() { | |
| try { | |
| const cloudEnabled = await isCloudEnabled(); | |
| if (!cloudEnabled) return; | |
| const machineId = await getConsistentMachineId(); | |
| await syncToCloud(machineId); | |
| } catch (error) { | |
| log.error("keys", "Error syncing keys to cloud", error); | |
| } | |
| } | |