Files
supabase/apps/studio/components/interfaces/SignIn/SignInMfaForm.tsx
Kang Ming 87f7f6ce81 chore: bump supabase-js (#22361)
Revert "Revert "chore: bump supabase-js" (#22325)"

This reverts commit 67e1b1b00b.
2024-04-03 14:23:55 +08:00

183 lines
5.8 KiB
TypeScript

import type { AuthError } from '@supabase/auth-js'
import type { Factor } from '@supabase/supabase-js'
import { useQueryClient } from '@tanstack/react-query'
import Link from 'next/link'
import { useRouter } from 'next/router'
import { useEffect, useState } from 'react'
import toast from 'react-hot-toast'
import { object, string } from 'yup'
import { useTelemetryProps } from 'common'
import AlertError from 'components/ui/AlertError'
import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader'
import { useMfaChallengeAndVerifyMutation } from 'data/profile/mfa-challenge-and-verify-mutation'
import { useMfaListFactorsQuery } from 'data/profile/mfa-list-factors-query'
import { useSignOut } from 'lib/auth'
import { getReturnToPath } from 'lib/gotrue'
import Telemetry from 'lib/telemetry'
import { Button, Form, IconLock, Input } from 'ui'
const signInSchema = object({
code: string().required('MFA Code is required'),
})
const SignInMfaForm = () => {
const queryClient = useQueryClient()
const router = useRouter()
const telemetryProps = useTelemetryProps()
const {
data: factors,
error: factorsError,
isError: isErrorFactors,
isSuccess: isSuccessFactors,
isLoading: isLoadingFactors,
} = useMfaListFactorsQuery()
const {
mutateAsync: mfaChallengeAndVerify,
isLoading,
isSuccess,
} = useMfaChallengeAndVerifyMutation()
const [selectedFactor, setSelectedFactor] = useState<Factor | null>(null)
const signOut = useSignOut()
const onClickLogout = async () => {
await signOut()
await router.replace('/sign-in')
}
useEffect(() => {
if (isSuccessFactors) {
// if the user wanders into this page and he has no MFA setup, send the user to the next screen
if (factors.totp.length === 0) {
queryClient.resetQueries().then(() => router.push(getReturnToPath()))
}
if (factors.totp.length > 0) {
setSelectedFactor(factors.totp[0])
}
}
}, [factors?.totp, isSuccessFactors, router, queryClient])
const onSignIn = async ({ code }: { code: string }) => {
const toastId = toast.loading('Signing in...')
if (selectedFactor) {
await mfaChallengeAndVerify(
{ factorId: selectedFactor.id, code, refreshFactors: false },
{
onSuccess: async () => {
toast.success('Signed in successfully!', { id: toastId })
Telemetry.sendEvent(
{ category: 'account', action: 'sign_in', label: '' },
telemetryProps,
router
)
await queryClient.resetQueries()
router.push(getReturnToPath())
},
onError: (error) => {
toast.error((error as AuthError).message, { id: toastId })
},
}
)
}
}
return (
<>
{isLoadingFactors && <GenericSkeletonLoader />}
{isErrorFactors && <AlertError error={factorsError} subject="Failed to retrieve factors" />}
{isSuccessFactors && (
<Form
validateOnBlur
id="sign-in-mfa-form"
initialValues={{ code: '' }}
validationSchema={signInSchema}
onSubmit={onSignIn}
>
{() => (
<>
<div className="flex flex-col gap-4">
<Input
id="code"
name="code"
type="text"
autoFocus
icon={<IconLock />}
placeholder="XXXXXX"
disabled={isLoading}
autoComplete="off"
spellCheck="false"
autoCapitalize="none"
autoCorrect="off"
label={
selectedFactor && factors?.totp.length === 2
? `Code generated by ${selectedFactor.friendly_name}`
: null
}
/>
<div className="flex items-center justify-between space-x-2">
<Button
block
type="outline"
size="large"
disabled={isLoading || isSuccess}
onClick={onClickLogout}
className="opacity-80 hover:opacity-100 transition"
>
Cancel
</Button>
<Button
block
form="sign-in-mfa-form"
htmlType="submit"
size="large"
disabled={isLoading || isSuccess}
loading={isLoading || isSuccess}
>
{isLoading ? 'Verifying' : isSuccess ? 'Signing in' : 'Verify'}
</Button>
</div>
</div>
</>
)}
</Form>
)}
<div className="my-8">
<div className="text-sm">
<span className="text-foreground-light">Unable to sign in?</span>{' '}
</div>
<ul className="list-disc pl-6">
{factors?.totp.length === 2 && (
<li>
<a
className="text-sm text-foreground-light hover:text-foreground cursor-pointer"
onClick={() =>
setSelectedFactor(factors.totp.find((f) => f.id !== selectedFactor?.id)!)
}
>{`Authenticate using ${
factors.totp.find((f) => f.id !== selectedFactor?.id)?.friendly_name
}?`}</a>
</li>
)}
<li>
<Link
href="/support/new?subject=Unable+to+sign+in+via+MFA&category=Login_issues"
target="_blank"
rel="noreferrer"
className="text-sm transition text-foreground-light hover:text-foreground"
>
Reach out to us via support
</Link>
</li>
</ul>
</div>
</>
)
}
export default SignInMfaForm