route.ts
2,585 bytes
| 1 | import { NextResponse } from 'next/server'; |
|---|---|
| 2 | import { z } from 'zod'; |
| 3 | import { currentUserId } from '@/auth'; |
| 4 | import { |
| 5 | clearSessionKeyCookie, |
| 6 | hasAnyKey, |
| 7 | setSessionKeyCookie, |
| 8 | } from '@/lib/byok'; |
| 9 | import { maskKey } from '@/lib/crypto'; |
| 10 | import { env } from '@/lib/env'; |
| 11 | import { validateOpenRouterKey } from '@/lib/openrouter'; |
| 12 | import { deleteSavedKey, getSavedKeyInfo, saveEncryptedKey } from '@/db/repositories'; |
| 13 | import { encryptSecret } from '@/lib/crypto'; |
| 14 | |
| 15 | export const runtime = 'nodejs'; |
| 16 | |
| 17 | /** GET: current key status (never returns the key itself). */ |
| 18 | export async function GET() { |
| 19 | const userId = await currentUserId(); |
| 20 | const saved = userId ? await getSavedKeyInfo(userId) : null; |
| 21 | const status = await hasAnyKey(); |
| 22 | return NextResponse.json({ |
| 23 | mockMode: env.MOCK_LLM, |
| 24 | hasKey: status.has, |
| 25 | source: status.source, |
| 26 | saved, |
| 27 | authenticated: Boolean(userId), |
| 28 | }); |
| 29 | } |
| 30 | |
| 31 | const postSchema = z.object({ |
| 32 | apiKey: z.string().min(8).max(512), |
| 33 | mode: z.enum(['save', 'session']), |
| 34 | label: z.string().max(80).optional(), |
| 35 | }); |
| 36 | |
| 37 | /** POST: validate a key against OpenRouter and store it (encrypted). */ |
| 38 | export async function POST(request: Request) { |
| 39 | const body = await request.json().catch(() => null); |
| 40 | const parsed = postSchema.safeParse(body); |
| 41 | if (!parsed.success) { |
| 42 | return NextResponse.json({ error: 'Invalid request', issues: parsed.error.flatten() }, { status: 400 }); |
| 43 | } |
| 44 | const { apiKey, mode, label } = parsed.data; |
| 45 | |
| 46 | const validation = await validateOpenRouterKey(apiKey); |
| 47 | if (!validation.valid) { |
| 48 | return NextResponse.json({ error: validation.error ?? 'Key rejected by OpenRouter' }, { status: 400 }); |
| 49 | } |
| 50 | |
| 51 | if (mode === 'save') { |
| 52 | const userId = await currentUserId(); |
| 53 | if (!userId) { |
| 54 | return NextResponse.json({ error: 'Sign in to save a key. Use session mode otherwise.' }, { status: 401 }); |
| 55 | } |
| 56 | await saveEncryptedKey(userId, encryptSecret(apiKey), maskKey(apiKey), label ?? validation.label ?? null); |
| 57 | } else { |
| 58 | await setSessionKeyCookie(apiKey); |
| 59 | } |
| 60 | |
| 61 | return NextResponse.json({ |
| 62 | ok: true, |
| 63 | source: mode, |
| 64 | keyMask: maskKey(apiKey), |
| 65 | credits: { |
| 66 | usage: validation.usage ?? null, |
| 67 | limit: validation.limit ?? null, |
| 68 | remaining: validation.limitRemaining ?? null, |
| 69 | isFreeTier: validation.isFreeTier ?? null, |
| 70 | }, |
| 71 | }); |
| 72 | } |
| 73 | |
| 74 | /** DELETE: remove the saved key and/or clear the session cookie. */ |
| 75 | export async function DELETE() { |
| 76 | const userId = await currentUserId(); |
| 77 | if (userId) await deleteSavedKey(userId); |
| 78 | await clearSessionKeyCookie(); |
| 79 | return NextResponse.json({ ok: true }); |
| 80 | } |
| 81 | |