Home / Function/ UserOverview() — supabase Function Reference

UserOverview() — supabase Function Reference

Architecture documentation for the UserOverview() function in UserOverview.tsx from the supabase codebase.

Entity Profile

Relationship Graph

Source Code

apps/studio/components/interfaces/Auth/Users/UserOverview.tsx lines 43–470

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 (
    <>
      <div>
        <UserHeader user={user} />

        {isBanned ? (
          <Admonition
            type="warning"
            label={`User banned until ${dayjs(user.banned_until).format(DATE_FORMAT)}`}
            className="border-r-0 border-l-0 rounded-none -mt-px [&_svg]:ml-0.5"
          />
        ) : (
          <Separator />
        )}

        <div className={cn('flex flex-col gap-y-1', PANEL_PADDING)}>
          <RowData property="User UID" value={user.id} />
          <RowData
            property="Created at"
            value={user.created_at ? dayjs(user.created_at).format(DATE_FORMAT) : undefined}
          />
          <RowData
            property="Updated at"
            value={user.updated_at ? dayjs(user.updated_at).format(DATE_FORMAT) : undefined}
          />
          <RowData property="Invited at" value={user.invited_at} />
          <RowData property="Confirmation sent at" value={user.confirmation_sent_at} />
          <RowData
            property="Confirmed at"
            value={user.confirmed_at ? dayjs(user.confirmed_at).format(DATE_FORMAT) : undefined}
          />
          <RowData
            property="Last signed in"
            value={
              user.last_sign_in_at ? dayjs(user.last_sign_in_at).format(DATE_FORMAT) : undefined
            }
          />
          <RowData property="SSO" value={user.is_sso_user} />
        </div>

        <div className={cn('flex flex-col !pt-0', PANEL_PADDING)}>
          <p>Provider Information</p>
          <p className="text-sm text-foreground-light">The user has the following providers</p>
        </div>

        <div className={cn('flex flex-col -space-y-1 !pt-0', PANEL_PADDING)}>
          {providers.map((provider) => {
            const providerMeta = PROVIDERS_SCHEMAS.find(
              (x) =>
                ('key' in x && x.key === provider.name) || x.title.toLowerCase() === provider.name
            )
            const enabledProperty = Object.keys(providerMeta?.properties ?? {}).find((x) =>
              x.toLowerCase().endsWith('_enabled')
            )
            const providerName =
              provider.name === 'email'
                ? provider.name.toLowerCase()
                : providerMeta?.title ?? provider.name
            const isActive = data?.[enabledProperty as keyof typeof data] ?? false

            return (
              <div key={provider.name} className={cn(CONTAINER_CLASS, 'items-start justify-start')}>
                {provider.icon && (
                  <img
                    width={16}
                    src={provider.icon}
                    alt={`${provider.name} auth icon`}
                    className={cn('mt-1.5', provider.name === 'github' ? 'dark:invert' : '')}
                  />
                )}
                <div className="flex-grow mt-0.5">
                  <p className="capitalize">{providerName}</p>
                  <p className="text-xs text-foreground-light">
                    Signed in with a {providerName} account via{' '}
                    {providerName === 'SAML' ? 'SSO' : 'OAuth'}
                  </p>
                  {authenticationSignInProviders && (
                    <Button asChild type="default" className="mt-2">
                      <Link
                        href={`/project/${projectRef}/auth/providers?provider=${provider.name === 'SAML' ? 'SAML 2.0' : provider.name}`}
                      >
                        Configure {providerName} provider
                      </Link>
                    </Button>
                  )}
                </div>
                {isActive ? (
                  <div className="flex items-center gap-1 rounded-full border border-brand-400 bg-brand-200 py-1 px-1 text-xs text-brand">
                    <span className="rounded-full bg-brand p-0.5 text-xs text-brand-200">
                      <Check strokeWidth={2} size={12} />
                    </span>
                    <span className="px-1">Enabled</span>
                  </div>
                ) : (
                  <div className="rounded-md border border-strong bg-surface-100 py-1 px-3 text-xs text-foreground-lighter">
                    Disabled
                  </div>
                )}
              </div>
            )
          })}
        </div>

        <Separator />

        <div className={cn('flex flex-col -space-y-1', PANEL_PADDING)}>
          {isEmailAuth && (
            <>
              <RowAction
                title="Reset password"
                description="Send a password recovery email to the user"
                button={{
                  icon: <Mail />,
                  text: 'Send password recovery',
                  isLoading: isResettingPassword,
                  disabled: !canSendRecovery,
                  onClick: () => {
                    if (projectRef) resetPassword({ projectRef, user })
                  },
                }}
                success={
                  successAction === 'send_recovery'
                    ? {
                        title: 'Password recovery sent',
                        description: `The link in the email is valid for ${formattedExpiry}`,
                      }
                    : undefined
                }
              />
              <RowAction
                title={isVerified ? 'Send Magic Link' : 'Send confirmation email'}
                description={
                  isVerified
                    ? 'Passwordless login via email for the user'
                    : 'Send a confirmation email to the user'
                }
                button={{
                  icon: <Mail />,
                  text: isVerified ? 'Send magic link' : 'Send confirmation email',
                  isLoading: isSendingMagicLink,
                  disabled: !canSendMagicLink,
                  onClick: () => {
                    if (projectRef) sendMagicLink({ projectRef, user })
                  },
                }}
                success={
                  successAction === 'send_magic_link'
                    ? {
                        title: isVerified ? 'Magic link sent' : 'Confirmation email sent',
                        description: isVerified
                          ? `The link in the email is valid for ${formattedExpiry}`
                          : 'The confirmation email has been sent to the user',
                      }
                    : undefined
                }
              />
            </>
          )}
          {isPhoneAuth && (
            <RowAction
              title="Send OTP"
              description="Passwordless login via phone for the user"
              button={{
                icon: <Mail />,
                text: 'Send OTP',
                isLoading: isSendingOTP,
                disabled: !canSendOtp,
                onClick: () => {
                  if (projectRef) sendOTP({ projectRef, user })
                },
              }}
              success={
                successAction === 'send_otp'
                  ? {
                      title: 'OTP sent',
                      description: `The link in the OTP SMS is valid for ${formattedExpiry}`,
                    }
                  : undefined
              }
            />
          )}
        </div>

        <Separator />

        <div className={cn('flex flex-col', PANEL_PADDING)}>
          <p>Danger zone</p>
          <p className="text-sm text-foreground-light">
            Be wary of the following features as they cannot be undone.
          </p>
        </div>

        <div className={cn('flex flex-col -space-y-1 !pt-0', PANEL_PADDING)}>
          <RowAction
            title="Remove MFA factors"
            description="Removes all MFA factors associated with the user"
            button={{
              icon: <ShieldOff />,
              text: 'Remove MFA factors',
              disabled: !canRemoveMFAFactors,
              onClick: () => setIsDeleteFactorsModalOpen(true),
            }}
            className="!bg border-destructive-400"
          />
          <RowAction
            title={
              isBanned
                ? `User is banned until ${dayjs(user.banned_until).format(DATE_FORMAT)}`
                : 'Ban user'
            }
            description={
              isBanned
                ? 'User has no access to the project until after this date'
                : 'Revoke access to the project for a set duration'
            }
            button={{
              icon: <Ban />,
              text: isBanned ? 'Unban user' : 'Ban user',
              disabled: !canUpdateUser,
              onClick: () => {
                if (isBanned) {
                  setIsUnbanModalOpen(true)
                } else {
                  setIsBanModalOpen(true)
                }
              },
            }}
            className="!bg border-destructive-400"
          />
          <RowAction
            title="Delete user"
            description="User will no longer have access to the project"
            button={{
              icon: <Trash />,
              type: 'danger',
              text: 'Delete user',
              disabled: !canRemoveUser,
              onClick: () => setIsDeleteModalOpen(true),
            }}
            className="!bg border-destructive-400"
          />
        </div>
      </div>

      <DeleteUserModal
        visible={isDeleteModalOpen}
        selectedUser={user}
        onClose={() => setIsDeleteModalOpen(false)}
        onDeleteSuccess={() => {
          setIsDeleteModalOpen(false)
          onDeleteSuccess()
        }}
      />

      <ConfirmationModal
        visible={isDeleteFactorsModalOpen}
        variant="warning"
        title="Confirm to remove MFA factors"
        confirmLabel="Remove factors"
        confirmLabelLoading="Removing"
        onCancel={() => setIsDeleteFactorsModalOpen(false)}
        onConfirm={() => handleDeleteFactors()}
        alert={{
          base: { variant: 'warning' },
          title:
            "Removing MFA factors will drop the user's authentication assurance level (AAL) to AAL1",
          description: 'Note that this does not sign the user out',
        }}
      >
        <p className="text-sm text-foreground-light">
          Are you sure you want to remove the MFA factors for the user{' '}
          <span className="text-foreground">{user.email ?? user.phone ?? 'this user'}</span>?
        </p>
      </ConfirmationModal>

      <BanUserModal visible={isBanModalOpen} user={user} onClose={() => setIsBanModalOpen(false)} />

      <ConfirmationModal
        variant="warning"
        visible={isUnbanModalOpen}
        title="Confirm to unban user"
        loading={isUpdatingUser}
        confirmLabel="Unban user"
        confirmLabelLoading="Unbanning"
        onCancel={() => setIsUnbanModalOpen(false)}
        onConfirm={() => handleUnban()}
      >
        <p className="text-sm text-foreground-light">
          The user will have access to your project again once unbanned. Are you sure you want to
          unban this user?
        </p>
      </ConfirmationModal>
    </>
  )
}

Subdomains

Analyze Your Own Codebase

Get architecture documentation, dependency graphs, and domain analysis for your codebase in minutes.

Try Supermodel Free