diff --git a/apps/web/lib/actions/api-keys.ts b/apps/web/lib/actions/api-keys.ts index 7252453..3279f76 100644 --- a/apps/web/lib/actions/api-keys.ts +++ b/apps/web/lib/actions/api-keys.ts @@ -1,39 +1,19 @@ "use server"; -import { getServerSession } from "@/lib/server-auth"; -import { db } from "@/db"; -import { apiKeys } from "@/db/schema"; -import { eq, and, isNull } from "drizzle-orm"; +import { forwardToCP, getPrimaryOrgId } from "@/lib/cp-proxy"; -function generateId(): string { - const chars = "abcdefghijklmnopqrstuvwxyz0123456789"; - let result = ""; - const array = new Uint8Array(24); - crypto.getRandomValues(array); - for (const byte of array) { - result += chars[byte % chars.length]; - } - return result; -} - -function generateApiKey(): string { - const chars = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; - let key = "vl_live_sk_"; - const array = new Uint8Array(32); - crypto.getRandomValues(array); - for (const byte of array) { - key += chars[byte % chars.length]; - } - return key; -} +// API keys are minted by the CONTROL PLANE (POST /admin/v1/orgs/{orgId}/ +// api-keys), which is what actually authenticates requests to the engine. +// The dashboard used to write keys to a *local* table — those never worked +// against the API. These actions now proxy to the CP so keys are real. -async function hashKey(key: string): Promise { - const encoder = new TextEncoder(); - const data = encoder.encode(key); - const hashBuffer = await crypto.subtle.digest("SHA-256", data); - const hashArray = Array.from(new Uint8Array(hashBuffer)); - return hashArray.map((b) => b.toString(16).padStart(2, "0")).join(""); +interface CpAPIKey { + id: string; + name: string; + prefix: string; + scopes?: string[]; + last_used_at?: string | null; + created_at?: string; } export async function createApiKey(data: { @@ -41,93 +21,68 @@ export async function createApiKey(data: { permissions?: string; expiresAt?: string; }) { - const session = await getServerSession(); - if (!session) { - return { error: "Unauthorized" }; - } - - try { - const rawKey = generateApiKey(); - const keyHash = await hashKey(rawKey); - const keyPrefix = rawKey.slice(0, 12); - const id = generateId(); + const orgId = await getPrimaryOrgId(); + if (!orgId) return { error: "No organization found on your account." }; - await db.insert(apiKeys).values({ - id, - userId: session.user.id, - name: data.name, - keyPrefix, - keyHash, - permissions: data.permissions || "full", - expiresAt: data.expiresAt ? new Date(data.expiresAt) : null, - }); - - // Return the raw key - it can only be shown once - return { - success: true, - key: rawKey, - keyId: id, - name: data.name, - prefix: keyPrefix, - }; - } catch (err) { + const { ok, status, data: resp } = await forwardToCP( + `/admin/v1/orgs/${orgId}/api-keys`, + { method: "POST", orgId, body: { name: data.name } }, + ); + if (!ok) { return { error: - err instanceof Error ? err.message : "Failed to create API key", + (resp as { error?: string } | null)?.error ?? + `Failed to create API key (HTTP ${status})`, }; } + + const r = (resp ?? {}) as CpAPIKey & { key?: string }; + return { + success: true, + key: r.key, // plaintext, shown once + keyId: r.id, + name: r.name ?? data.name, + prefix: r.prefix ?? r.key?.slice(0, 12), + }; } export async function revokeApiKey(keyId: string) { - const session = await getServerSession(); - if (!session) { - return { error: "Unauthorized" }; - } + const orgId = await getPrimaryOrgId(); + if (!orgId) return { error: "No organization found on your account." }; - try { - await db - .update(apiKeys) - .set({ revokedAt: new Date() }) - .where(and(eq(apiKeys.id, keyId), eq(apiKeys.userId, session.user.id))); - - return { success: true }; - } catch (err) { + const { ok, status, data } = await forwardToCP( + `/admin/v1/orgs/${orgId}/api-keys/${keyId}`, + { method: "DELETE", orgId }, + ); + if (!ok) { return { error: - err instanceof Error ? err.message : "Failed to revoke API key", + (data as { error?: string } | null)?.error ?? + `Failed to revoke API key (HTTP ${status})`, }; } + return { success: true }; } export async function listApiKeys() { - const session = await getServerSession(); - if (!session) { - return { error: "Unauthorized", keys: [] }; - } + const orgId = await getPrimaryOrgId(); + if (!orgId) return { error: "No organization found", keys: [] }; - try { - const keys = await db - .select({ - id: apiKeys.id, - name: apiKeys.name, - keyPrefix: apiKeys.keyPrefix, - permissions: apiKeys.permissions, - rateLimit: apiKeys.rateLimit, - lastUsedAt: apiKeys.lastUsedAt, - expiresAt: apiKeys.expiresAt, - createdAt: apiKeys.createdAt, - }) - .from(apiKeys) - .where( - and(eq(apiKeys.userId, session.user.id), isNull(apiKeys.revokedAt)) - ); + const { ok, data } = await forwardToCP(`/admin/v1/orgs/${orgId}/api-keys`, { + orgId, + }); + if (!ok) return { error: "Failed to list API keys", keys: [] }; - return { success: true, keys }; - } catch (err) { - return { - error: - err instanceof Error ? err.message : "Failed to list API keys", - keys: [], - }; - } + const keys = ((data as CpAPIKey[]) ?? []).map((k) => ({ + id: k.id, + name: k.name, + keyPrefix: k.prefix, + permissions: (k.scopes ?? []).join(", ") || "full", + rateLimit: null as number | null, + lastUsedAt: k.last_used_at ? new Date(k.last_used_at) : null, + expiresAt: null as Date | null, + createdAt: k.created_at ? new Date(k.created_at) : new Date(), + })); + + return { success: true, keys }; }