Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 0
feat(security): Move API Key to Cookies#243
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
211cddbce5a5f9adee97e195d1378dac10bFile filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,72 @@ | ||
| import { NextRequest, NextResponse } from "next/server"; | ||
| import { apiClient } from "@/app/lib/apiClient"; | ||
| import { clearApiKeyCookies, setApiKeyCookies } from "@/app/lib/authCookie"; | ||
| import type { AddApiKeyRequest, ApiKeyMeta } from "@/app/lib/types/credentials"; | ||
| function maskKey(key: string): string { | ||
| const tail = key.slice(-4); | ||
| return `${"•".repeat(8)}${tail}`; | ||
| } | ||
| export async function POST(request: NextRequest) { | ||
| let body: AddApiKeyRequest; | ||
| try { | ||
| body = (await request.json()) as AddApiKeyRequest; | ||
| } catch { | ||
| return NextResponse.json( | ||
| { error: "Invalid request body" }, | ||
| { status: 400 }, | ||
| ); | ||
| } | ||
| const key = body.key?.trim(); | ||
| const label = body.label?.trim(); | ||
| if (!key || !label) { | ||
| return NextResponse.json( | ||
| { error: "Both a label and an API key are required" }, | ||
| { status: 400 }, | ||
| ); | ||
| } | ||
| const verifyRequest = new Request(request.url, { | ||
| headers: { | ||
| "X-API-KEY": key, | ||
| Cookie: request.headers.get("Cookie") || "", | ||
| }, | ||
| }); | ||
| let status: number; | ||
| try { | ||
| ({ status } = await apiClient(verifyRequest, "/api/v1/apikeys/verify")); | ||
| } catch { | ||
| return NextResponse.json( | ||
| { error: "Failed to reach backend for API key verification" }, | ||
| { status: 502 }, | ||
| ); | ||
| } | ||
| if (status < 200 || status >= 300) { | ||
| return NextResponse.json( | ||
| { error: "Invalid API key. Please check the key and try again." }, | ||
| { status: 401 }, | ||
| ); | ||
| } | ||
Ayush8923 marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| const meta: ApiKeyMeta = { | ||
| id: crypto.randomUUID(), | ||
| label, | ||
| masked: maskKey(key), | ||
| createdAt: new Date().toISOString(), | ||
| }; | ||
| const res = NextResponse.json({ data: meta }, { status: 200 }); | ||
| setApiKeyCookies(res, key, meta); | ||
| return res; | ||
| } | ||
Ayush8923 marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| export async function DELETE() { | ||
| const res = NextResponse.json({ success: true }, { status: 200 }); | ||
| clearApiKeyCookies(res); | ||
| return res; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,19 +1,15 @@ | ||
| "use client"; | ||
| import { Button, Field, Modal, Select } from "@/app/components/ui"; | ||
| import { InfoIcon } from "@/app/components/icons"; | ||
| const PROVIDERS = [{ value: "Kaapi", label: "Kaapi" }]; | ||
| import { Button, Field, Modal } from "@/app/components/ui"; | ||
| import { APP_NAME } from "@/app/lib/constants"; | ||
| interface AddKeyModalProps { | ||
| open: boolean; | ||
| newKeyLabel: string; | ||
| newKeyValue: string; | ||
| newKeyProvider: string; | ||
| isValidating?: boolean; | ||
| onLabelChange: (value: string) => void; | ||
| onValueChange: (value: string) => void; | ||
| onProviderChange: (value: string) => void; | ||
| onAddKey: () => void; | ||
| onClose: () => void; | ||
| } | ||
| @@ -22,11 +18,9 @@ export default function AddKeyModal({ | ||
| open, | ||
| newKeyLabel, | ||
| newKeyValue, | ||
| newKeyProvider, | ||
| isValidating, | ||
| onLabelChange, | ||
| onValueChange, | ||
| onProviderChange, | ||
| onAddKey, | ||
| onClose, | ||
| }: AddKeyModalProps) { | ||
| @@ -43,20 +37,16 @@ export default function AddKeyModal({ | ||
| <div className="px-6 pb-2"> | ||
| <p className="text-sm mb-5 text-text-secondary"> | ||
| Add a new API key to use in your evaluation workflows. Keys are stored | ||
| locally in your browser. | ||
| securely server-side and cannot be viewed again after they are added. | ||
| </p> | ||
Ayush8923 marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| <div className="space-y-4"> | ||
| <div> | ||
| <label className="block text-xs font-medium text-text-secondary mb-1"> | ||
| Provider | ||
| </label> | ||
| <Select | ||
| value={newKeyProvider} | ||
| onChange={(e) => onProviderChange(e.target.value)} | ||
| options={PROVIDERS} | ||
| /> | ||
| </div> | ||
| <Field | ||
| label="Provider" | ||
| value={APP_NAME} | ||
| onChange={() => {}} | ||
| disabled | ||
| /> | ||
| <Field | ||
| label="Label" | ||
| @@ -73,15 +63,6 @@ export default function AddKeyModal({ | ||
| placeholder="Paste your API key here" | ||
| /> | ||
| </div> | ||
| <div className="mt-5 rounded-md p-3 bg-accent-primary/5 border border-accent-primary/20"> | ||
| <div className="flex gap-2"> | ||
| <InfoIcon className="w-4 h-4 shrink-0 mt-0.5 text-accent-primary" /> | ||
| <p className="text-xs text-text-secondary"> | ||
| API keys are stored in your browser's local storage. | ||
| </p> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| <div className="border-t border-border px-6 py-4 flex items-center justify-end gap-3 shrink-0"> | ||
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.