feat: two-factor authentication (TOTP + backup codes)
Uses better-auth's built-in twoFactor plugin rather than hand-rolling TOTP — adds the two_factors table and users.twoFactorEnabled, wires the server/client plugins (allowPasswordless: true so OAuth-only accounts aren't locked out of managing 2FA), and adds a custom rate limit for the verify endpoints (5/min — far stricter than the default 100/10s, since a 6-digit code has a much smaller keyspace than a password). New Settings → Security section walks through enable (password confirm -> QR + backup codes -> confirm a live code before it's actually turned on, per the plugin's default flow) and disable. New /verify-2fa page handles the post-password mid-login step, with a backup-code fallback. Verified live end-to-end: enable, confirm, forced re-auth on next sign-in, TOTP accepted, backup code accepted (single-use), disable. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -7,8 +7,17 @@ import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { authClient } from "@/lib/auth/client";
|
||||
import { TwoFactorSettings } from "@/components/settings/two-factor-settings";
|
||||
|
||||
export function SecurityForm({ currentEmail }: { currentEmail: string }) {
|
||||
export function SecurityForm({
|
||||
currentEmail,
|
||||
twoFactorEnabled,
|
||||
hasPassword,
|
||||
}: {
|
||||
currentEmail: string;
|
||||
twoFactorEnabled: boolean;
|
||||
hasPassword: boolean;
|
||||
}) {
|
||||
const t = useTranslations("settingsForm");
|
||||
|
||||
const [newEmail, setNewEmail] = useState("");
|
||||
@@ -139,6 +148,8 @@ export function SecurityForm({ currentEmail }: { currentEmail: string }) {
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<TwoFactorSettings initialEnabled={twoFactorEnabled} hasPassword={hasPassword} />
|
||||
|
||||
<section className="rounded-xl border p-6 space-y-4">
|
||||
<div>
|
||||
<h2 className="font-semibold text-lg">{t("downloadData")}</h2>
|
||||
|
||||
Reference in New Issue
Block a user