diff --git a/src/components/auth/PasswordVerificationDialog.tsx b/src/components/auth/PasswordVerificationDialog.tsx new file mode 100644 index 00000000..0ef10437 --- /dev/null +++ b/src/components/auth/PasswordVerificationDialog.tsx @@ -0,0 +1,152 @@ +import { useState } from "react"; +import { Button } from "@/components/ui/button"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { reverifyPasswordAuth } from "@/lib/identityService"; +import { toast } from "sonner"; +import { Loader2, AlertCircle } from "lucide-react"; +import { Alert, AlertDescription } from "@/components/ui/alert"; + +interface PasswordVerificationDialogProps { + open: boolean; + onOpenChange: (open: boolean) => void; + onSuccess: () => void; + defaultEmail?: string; +} + +export function PasswordVerificationDialog({ + open, + onOpenChange, + onSuccess, + defaultEmail = "", +}: PasswordVerificationDialogProps) { + const [email, setEmail] = useState(defaultEmail); + const [password, setPassword] = useState(""); + const [isVerifying, setIsVerifying] = useState(false); + const [showResetOption, setShowResetOption] = useState(false); + + const handleVerify = async (e: React.FormEvent) => { + e.preventDefault(); + + if (!email || !password) { + toast.error("Please enter both email and password"); + return; + } + + setIsVerifying(true); + setShowResetOption(false); + + try { + const result = await reverifyPasswordAuth(email, password); + + if (result.success) { + toast.success("Password Verified!", { + description: "Your password authentication has been activated.", + }); + onOpenChange(false); + onSuccess(); + } else { + setShowResetOption(true); + toast.error("Verification Failed", { + description: result.error || "Unable to verify password. Try the password reset option below.", + }); + } + } catch (error: any) { + setShowResetOption(true); + toast.error("Verification Error", { + description: error.message || "An unexpected error occurred.", + }); + } finally { + setIsVerifying(false); + } + }; + + const handlePasswordReset = () => { + onOpenChange(false); + // Navigate or trigger password reset flow + window.location.href = `/auth?email=${encodeURIComponent(email)}&message=reset-password`; + }; + + return ( + + + + Verify Password Access + + Enter your email and password to activate password authentication for your account. + + + +
+
+
+ + setEmail(e.target.value)} + placeholder="your@email.com" + disabled={isVerifying} + required + /> +
+ +
+ + setPassword(e.target.value)} + placeholder="Enter your password" + disabled={isVerifying} + required + /> +
+ + {showResetOption && ( + + + + Can't remember your password?{" "} + + + + )} +
+ + + + + +
+
+
+ ); +} diff --git a/src/components/settings/SecurityTab.tsx b/src/components/settings/SecurityTab.tsx index ed3bb682..9d95784d 100644 --- a/src/components/settings/SecurityTab.tsx +++ b/src/components/settings/SecurityTab.tsx @@ -16,14 +16,18 @@ import { getUserIdentities, checkDisconnectSafety, disconnectIdentity, - connectIdentity + connectIdentity, + hasOrphanedPassword } from '@/lib/identityService'; import type { UserIdentity, OAuthProvider } from '@/types/identity'; +import { PasswordVerificationDialog } from '@/components/auth/PasswordVerificationDialog'; +import { toast as sonnerToast } from 'sonner'; export function SecurityTab() { const { user } = useAuth(); const { toast } = useToast(); const navigate = useNavigate(); const [passwordDialogOpen, setPasswordDialogOpen] = useState(false); + const [verificationDialogOpen, setVerificationDialogOpen] = useState(false); const [identities, setIdentities] = useState([]); const [loadingIdentities, setLoadingIdentities] = useState(true); const [disconnectingProvider, setDisconnectingProvider] = useState(null); @@ -31,12 +35,33 @@ export function SecurityTab() { const [hasPassword, setHasPassword] = useState(false); const [addPasswordMode, setAddPasswordMode] = useState<'standalone' | 'disconnect'>('standalone'); const [addingPassword, setAddingPassword] = useState(false); + const [showOrphanedPasswordOption, setShowOrphanedPasswordOption] = useState(false); // Load user identities on mount useEffect(() => { loadIdentities(); }, []); + useEffect(() => { + const checkOrphanedPassword = async () => { + if (!hasPassword) { + const isOrphaned = await hasOrphanedPassword(); + setShowOrphanedPasswordOption(isOrphaned); + + if (isOrphaned) { + sonnerToast.info("Password Authentication Needs Activation", { + description: "Click 'Verify Password Access' to complete your password setup.", + duration: 10000, + }); + } + } + }; + + if (!loadingIdentities) { + checkOrphanedPassword(); + } + }, [hasPassword, loadingIdentities]); + const loadIdentities = async () => { try { setLoadingIdentities(true); @@ -162,6 +187,15 @@ export function SecurityTab() { setPasswordSetupProvider('google' as OAuthProvider); }; + const handleVerifyExistingPassword = () => { + setVerificationDialogOpen(true); + }; + + const handleVerificationSuccess = async () => { + await loadIdentities(); + sonnerToast.success("Password authentication activated successfully!"); + }; + // Get connected accounts with identity data const connectedAccounts = [ { @@ -198,6 +232,13 @@ export function SecurityTab() { /> )} + +
{/* Password Section - Conditional based on auth method */}
@@ -216,22 +257,34 @@ export function SecurityTab() { )} - + {hasPassword ? ( ) : ( - + + {showOrphanedPasswordOption && ( + )} - + )}