feat: first-run setup wizard, CLI seed script, and installation docs
- /setup Server Component + SetupClient form + createFirstAdmin Server Action: zero-users guard (TOCTOU-safe), argon2 hash, ADMIN user creation, redirects to /auth/signin after setup - scripts/setup-admin.mjs: CLI alternative for headless/container setups - docs/installation.md: 7-section install guide (clone → configure → run → verify) Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
"use server";
|
||||
import { prisma } from "@capakraken/db";
|
||||
import { SystemRole } from "@capakraken/db";
|
||||
|
||||
export type SetupResult =
|
||||
| { success: true }
|
||||
| { error: "alreadySetup" | "emailTaken" | "validation"; message?: string };
|
||||
|
||||
export async function createFirstAdmin(formData: {
|
||||
name: string;
|
||||
email: string;
|
||||
password: string;
|
||||
}): Promise<SetupResult> {
|
||||
// Validate
|
||||
if (!formData.name.trim()) return { error: "validation", message: "Name is required." };
|
||||
if (!formData.email.includes("@")) return { error: "validation", message: "Valid email required." };
|
||||
if (formData.password.length < 8) return { error: "validation", message: "Password must be at least 8 characters." };
|
||||
|
||||
// TOCTOU guard — check again inside the action
|
||||
const count = await prisma.user.count();
|
||||
if (count > 0) return { error: "alreadySetup" };
|
||||
|
||||
const { hash } = await import("@node-rs/argon2");
|
||||
const passwordHash = await hash(formData.password);
|
||||
|
||||
try {
|
||||
await prisma.user.create({
|
||||
data: {
|
||||
email: formData.email.toLowerCase().trim(),
|
||||
name: formData.name.trim(),
|
||||
passwordHash,
|
||||
systemRole: SystemRole.ADMIN,
|
||||
isActive: true,
|
||||
},
|
||||
});
|
||||
return { success: true };
|
||||
} catch (err: unknown) {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
if (message.includes("Unique constraint") || message.includes("unique")) {
|
||||
return { error: "emailTaken" };
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user