admin / Synapse-Sonar
publicAttack Surface Simulation
Synapse-Sonar / synapse-sonar / app / api / setup / route.ts
2303 B · main
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 | import { NextRequest, NextResponse } from "next/server"; import crypto from "crypto"; import { z } from "zod"; import bcrypt from "bcryptjs"; import { prisma } from "@/lib/prisma"; export const runtime = "nodejs"; /** * First-run bootstrap. Creates the very first Tenant + Tenant Admin. * * Username + password only — no organization required. A default tenant is * created silently (the admin can rename it later). One-shot: refuses once any * user exists. */ /** GET /api/setup → whether first-run setup is still required. */ export async function GET() { const userCount = await prisma.user.count(); return NextResponse.json({ needsSetup: userCount === 0 }); } const SetupSchema = z.object({ username: z .string() .min(3) .max(40) .regex(/^[a-zA-Z0-9._-]+$/, "Letters, numbers, dot, underscore and hyphen only"), name: z.string().max(80).optional(), password: z.string().min(10, "Use at least 10 characters"), }); export async function POST(req: NextRequest) { // Bootstrap guard: refuse if the system is already initialized. const existing = await prisma.user.count(); if (existing > 0) { return NextResponse.json( { error: "Setup has already been completed" }, { status: 409 } ); } let body; try { body = SetupSchema.parse(await req.json()); } catch (err) { if (err instanceof z.ZodError) { return NextResponse.json( { error: "Invalid input", details: err.issues }, { status: 422 } ); } return NextResponse.json({ error: "Invalid request" }, { status: 400 }); } // Create the default tenant + admin atomically. const result = await prisma.$transaction(async (tx) => { const tenant = await tx.tenant.create({ data: { name: "My Organization", slug: `org-${crypto.randomBytes(4).toString("hex")}`, }, }); const admin = await tx.user.create({ data: { tenantId: tenant.id, username: body.username, name: body.name, role: "TENANT_ADMIN", status: "ACTIVE", passwordHash: await bcrypt.hash(body.password, 12), }, select: { id: true, username: true }, }); return { tenant, admin }; }); return NextResponse.json({ ok: true, username: result.admin.username }); } |