Fix first setup server error handling
All checks were successful
Release Dry Run / release-dry-run (push) Successful in 1m30s
Template Compliance / compliance (push) Successful in 5s
Build / build (push) Successful in 11m24s

This commit is contained in:
MrSphay
2026-05-15 18:20:01 +02:00
parent 9ac6c87d08
commit 2f0b54b94e
10 changed files with 53 additions and 3 deletions

View File

@@ -8,6 +8,8 @@ import { requireInitialSetup } from "@/lib/setup";
import Link from "next/link";
import { redirect } from "next/navigation";
export const dynamic = "force-dynamic";
export default async function AdminPage() {
await requireInitialSetup();
const user = await requireCurrentUser();

View File

@@ -7,6 +7,8 @@ import { requireCurrentUser, userIsAdmin } from "@/lib/session";
import { requireInitialSetup } from "@/lib/setup";
import Link from "next/link";
export const dynamic = "force-dynamic";
export default async function DashboardPage() {
await requireInitialSetup();
const user = await requireCurrentUser();

View File

@@ -7,6 +7,8 @@ import { requireCurrentUser, userIsAdmin } from "@/lib/session";
import { requireInitialSetup } from "@/lib/setup";
import Link from "next/link";
export const dynamic = "force-dynamic";
export default async function FriendsPage() {
await requireInitialSetup();
const user = await requireCurrentUser();

View File

@@ -416,6 +416,17 @@ select {
font-weight: 700;
}
.form-error {
border: 1px solid color-mix(in srgb, var(--danger) 45%, var(--border));
border-radius: 8px;
background: color-mix(in srgb, var(--danger) 10%, var(--panel));
color: var(--danger);
margin: 0 0 14px;
padding: 10px 12px;
font-size: 13px;
font-weight: 700;
}
@media (max-width: 1100px) {
.room-layout {
grid-template-columns: 1fr;

View File

@@ -3,6 +3,8 @@ import { loginUser } from "@/lib/user-actions";
import { hasAdminUser } from "@/lib/setup";
import { redirect } from "next/navigation";
export const dynamic = "force-dynamic";
export default async function LoginPage() {
if (!(await hasAdminUser())) {
redirect("/setup");

View File

@@ -1,6 +1,8 @@
import { redirect } from "next/navigation";
import { hasAdminUser } from "@/lib/setup";
export const dynamic = "force-dynamic";
export default async function HomePage() {
if (!(await hasAdminUser())) {
redirect("/setup");

View File

@@ -3,6 +3,8 @@ import { registerUser } from "@/lib/user-actions";
import { hasAdminUser } from "@/lib/setup";
import { redirect } from "next/navigation";
export const dynamic = "force-dynamic";
export default async function RegisterPage() {
if (!(await hasAdminUser())) {
redirect("/setup");

View File

@@ -7,6 +7,8 @@ import { requireCurrentUser, userIsAdmin } from "@/lib/session";
import { requireInitialSetup } from "@/lib/setup";
import { redirect } from "next/navigation";
export const dynamic = "force-dynamic";
export default async function RoomPage({ params }: { params: Promise<{ slug: string }> }) {
await requireInitialSetup();
const user = await requireCurrentUser();

View File

@@ -7,14 +7,21 @@ import { hasAdminUser } from "@/lib/setup";
export const dynamic = "force-dynamic";
function normalizeUsername(value: FormDataEntryValue | null) {
return String(value || "")
.trim()
.toLowerCase()
.replace(/[^a-z0-9_-]/g, "");
}
async function createFirstAdmin(formData: FormData) {
"use server";
const username = String(formData.get("username") || "").trim().toLowerCase();
const username = normalizeUsername(formData.get("username"));
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.");
redirect("/setup?error=invalid");
}
const existingAdmin = await prisma.userRole.findFirst({
@@ -26,6 +33,15 @@ async function createFirstAdmin(formData: FormData) {
redirect("/login");
}
const existingUser = await prisma.user.findUnique({
where: { username },
select: { id: true }
});
if (existingUser) {
redirect("/setup?error=username");
}
const passwordHash = await hash(password, 12);
const user = await prisma.$transaction(async (tx) => {
@@ -83,10 +99,11 @@ async function createFirstAdmin(formData: FormData) {
redirect("/dashboard");
}
export default async function SetupPage() {
export default async function SetupPage({ searchParams }: { searchParams: Promise<{ error?: string }> }) {
if (await hasAdminUser()) {
redirect("/login");
}
const { error } = await searchParams;
return (
<main className="auth-page">
@@ -95,6 +112,7 @@ export default async function SetupPage() {
<h1>WatchLink first setup</h1>
<p>Create the first admin account. This screen locks after setup.</p>
</div>
{error ? <p className="form-error">{setupErrorMessage(error)}</p> : null}
<form className="form" action={createFirstAdmin}>
<label>
Username
@@ -112,3 +130,8 @@ export default async function SetupPage() {
</main>
);
}
function setupErrorMessage(error: string) {
if (error === "username") return "This username already exists. Choose another username.";
return "Use a username with letters, numbers, dashes or underscores and a password with at least 10 characters.";
}

View File

@@ -1,7 +1,9 @@
import { redirect } from "next/navigation";
import { unstable_noStore as noStore } from "next/cache";
import { prisma } from "./prisma";
export async function hasAdminUser() {
noStore();
try {
const admin = await prisma.userRole.findFirst({
where: { role: { name: "admin" } },