"use client"; import { useState } from "react"; import { Button } from "@/components/ui/button"; import { Separator } from "@/components/ui/separator"; import { Badge } from "@/components/ui/badge"; import { Globe, LogOut, Loader2, Fingerprint, Trash2, Plus, } from "lucide-react"; import { useMutation, useQuery } from "@tanstack/react-query"; import { toast } from "sonner"; import { revokeAllSessionsAction, revokeSessionAction, getPasskeysAction, revokePasskeyAction, } from "./security.action"; import { useRouter } from "next/navigation"; import { ResetPasswordProfileProviderModal } from "./reset-password-modal"; import { SetPasswordProfileProviderModal } from "./set-password-modal"; import { Setup2FAProfileProviderModal } from "./setup-2fa-modal"; import { Disable2FAProfileProviderModal } from "./disable-2fa-modal"; import { ViewBackupCodesModal } from "./view-backup-codes-modal"; import { getDeviceDetails } from "@/utils/detection"; import { timeAgo } from "@/utils/date-formatting"; import { authClient } from "@/lib/auth/auth-client"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, } from "@/components/ui/dialog"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Account, Session, User } from "@/db/schema/02_user"; import { Icon } from "@iconify/react"; import Image from "next/image"; import type { AuthProviderConfig } from "@/lib/auth/config"; import { is } from "date-fns/locale"; interface ProfileSecurityProps { user: User; sessions: Session[]; credentialAccount: Account; currentSession: Session; isPasswordEnabled?: boolean; isPasskeyEnabled?: boolean; providers: AuthProviderConfig[]; } export function ProfileSecurity({ user, sessions, credentialAccount, currentSession, isPasswordEnabled = false, isPasskeyEnabled = false, providers, }: ProfileSecurityProps) { const router = useRouter(); const [isBackupCodesDialogOpen, setIsBackupCodesDialogOpen] = useState(false); const [isPasswordDialogOpen, setIsPasswordDialogOpen] = useState(false); const [isSetup2FADialogOpen, setIsSetup2FADialogOpen] = useState(false); const [isDisable2FADialogOpen, setIsDisable2FADialogOpen] = useState(false); const [isAddPasskeyOpen, setIsAddPasskeyOpen] = useState(false); const [passkeyName, setPasskeyName] = useState(""); const { mutate: revokeSession, isPending: isRevoking } = useMutation({ mutationFn: async (token: string) => { const result = await revokeSessionAction({ token }); const inner = result?.data; if (inner?.success) { toast.success("Session successfully revoked"); router.refresh(); } else { toast.error("An error occurred while revoking session"); } }, }); const { mutate: revokeOthers, isPending: isRevokingOthers } = useMutation({ mutationFn: async () => { const result = await revokeAllSessionsAction(); const inner = result?.data; if (inner?.success) { toast.success("Revoking all sessions successfully done."); router.refresh(); } else { toast.error("An error occurred while revoking all sessions"); } }, }); const { data: passkeys, isLoading: isLoadingPasskeys, refetch: refetchPasskeys, } = useQuery({ queryKey: ["passkeys"], queryFn: async () => { const result = await getPasskeysAction(); if (result?.data?.success) { return result.data.value; } throw new Error("Failed to fetch passkeys"); }, }); const { mutate: revokePasskey, isPending: isRevokingPasskey } = useMutation({ mutationFn: async (id: string) => { const result = await revokePasskeyAction({ id }); if (!result?.data?.success) { throw new Error("Failed to revoke passkey"); } }, onSuccess: () => { toast.success("Passkey revoked successfully"); refetchPasskeys(); }, onError: () => { toast.error("Failed to revoke passkey"); }, }); const { mutate: addPasskey, isPending: isAddingPasskey } = useMutation({ mutationFn: async () => { const result = await authClient.passkey.addPasskey({ name: passkeyName || "My Passkey", }); if (result?.error) { throw result.error; } return result; }, onSuccess: () => { toast.success("Passkey added successfully"); setIsAddPasskeyOpen(false); setPasskeyName(""); refetchPasskeys(); }, onError: (error: any) => { toast.error(error.message || "Failed to add passkey"); }, }); return (

Security Settings

Manage your password, two-factor authentication and sessions.

{(isPasskeyEnabled || isPasswordEnabled || user.twoFactorEnabled) && (

Authentication

{isPasswordEnabled && ( <>
Password
{user.lastChangedPasswordAt ? `Last changed ${timeAgo(new Date(user.lastChangedPasswordAt))}` : "Never changed"}
{credentialAccount ? ( ) : ( )}
)} {isPasswordEnabled && (
Two-Factor Authentication
{user.twoFactorEnabled && ( Active )}
Enhance the security of your account by requiring a second form of verification during login.
{user.twoFactorEnabled ? (
) : ( )}
)}
)} {isPasskeyEnabled && (

Passkeys

Login securely with your fingerprint, face recognition, or hardware key.
Add New Passkey Create a name for your passkey to identify it later.
setPasskeyName(e.target.value)} />
{isLoadingPasskeys ? (
) : passkeys && passkeys.length > 0 ? ( passkeys.map((pk: any) => ( revokePasskey(id)} isRevoking={isRevokingPasskey} /> )) ) : (
No passkeys found.
)}
)}

Active Sessions

{sessions && sessions.length > 1 && ( )}
{sessions && sessions.length > 0 ? ( sessions?.map((session) => ( revokeSession(token)} isRevoking={isRevoking} currentSession={currentSession} providers={providers} /> )) ) : (
No active sessions found.
)}
); } function SessionRow({ session, onRevoke, isRevoking, currentSession, providers, }: { session: Session; onRevoke: (token: string) => void; isRevoking: boolean; currentSession: Session; providers: AuthProviderConfig[]; }) { const deviceInfo = getDeviceDetails(session.userAgent); const provider = providers.find((p) => p.id === (session as any).providerId); return (
{provider && (
{provider.icon.startsWith("/") || provider.icon.startsWith("http") ? ( {provider.id} ) : ( )}
)}
{deviceInfo.os}{" "} • {deviceInfo.browser} {provider && ( • {provider.title || provider.name} )} {session.id === currentSession.id && ( This device )}
{session.ipAddress} • {session.id === currentSession.id ? "Active now" : `Last active ${timeAgo(new Date(session.createdAt))}`}
{session.id !== currentSession.id && ( )}
); } function PasskeyRow({ passkey, onRevoke, isRevoking, }: { passkey: any; onRevoke: (id: string) => void; isRevoking: boolean; }) { return (
{passkey.name || "Unnamed Passkey"}
Created {timeAgo(new Date(passkey.createdAt))}
); }