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>
</>
)
}
Domain
Subdomains
Source
Analyze Your Own Codebase
Get architecture documentation, dependency graphs, and domain analysis for your codebase in minutes.
Try Supermodel Free