Konto: Passwort-Anmeldung pro Nutzer deaktivierbar
Schalter im eigenen Konto, erzwingt vorher mindestens einen registrierten Passkey (sonst Aussperrung). Durchsetzung beim Login: nach erfolgreicher Passwort-Prüfung wird bei deaktiviertem Flag sofort wieder abgemeldet; Passkey-Anmeldung bleibt davon unberührt.
This commit is contained in:
+2
-1
@@ -12,7 +12,7 @@ export default async function KontoPage() {
|
||||
|
||||
const { data: profile } = await supabase
|
||||
.from("profiles")
|
||||
.select("role, full_name")
|
||||
.select("role, full_name, password_auth_enabled")
|
||||
.eq("id", user.id)
|
||||
.single();
|
||||
if (!profile || !["staff", "admin"].includes(profile.role)) redirect("/");
|
||||
@@ -28,6 +28,7 @@ export default async function KontoPage() {
|
||||
role={profile.role}
|
||||
initialFullName={profile.full_name ?? ""}
|
||||
initialEmail={user.email ?? ""}
|
||||
initialPasswordAuthEnabled={profile.password_auth_enabled}
|
||||
/>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
+16
-1
@@ -46,7 +46,22 @@ export default function LoginPage() {
|
||||
setBusy(false);
|
||||
return;
|
||||
}
|
||||
await redirectByRole(data.user.id);
|
||||
|
||||
const { data: profile } = await supabase
|
||||
.from("profiles")
|
||||
.select("role, password_auth_enabled")
|
||||
.eq("id", data.user.id)
|
||||
.single();
|
||||
if (profile?.password_auth_enabled === false) {
|
||||
await supabase.auth.signOut();
|
||||
setError(
|
||||
"Die Anmeldung mit Passwort ist für dieses Konto deaktiviert. Bitte mit Passkey anmelden."
|
||||
);
|
||||
setBusy(false);
|
||||
return;
|
||||
}
|
||||
router.replace(profile?.role === "admin" ? "/admin" : "/intern");
|
||||
router.refresh();
|
||||
}
|
||||
|
||||
async function signInWithPasskey() {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { useMemo, useState } from "react";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { createClient } from "@/lib/supabase/client";
|
||||
import PasskeyManager from "@/components/PasskeyManager";
|
||||
@@ -24,10 +24,12 @@ export default function AccountManager({
|
||||
role,
|
||||
initialFullName,
|
||||
initialEmail,
|
||||
initialPasswordAuthEnabled,
|
||||
}: {
|
||||
role: string;
|
||||
initialFullName: string;
|
||||
initialEmail: string;
|
||||
initialPasswordAuthEnabled: boolean;
|
||||
}) {
|
||||
const supabase = useMemo(() => createClient(), []);
|
||||
const router = useRouter();
|
||||
@@ -52,6 +54,21 @@ export default function AccountManager({
|
||||
const [pwInfo, setPwInfo] = useState<string | null>(null);
|
||||
const [pwBusy, setPwBusy] = useState(false);
|
||||
|
||||
// Passwort-Anmeldung an/aus
|
||||
const [passwordAuthEnabled, setPasswordAuthEnabled] = useState(
|
||||
initialPasswordAuthEnabled
|
||||
);
|
||||
const [passkeyCount, setPasskeyCount] = useState<number | null>(null);
|
||||
const [authError, setAuthError] = useState<string | null>(null);
|
||||
const [authInfo, setAuthInfo] = useState<string | null>(null);
|
||||
const [authBusy, setAuthBusy] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
supabase.auth.passkey
|
||||
.list()
|
||||
.then(({ data }) => setPasskeyCount((data ?? []).length));
|
||||
}, [supabase]);
|
||||
|
||||
async function saveName(e: React.FormEvent) {
|
||||
e.preventDefault();
|
||||
setNameError(null);
|
||||
@@ -129,6 +146,40 @@ export default function AccountManager({
|
||||
setPwBusy(false);
|
||||
}
|
||||
|
||||
async function togglePasswordAuth() {
|
||||
setAuthError(null);
|
||||
setAuthInfo(null);
|
||||
const next = !passwordAuthEnabled;
|
||||
if (!next && (passkeyCount ?? 0) === 0) {
|
||||
setAuthError(
|
||||
"Ohne registrierten Passkey kannst du die Passwort-Anmeldung nicht deaktivieren – sonst wärst du ausgesperrt. Registriere zuerst einen Passkey weiter unten."
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (
|
||||
!next &&
|
||||
!window.confirm(
|
||||
"Anmeldung mit E-Mail und Passwort wirklich deaktivieren? Du kannst dich danach nur noch per Passkey anmelden."
|
||||
)
|
||||
)
|
||||
return;
|
||||
|
||||
setAuthBusy(true);
|
||||
const { error } = await supabase
|
||||
.from("profiles")
|
||||
.update({ password_auth_enabled: next })
|
||||
.eq("id", (await supabase.auth.getUser()).data.user?.id ?? "");
|
||||
if (error) {
|
||||
setAuthError(`Speichern fehlgeschlagen: ${error.message}`);
|
||||
} else {
|
||||
setPasswordAuthEnabled(next);
|
||||
setAuthInfo(
|
||||
next ? "Passwort-Anmeldung aktiviert." : "Passwort-Anmeldung deaktiviert."
|
||||
);
|
||||
}
|
||||
setAuthBusy(false);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="max-w-3xl space-y-6">
|
||||
<div>
|
||||
@@ -256,6 +307,30 @@ export default function AccountManager({
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<section className="card">
|
||||
<h2 className="font-semibold mb-1">Anmeldung mit Passwort</h2>
|
||||
<p className="text-sm text-gray-600 mb-3">
|
||||
Du kannst die Anmeldung mit E-Mail und Passwort für dein Konto
|
||||
deaktivieren und dich künftig ausschließlich per Passkey anmelden.
|
||||
Dafür muss mindestens ein Passkey registriert sein.
|
||||
</p>
|
||||
{authError && (
|
||||
<p className="mb-2 text-sm text-[var(--c-del)]" role="alert">
|
||||
{authError}
|
||||
</p>
|
||||
)}
|
||||
{authInfo && <p className="mb-2 text-sm text-green-700">{authInfo}</p>}
|
||||
<label className="flex items-center gap-2 cursor-pointer text-sm w-fit">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={passwordAuthEnabled}
|
||||
onChange={togglePasswordAuth}
|
||||
disabled={authBusy || passkeyCount === null}
|
||||
/>
|
||||
Anmeldung mit E-Mail und Passwort erlaubt
|
||||
</label>
|
||||
</section>
|
||||
|
||||
<PasskeyManager />
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user