diff --git a/app/api/env-vars/route.ts b/app/api/env-vars/route.ts index 849c0f6..fd78d08 100644 --- a/app/api/env-vars/route.ts +++ b/app/api/env-vars/route.ts @@ -1,4 +1,6 @@ import { NextResponse } from "next/server"; +import { auth } from "@/lib/auth"; +import { getDb, ensureTables } from "@/lib/db"; import { getEnvVars, listEnvVars, @@ -6,12 +8,29 @@ import { deleteEnvVar, } from "@/lib/env-store"; +/** Returns true if userId owns at least one deployment for the given repoName. */ +async function userOwnsProject(userId: string, project: string): Promise { + await ensureTables(); + const sql = getDb(); + const rows = await sql` + SELECT 1 FROM deployments + WHERE user_id = ${userId} AND repo_name = ${project} + LIMIT 1 + `; + return rows.length > 0; +} + /** * GET /api/env-vars?project=&reveal=1 * reveal=1 → returns plaintext values (admin use only) * otherwise → returns masked values */ export async function GET(request: Request) { + const session = await auth(); + if (!session?.user?.id) { + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + } + try { const { searchParams } = new URL(request.url); const project = searchParams.get("project"); @@ -21,6 +40,10 @@ export async function GET(request: Request) { return NextResponse.json({ error: "project query param required" }, { status: 400 }); } + if (!(await userOwnsProject(session.user.id, project))) { + return NextResponse.json({ error: "Forbidden" }, { status: 403 }); + } + if (reveal) { const vars = await getEnvVars(project); return NextResponse.json({ @@ -42,6 +65,11 @@ export async function GET(request: Request) { * Body: { project: string; key: string; value: string } */ export async function POST(request: Request) { + const session = await auth(); + if (!session?.user?.id) { + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + } + try { const body = await request.json(); const { project, key, value } = body; @@ -53,6 +81,10 @@ export async function POST(request: Request) { ); } + if (!(await userOwnsProject(session.user.id, project))) { + return NextResponse.json({ error: "Forbidden" }, { status: 403 }); + } + // Basic key validation — only allow safe env var names if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) { return NextResponse.json( @@ -74,6 +106,11 @@ export async function POST(request: Request) { * Body: { project: string; key: string } */ export async function DELETE(request: Request) { + const session = await auth(); + if (!session?.user?.id) { + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + } + try { const body = await request.json(); const { project, key } = body; @@ -82,6 +119,10 @@ export async function DELETE(request: Request) { return NextResponse.json({ error: "project and key are required" }, { status: 400 }); } + if (!(await userOwnsProject(session.user.id, project))) { + return NextResponse.json({ error: "Forbidden" }, { status: 403 }); + } + await deleteEnvVar(project, key); return NextResponse.json({ ok: true }); } catch (err: unknown) {