diff --git a/src/app/api/auth/login/route.ts b/src/app/api/auth/login/route.ts index 57a49f7..018a809 100644 --- a/src/app/api/auth/login/route.ts +++ b/src/app/api/auth/login/route.ts @@ -52,19 +52,30 @@ export async function POST(request: NextRequest) { } // Verify password - const passwordHash = process.env.ADMIN_PASSWORD_HASH; + const setting = await prisma.setting.findUnique({ + where: { key: "admin_password_hash" }, + }); - // For development: if no hash is set, accept any non-empty password let isValid = false; - if (!passwordHash) { - isValid = body.password.length > 0; + + if (!setting) { + // Initial setup: hash and save the new password + const argon2 = await import("argon2"); + const newHash = await argon2.hash(body.password); + await prisma.setting.create({ + data: { + key: "admin_password_hash", + value: newHash, + }, + }); + isValid = true; } else { - // Dynamic import to avoid issues when argon2 isn't installed + // Verify existing password + const passwordHash = setting.value; try { const argon2 = await import("argon2"); isValid = await argon2.verify(passwordHash, body.password); } catch { - // Fallback: direct comparison (only for development) isValid = body.password === passwordHash; } } diff --git a/src/app/api/auth/setup-status/route.ts b/src/app/api/auth/setup-status/route.ts new file mode 100644 index 0000000..24816e0 --- /dev/null +++ b/src/app/api/auth/setup-status/route.ts @@ -0,0 +1,18 @@ +import { NextResponse } from "next/server"; +import { prisma } from "@/lib/db"; + +export async function GET() { + try { + const setting = await prisma.setting.findUnique({ + where: { key: "admin_password_hash" }, + }); + + return NextResponse.json({ setupRequired: !setting }); + } catch (error) { + console.error("Failed to check setup status:", error); + return NextResponse.json( + { error: "Failed to check setup status" }, + { status: 500 } + ); + } +} diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx index d9fca7c..5005bad 100644 --- a/src/app/login/page.tsx +++ b/src/app/login/page.tsx @@ -1,14 +1,22 @@ "use client"; -import { useState } from "react"; +import { useState, useEffect } from "react"; import { useRouter } from "next/navigation"; export default function LoginPage() { const [password, setPassword] = useState(""); const [error, setError] = useState(""); const [loading, setLoading] = useState(false); + const [isSetup, setIsSetup] = useState(null); const router = useRouter(); + useEffect(() => { + fetch("/api/auth/setup-status") + .then((res) => res.json()) + .then((data) => setIsSetup(data.setupRequired)) + .catch(() => setIsSetup(false)); // fallback + }, []); + async function handleSubmit(e: React.FormEvent) { e.preventDefault(); setError(""); @@ -37,9 +45,19 @@ export default function LoginPage() { } } + if (isSetup === null) { + return ( +
+
+
+
+
+ ); + } + return (
-
+
{/* Logo / Title area */}
@@ -57,9 +75,13 @@ export default function LoginPage() { />
-

Study App

+

+ {isSetup ? "Welcome to Study App!" : "Study App"} +

- Enter your password to continue + {isSetup + ? "Please set your admin password for the first time." + : "Enter your password to continue"}

@@ -78,7 +100,7 @@ export default function LoginPage() { type="password" value={password} onChange={(e) => setPassword(e.target.value)} - placeholder="Enter your password" + placeholder={isSetup ? "Create a new password" : "Enter your password"} autoFocus className="w-full px-3.5 py-2.5 rounded-lg border border-border bg-bg-surface-alt/50 text-text-heading placeholder:text-text-muted focus:outline-none focus:ring-2 focus:ring-primary/30 focus:border-primary transition-all duration-200" /> @@ -129,8 +151,10 @@ export default function LoginPage() { d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" /> - Signing in... + {isSetup ? "Saving..." : "Signing in..."} + ) : isSetup ? ( + "Save Password & Login" ) : ( "Sign In" )}