import { PermissionAction } from '@supabase/shared-types/out/constants' import { useParams } from 'common' import dayjs from 'dayjs' import { Ban, Check, Copy, Mail, ShieldOff, Trash, X } from 'lucide-react' import Link from 'next/link' import { ComponentProps, ReactNode, useEffect, useState } from 'react' import { toast } from 'sonner' import { Button, cn, Separator } from 'ui' import { Admonition } from 'ui-patterns/admonition' import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal' import { PROVIDERS_SCHEMAS } from '../AuthProvidersFormValidation' import { BanUserModal } from './BanUserModal' import { DeleteUserModal } from './DeleteUserModal' import { UserHeader } from './UserHeader' import { PANEL_PADDING } from './Users.constants' import { providerIconMap } from './Users.utils' import { ButtonTooltip } from '@/components/ui/ButtonTooltip' import CopyButton from '@/components/ui/CopyButton' import { useAuthConfigQuery } from '@/data/auth/auth-config-query' import { useUserDeleteMFAFactorsMutation } from '@/data/auth/user-delete-mfa-factors-mutation' import { useUserResetPasswordMutation } from '@/data/auth/user-reset-password-mutation' import { useUserSendMagicLinkMutation } from '@/data/auth/user-send-magic-link-mutation' import { useUserSendOTPMutation } from '@/data/auth/user-send-otp-mutation' import { useUserUpdateMutation } from '@/data/auth/user-update-mutation' import { User } from '@/data/auth/users-infinite-query' import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions' import { useIsFeatureEnabled } from '@/hooks/misc/useIsFeatureEnabled' import { BASE_PATH } from '@/lib/constants' import { timeout } from '@/lib/helpers' const DATE_FORMAT = 'DD MMM, YYYY HH:mm' const CONTAINER_CLASS = cn( 'bg-surface-100 border-default text-foreground flex items-center justify-between', 'gap-x-4 border px-5 py-4 text-sm first:rounded-tr first:rounded-tl last:rounded-br last:rounded-bl' ) interface UserOverviewProps { user: User onDeleteSuccess: () => void } export const UserOverview = ({ user, onDeleteSuccess }: UserOverviewProps) => { const { ref: projectRef } = useParams() const isEmailAuth = user.email !== null const isPhoneAuth = user.phone !== null const isBanned = user.banned_until !== null const isVerified = user.confirmed_at != null const { authenticationSignInProviders } = useIsFeatureEnabled([ 'authentication:sign_in_providers', ]) const providers = ((user.raw_app_meta_data?.providers as string[]) ?? []).map( (provider: string) => { return { name: provider.startsWith('sso') ? 'SAML' : provider, icon: provider === 'email' ? `${BASE_PATH}/img/icons/email-icon2.svg` : providerIconMap[provider] ? `${BASE_PATH}/img/icons/${providerIconMap[provider]}.svg` : undefined, } } ) const { can: canUpdateUser } = useAsyncCheckPermissions(PermissionAction.AUTH_EXECUTE, '*') const { can: canSendMagicLink } = useAsyncCheckPermissions( PermissionAction.AUTH_EXECUTE, 'send_magic_link' ) const { can: canSendRecovery } = useAsyncCheckPermissions( PermissionAction.AUTH_EXECUTE, 'send_recovery' ) const { can: canSendOtp } = useAsyncCheckPermissions(PermissionAction.AUTH_EXECUTE, 'send_otp') const { can: canRemoveUser } = useAsyncCheckPermissions( PermissionAction.TENANT_SQL_DELETE, 'auth.users' ) const { can: canRemoveMFAFactors } = useAsyncCheckPermissions( PermissionAction.TENANT_SQL_DELETE, 'auth.mfa_factors' ) const [successAction, setSuccessAction] = useState< 'send_magic_link' | 'send_recovery' | 'send_otp' >() const [isBanModalOpen, setIsBanModalOpen] = useState(false) const [isUnbanModalOpen, setIsUnbanModalOpen] = useState(false) const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false) const [isDeleteFactorsModalOpen, setIsDeleteFactorsModalOpen] = useState(false) const { data } = useAuthConfigQuery({ projectRef }) const mailerOtpExpiry = data?.MAILER_OTP_EXP ?? 0 const minutes = Math.floor(mailerOtpExpiry / 60) const seconds = Math.floor(mailerOtpExpiry % 60) const formattedExpiry = `${mailerOtpExpiry > 60 ? `${minutes} minute${minutes > 1 ? 's' : ''} ${seconds > 0 ? 'and' : ''} ` : ''}${seconds > 0 ? `${seconds} second${seconds > 1 ? 's' : ''}` : ''}` const { mutate: resetPassword, isPending: isResettingPassword } = useUserResetPasswordMutation({ onSuccess: (_, vars) => { setSuccessAction('send_recovery') toast.success(`Sent password recovery to ${vars.user.email}`) }, onError: (err) => { toast.error(`Failed to send password recovery: ${err.message}`) }, }) const { mutate: sendMagicLink, isPending: isSendingMagicLink } = useUserSendMagicLinkMutation({ onSuccess: (_, vars) => { setSuccessAction('send_magic_link') toast.success( isVerified ? `Sent magic link to ${vars.user.email}` : `Sent confirmation email to ${vars.user.email}` ) }, onError: (err) => { toast.error( isVerified ? `Failed to send magic link: ${err.message}` : `Failed to send confirmation email: ${err.message}` ) }, }) const { mutate: sendOTP, isPending: isSendingOTP } = useUserSendOTPMutation({ onSuccess: (_, vars) => { setSuccessAction('send_otp') toast.success(`Sent OTP to ${vars.user.phone}`) }, onError: (err) => { toast.error(`Failed to send OTP: ${err.message}`) }, }) const { mutate: deleteUserMFAFactors } = useUserDeleteMFAFactorsMutation({ onSuccess: () => { toast.success("Successfully deleted the user's factors") setIsDeleteFactorsModalOpen(false) }, }) const { mutate: updateUser, isPending: isUpdatingUser } = useUserUpdateMutation({ onSuccess: () => { toast.success('Successfully unbanned user') setIsUnbanModalOpen(false) }, }) const handleDeleteFactors = async () => { await timeout(200) if (!projectRef) return console.error('Project ref is required') deleteUserMFAFactors({ projectRef, userId: user.id as string }) } const handleUnban = () => { if (projectRef === undefined) return console.error('Project ref is required') if (user.id === undefined) { return toast.error(`Failed to ban user: User ID not found`) } updateUser({ projectRef, userId: user.id, banDuration: 'none', }) } useEffect(() => { if (successAction !== undefined) { const timer = setTimeout(() => setSuccessAction(undefined), 5000) return () => clearTimeout(timer) } }, [successAction]) return ( <>
Provider Information
The user has the following providers
{providerName}
Signed in with a {providerName} account via{' '} {providerName === 'SAML' ? 'SSO' : 'OAuth'}
{authenticationSignInProviders && ( )}Danger zone
Be wary of the following features as they cannot be undone.
Are you sure you want to remove the MFA factors for the user{' '} {user.email ?? user.phone ?? 'this user'}?
The user will have access to your project again once unbanned. Are you sure you want to unban this user?
{property}
{typeof value === 'boolean' ? ({!value ? '-' : value}
{!!value && ({success ? success.title : title}
{success ? success.description : description}