Block 2FA setup in demo mode at all entry points
The dashboard had a 'Set up 2FA' banner link to /security/totp that bypassed the settings button guard entirely. Three fixes: - Dashboard: hide the 2FA nudge banner completely in demo mode - TwoFactorSetupPage: redirect to /settings on mount if isDemo, and disable the setup query so no API call fires even briefly - This covers both the UI entry point and direct URL navigation Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
bc1ed8372d
commit
b30e8e577b
2 changed files with 12 additions and 3 deletions
|
|
@ -1,4 +1,4 @@
|
||||||
import { useState } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
|
@ -7,6 +7,7 @@ import { getTotpSetup, enableTotp } from "@/api/auth";
|
||||||
import { useAuthStore } from "@/store/authStore";
|
import { useAuthStore } from "@/store/authStore";
|
||||||
import { useQuery, useMutation } from "@tanstack/react-query";
|
import { useQuery, useMutation } from "@tanstack/react-query";
|
||||||
import { ShieldCheck, Copy, CheckCircle, Loader2 } from "lucide-react";
|
import { ShieldCheck, Copy, CheckCircle, Loader2 } from "lucide-react";
|
||||||
|
import { useDemoMode } from "@/hooks/useDemoMode";
|
||||||
|
|
||||||
const schema = z.object({ code: z.string().length(6, "6-digit code required") });
|
const schema = z.object({ code: z.string().length(6, "6-digit code required") });
|
||||||
type Form = z.infer<typeof schema>;
|
type Form = z.infer<typeof schema>;
|
||||||
|
|
@ -14,6 +15,11 @@ type Form = z.infer<typeof schema>;
|
||||||
export default function TwoFactorSetupPage() {
|
export default function TwoFactorSetupPage() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { setTotpEnabled } = useAuthStore();
|
const { setTotpEnabled } = useAuthStore();
|
||||||
|
const isDemo = useDemoMode();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isDemo) navigate("/settings", { replace: true });
|
||||||
|
}, [isDemo, navigate]);
|
||||||
const [copied, setCopied] = useState(false);
|
const [copied, setCopied] = useState(false);
|
||||||
const [secret, setSecret] = useState<string | null>(null);
|
const [secret, setSecret] = useState<string | null>(null);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
@ -25,6 +31,7 @@ export default function TwoFactorSetupPage() {
|
||||||
setSecret(res.secret);
|
setSecret(res.secret);
|
||||||
return res;
|
return res;
|
||||||
},
|
},
|
||||||
|
enabled: !isDemo,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { register, handleSubmit, formState } = useForm<Form>({
|
const { register, handleSubmit, formState } = useForm<Form>({
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ import {
|
||||||
} from "recharts";
|
} from "recharts";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { TOOLTIP_STYLE, ACTIVE_DOT } from "@/utils/chartTheme";
|
import { TOOLTIP_STYLE, ACTIVE_DOT } from "@/utils/chartTheme";
|
||||||
|
import { useDemoMode } from "@/hooks/useDemoMode";
|
||||||
|
|
||||||
const COLORS = ["#6366f1","#22c55e","#f97316","#ec4899","#14b8a6","#f59e0b","#8b5cf6","#ef4444"];
|
const COLORS = ["#6366f1","#22c55e","#f97316","#ec4899","#14b8a6","#f59e0b","#8b5cf6","#ef4444"];
|
||||||
|
|
||||||
|
|
@ -29,6 +30,7 @@ const TYPE_COLORS: Record<string, string> = {
|
||||||
export default function Dashboard() {
|
export default function Dashboard() {
|
||||||
const displayName = useAuthStore((s) => s.displayName);
|
const displayName = useAuthStore((s) => s.displayName);
|
||||||
const totpEnabled = useAuthStore((s) => s.totpEnabled);
|
const totpEnabled = useAuthStore((s) => s.totpEnabled);
|
||||||
|
const isDemo = useDemoMode();
|
||||||
|
|
||||||
const { data: nw } = useQuery({ queryKey: ["net-worth"], queryFn: getNetWorth });
|
const { data: nw } = useQuery({ queryKey: ["net-worth"], queryFn: getNetWorth });
|
||||||
const { data: accounts = [] } = useQuery({ queryKey: ["accounts"], queryFn: getAccounts });
|
const { data: accounts = [] } = useQuery({ queryKey: ["accounts"], queryFn: getAccounts });
|
||||||
|
|
@ -52,8 +54,8 @@ export default function Dashboard() {
|
||||||
<p className="text-muted-foreground text-sm mt-1">Here's your financial overview</p>
|
<p className="text-muted-foreground text-sm mt-1">Here's your financial overview</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 2FA nudge */}
|
{/* 2FA nudge — hidden in demo mode */}
|
||||||
{!totpEnabled && (
|
{!totpEnabled && !isDemo && (
|
||||||
<div className="flex items-center gap-3 bg-warning/10 border border-warning/30 rounded-xl px-4 py-3">
|
<div className="flex items-center gap-3 bg-warning/10 border border-warning/30 rounded-xl px-4 py-3">
|
||||||
<ShieldAlert className="w-5 h-5 text-warning shrink-0" />
|
<ShieldAlert className="w-5 h-5 text-warning shrink-0" />
|
||||||
<p className="flex-1 text-sm">
|
<p className="flex-1 text-sm">
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue