115 lines
3.1 KiB
TypeScript
115 lines
3.1 KiB
TypeScript
import { redirect } from "next/navigation";
|
|
import { hash } from "bcryptjs";
|
|
import { prisma } from "@/lib/prisma";
|
|
import { SYSTEM_PERMISSIONS } from "@/lib/access";
|
|
import { setSession } from "@/lib/session";
|
|
import { hasAdminUser } from "@/lib/setup";
|
|
|
|
export const dynamic = "force-dynamic";
|
|
|
|
async function createFirstAdmin(formData: FormData) {
|
|
"use server";
|
|
|
|
const username = String(formData.get("username") || "").trim().toLowerCase();
|
|
const password = String(formData.get("password") || "");
|
|
|
|
if (!username || password.length < 10) {
|
|
throw new Error("Username is required and password must be at least 10 characters.");
|
|
}
|
|
|
|
const existingAdmin = await prisma.userRole.findFirst({
|
|
where: { role: { name: "admin" } },
|
|
select: { userId: true }
|
|
});
|
|
|
|
if (existingAdmin) {
|
|
redirect("/login");
|
|
}
|
|
|
|
const passwordHash = await hash(password, 12);
|
|
|
|
const user = await prisma.$transaction(async (tx) => {
|
|
const permissions = await Promise.all(
|
|
SYSTEM_PERMISSIONS.map((key) =>
|
|
tx.permission.upsert({
|
|
where: { key },
|
|
update: {},
|
|
create: { key, description: key }
|
|
})
|
|
)
|
|
);
|
|
|
|
const adminRole = await tx.role.upsert({
|
|
where: { name: "admin" },
|
|
update: {},
|
|
create: {
|
|
name: "admin",
|
|
description: "Full system administrator"
|
|
}
|
|
});
|
|
|
|
await Promise.all(
|
|
permissions.map((permission) =>
|
|
tx.rolePermission.upsert({
|
|
where: { roleId_permissionId: { roleId: adminRole.id, permissionId: permission.id } },
|
|
update: {},
|
|
create: { roleId: adminRole.id, permissionId: permission.id }
|
|
})
|
|
)
|
|
);
|
|
|
|
const createdUser = await tx.user.create({
|
|
data: {
|
|
username,
|
|
displayName: username,
|
|
passwordHash
|
|
}
|
|
});
|
|
|
|
await tx.userRole.create({ data: { userId: createdUser.id, roleId: adminRole.id } });
|
|
await tx.room.create({
|
|
data: {
|
|
slug: `@${username}`,
|
|
name: `${username}'s room`,
|
|
ownerId: createdUser.id,
|
|
visibility: "FRIENDS"
|
|
}
|
|
});
|
|
|
|
return createdUser;
|
|
});
|
|
|
|
await setSession(user.id);
|
|
redirect("/dashboard");
|
|
}
|
|
|
|
export default async function SetupPage() {
|
|
if (await hasAdminUser()) {
|
|
redirect("/login");
|
|
}
|
|
|
|
return (
|
|
<main className="auth-page">
|
|
<section className="auth-card">
|
|
<div className="title-block" style={{ marginBottom: 18 }}>
|
|
<h1>WatchLink first setup</h1>
|
|
<p>Create the first admin account. This screen locks after setup.</p>
|
|
</div>
|
|
<form className="form" action={createFirstAdmin}>
|
|
<label>
|
|
Username
|
|
<input className="input" name="username" autoComplete="username" required />
|
|
</label>
|
|
<label>
|
|
Password
|
|
<input className="input" name="password" type="password" autoComplete="new-password" minLength={10} required />
|
|
</label>
|
|
<button className="button primary" type="submit">
|
|
Create admin
|
|
</button>
|
|
</form>
|
|
</section>
|
|
</main>
|
|
);
|
|
}
|