import { NextRequest, NextResponse } from "next/server"; import { registerCustomer } from "@/lib/zitadel"; import { rateLimit } from "@/lib/rate-limit"; import type { RegistrationInput } from "@/types"; import { z } from "zod"; const registrationSchema = z.object({ companyName: z.string().min(2).max(100), givenName: z.string().min(1).max(100), familyName: z.string().min(1).max(100), email: z.string().email(), preferredLanguage: z.enum(["en", "de", "fr", "it"]).optional(), }); /** 3 registrations per IP per hour */ const RATE_LIMIT = 3; const RATE_WINDOW_MS = 3_600_000; // 1 hour export async function POST(request: NextRequest) { // --- Rate limiting --- const ip = request.headers.get("x-forwarded-for")?.split(",")[0]?.trim() ?? request.headers.get("x-real-ip") ?? "unknown"; const rl = rateLimit(`register:${ip}`, RATE_LIMIT, RATE_WINDOW_MS); if (!rl.allowed) { return NextResponse.json( { error: "Too many registration attempts. Please try again later." }, { status: 429, headers: { "Retry-After": String(Math.ceil(rl.resetMs / 1000)), "X-RateLimit-Limit": String(RATE_LIMIT), "X-RateLimit-Remaining": "0", }, }, ); } // --- Validation --- try { const body = await request.json(); const parsed = registrationSchema.safeParse(body); if (!parsed.success) { return NextResponse.json( { error: "Validation failed", details: parsed.error.flatten() }, { status: 400 }, ); } const input: RegistrationInput = parsed.data; const result = await registerCustomer({ companyName: input.companyName, email: input.email, givenName: input.givenName, familyName: input.familyName, preferredLanguage: input.preferredLanguage, }); return NextResponse.json( { orgId: result.orgId, userId: result.userId, message: "Registration successful. You will receive an invitation email to set your password.", }, { status: 201, headers: { "X-RateLimit-Limit": String(RATE_LIMIT), "X-RateLimit-Remaining": String(rl.remaining), }, }, ); } catch (e: any) { console.error("Registration failed:", e); const zitadelMessage = extractZitadelMessage(e.message); return NextResponse.json( { error: zitadelMessage || "Registration failed. Please try again." }, { status: e.statusCode || 500 }, ); } } /** * ZITADEL errors come as: * "ZITADEL POST /path: 400 {"code":3, "message":"..."}" * Extract the human-readable "message" field. */ function extractZitadelMessage(errorMsg: string): string | null { try { const jsonStart = errorMsg.indexOf("{"); if (jsonStart === -1) return null; const json = JSON.parse(errorMsg.slice(jsonStart)); return json.message || null; } catch { return null; } }