Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions app/api/env-vars/route.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,36 @@
import { NextResponse } from "next/server";
import { auth } from "@/lib/auth";
import { getDb, ensureTables } from "@/lib/db";
import {
getEnvVars,
listEnvVars,
setEnvVar,
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<boolean> {
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=<repoName>&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 });
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

try {
const { searchParams } = new URL(request.url);
const project = searchParams.get("project");
Expand All @@ -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({
Expand All @@ -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;
Expand All @@ -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(
Expand All @@ -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;
Expand All @@ -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) {
Expand Down