import { NextRequest, NextResponse } from "next/server"; import prisma from "@/lib/db"; import bcrypt from "bcryptjs"; import jwt from "jsonwebtoken"; import { logAudit } from "@/lib/audit"; import { apiError } from "@/lib/api-helpers"; const JWT_SECRET = (() => { const s = process.env.JWT_SECRET; if (s && s.length >= 16) return s; if (process.env.NODE_ENV === "production") { throw new Error( "JWT_SECRET env var is required in production (min 16 chars). " + "Refusing to issue tokens with a default key." ); } return "erp-platform-dev-secret-key-change-in-prod"; })(); export async function POST(request: NextRequest) { try { const { organizationName, name, email, password } = await request.json(); // Validate required fields if (!organizationName || !name || !email || !password) { return NextResponse.json( { error: "organizationName, name, email, and password are required", }, { status: 400 } ); } // Normalize email const normalizedEmail = email.toLowerCase().trim(); // Hash password const passwordHash = await bcrypt.hash(password, 10); // Transaction: create org (or get existing), create user, create role, assign role const result = await prisma.$transaction(async (tx) => { // Find or create organization by name let organization = await tx.organization.findFirst({ where: { name: organizationName }, }); if (!organization) { organization = await tx.organization.create({ data: { name: organizationName, status: "ACTIVE", defaultCurrency: "USD", timezone: "UTC", }, }); } // Check if user already exists in this org const existingUser = await tx.user.findFirst({ where: { organizationId: organization.id, email: normalizedEmail, }, }); if (existingUser) { throw new Error( "User with this email already exists in this organization" ); } // Create user const user = await tx.user.create({ data: { organizationId: organization.id, email: normalizedEmail, name, passwordHash, status: "ACTIVE", }, include: { organization: true, }, }); // Create default "CEO" role if this is a new org let ceoRole = await tx.role.findFirst({ where: { organizationId: organization.id, roleType: "CEO", }, }); if (!ceoRole) { ceoRole = await tx.role.create({ data: { organizationId: organization.id, name: "CEO", roleType: "CEO", isSystem: true, status: "ACTIVE", description: "Chief Executive Officer - Full system access", }, }); // Create default CEO permissions if this is a new role const modules = [ "DASHBOARD", "CHART_OF_ACCOUNTS", "JOURNAL_ENTRIES", "INVOICES", "PAYMENTS", "VENDORS", "CUSTOMERS", "BANK_ACCOUNTS", "TREASURY", "REPORTS", "SETTINGS", "USERS", "INTEGRATIONS", "AUDIT_LOG", ]; for (const module of modules) { await tx.permission.create({ data: { organizationId: organization.id, roleId: ceoRole.id, module: module as any, action: "ADMIN", }, }); } } // Assign CEO role to user await tx.userRole.create({ data: { userId: user.id, roleId: ceoRole.id, }, }); return { user, organization, role: ceoRole, }; }); // Generate JWT token const token = jwt.sign( { userId: result.user.id, email: result.user.email, name: result.user.name, roleName: result.role.name, organizationId: result.organization.id, organizationName: result.organization.name, }, JWT_SECRET, { expiresIn: "7d" } ); // Audit log the registration logAudit({ organizationId: result.organization.id, userId: result.user.id, userName: result.user.name, action: "CREATE", module: "auth", entityType: "User", entityId: result.user.id, entityLabel: result.user.email, description: `${result.user.name} registered as a new user`, }); // Set cookie and return user info const response = NextResponse.json({ user: { id: result.user.id, name: result.user.name, email: result.user.email, role: result.role.name, organization: result.organization.name, }, token, }); response.cookies.set("auth-token", token, { httpOnly: true, secure: process.env.NODE_ENV === "production", sameSite: "lax", maxAge: 60 * 60 * 24 * 7, // 7 days path: "/", }); return response; } catch (error) { const message = error instanceof Error ? error.message : "Registration failed"; return apiError(message, error); } }