diff --git a/apps/studio/components/interfaces/SignIn/ForgotPasswordForm.tsx b/apps/studio/components/interfaces/SignIn/ForgotPasswordForm.tsx
index cb88f8ccab0..0c5630b9007 100644
--- a/apps/studio/components/interfaces/SignIn/ForgotPasswordForm.tsx
+++ b/apps/studio/components/interfaces/SignIn/ForgotPasswordForm.tsx
@@ -1,5 +1,4 @@
import HCaptcha from '@hcaptcha/react-hcaptcha'
-import * as Sentry from '@sentry/nextjs'
import { useRouter } from 'next/router'
import { useRef, useState } from 'react'
import toast from 'react-hot-toast'
@@ -9,8 +8,6 @@ import { useResetPasswordMutation } from 'data/misc/reset-password-mutation'
import { BASE_PATH } from 'lib/constants'
import { Button, Form, Input } from 'ui'
-const WHITELIST_ERRORS = ['email must be an email']
-
const forgotPasswordSchema = object({
email: string().email('Must be a valid email').required('Email is required'),
})
@@ -31,10 +28,6 @@ const ForgotPasswordForm = () => {
setCaptchaToken(null)
captchaRef.current?.resetCaptcha()
toast.error(`Failed to send reset email: ${error.message}`)
-
- if (!WHITELIST_ERRORS.some((e) => error.message.includes(e))) {
- Sentry.captureMessage('[CRITICAL] Failed to send reset password email: ' + error.message)
- }
},
})
diff --git a/apps/studio/components/interfaces/SignIn/SignInMfaForm.tsx b/apps/studio/components/interfaces/SignIn/SignInMfaForm.tsx
index 837a8204d1f..20c3c60ca06 100644
--- a/apps/studio/components/interfaces/SignIn/SignInMfaForm.tsx
+++ b/apps/studio/components/interfaces/SignIn/SignInMfaForm.tsx
@@ -1,12 +1,10 @@
-import type { AuthError } from '@supabase/auth-js'
import type { Factor } from '@supabase/supabase-js'
import { useQueryClient } from '@tanstack/react-query'
+import { Lock } from 'lucide-react'
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 * as Sentry from '@sentry/nextjs'
import { useTelemetryProps } from 'common'
import AlertError from 'components/ui/AlertError'
@@ -16,7 +14,7 @@ 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'
+import { Button, Form, Input } from 'ui'
const signInSchema = object({
code: string().required('MFA Code is required'),
@@ -36,7 +34,21 @@ const SignInMfaForm = () => {
isSuccess: isSuccessFactors,
isLoading: isLoadingFactors,
} = useMfaListFactorsQuery()
- const { mutate: mfaChallengeAndVerify, isLoading, isSuccess } = useMfaChallengeAndVerifyMutation()
+ const {
+ mutate: mfaChallengeAndVerify,
+ isLoading: isVerifying,
+ isSuccess,
+ } = useMfaChallengeAndVerifyMutation({
+ onSuccess: async () => {
+ Telemetry.sendEvent(
+ { category: 'account', action: 'sign_in', label: '' },
+ telemetryProps,
+ router
+ )
+ await queryClient.resetQueries()
+ router.push(getReturnToPath())
+ },
+ })
const onClickLogout = async () => {
await signOut()
@@ -44,27 +56,8 @@ const SignInMfaForm = () => {
}
const onSignIn = async ({ code }: { code: string }) => {
- const toastId = toast.loading('Signing in...')
if (selectedFactor) {
- 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(`Failed to sign in: ${(error as AuthError).message}`, { id: toastId })
- Sentry.captureMessage('[CRITICAL] Failed to sign in via MFA: ' + error.message)
- },
- }
- )
+ mfaChallengeAndVerify({ factorId: selectedFactor.id, code, refreshFactors: false })
}
}
@@ -102,9 +95,9 @@ const SignInMfaForm = () => {
name="code"
type="text"
autoFocus
- icon={}
+ icon={}
placeholder="XXXXXX"
- disabled={isLoading}
+ disabled={isVerifying}
autoComplete="off"
spellCheck="false"
autoCapitalize="none"
@@ -121,7 +114,7 @@ const SignInMfaForm = () => {
block
type="outline"
size="large"
- disabled={isLoading || isSuccess}
+ disabled={isVerifying || isSuccess}
onClick={onClickLogout}
className="opacity-80 hover:opacity-100 transition"
>
@@ -132,10 +125,10 @@ const SignInMfaForm = () => {
form="sign-in-mfa-form"
htmlType="submit"
size="large"
- disabled={isLoading || isSuccess}
- loading={isLoading || isSuccess}
+ disabled={isVerifying || isSuccess}
+ loading={isVerifying || isSuccess}
>
- {isLoading ? 'Verifying' : isSuccess ? 'Signing in' : 'Verify'}
+ {isVerifying ? 'Verifying' : isSuccess ? 'Signing in' : 'Verify'}
@@ -163,9 +156,9 @@ const SignInMfaForm = () => {
)}
Reach out to us via support
diff --git a/apps/studio/components/interfaces/SignIn/SignUpForm.tsx b/apps/studio/components/interfaces/SignIn/SignUpForm.tsx
index 73c4b27475c..3e593e5dcca 100644
--- a/apps/studio/components/interfaces/SignIn/SignUpForm.tsx
+++ b/apps/studio/components/interfaces/SignIn/SignUpForm.tsx
@@ -1,9 +1,8 @@
import HCaptcha from '@hcaptcha/react-hcaptcha'
-import { CheckCircle } from 'lucide-react'
+import { CheckCircle, Eye, EyeOff } from 'lucide-react'
import { useRef, useState } from 'react'
import toast from 'react-hot-toast'
import * as yup from 'yup'
-import * as Sentry from '@sentry/nextjs'
import { useSignUpMutation } from 'data/misc/signup-mutation'
import { BASE_PATH } from 'lib/constants'
@@ -14,14 +13,10 @@ import {
Alert_Shadcn_,
Button,
Form,
- IconEye,
- IconEyeOff,
Input,
} from 'ui'
import PasswordConditionsHelper from './PasswordConditionsHelper'
-const WHITELIST_ERRORS = ['A user with this email already exists']
-
const signUpSchema = passwordSchema.shape({
email: yup.string().email().required().label('Email'),
})
@@ -42,10 +37,6 @@ const SignUpForm = () => {
setCaptchaToken(null)
captchaRef.current?.resetCaptcha()
toast.error(`Failed to sign up: ${error.message}`)
-
- if (!WHITELIST_ERRORS.includes(error.message)) {
- Sentry.captureMessage('[CRITICAL] Failed to sign up: ' + error.message)
- }
},
})
@@ -118,7 +109,7 @@ const SignUpForm = () => {
onFocus={() => setShowConditions(true)}
actions={
: }
+ icon={passwordHidden ? : }
type="default"
className="!mr-1"
onClick={() => setPasswordHidden((prev) => !prev)}
diff --git a/apps/studio/data/misc/reset-password-mutation.ts b/apps/studio/data/misc/reset-password-mutation.ts
index 17eeba8201b..1e2e21d597b 100644
--- a/apps/studio/data/misc/reset-password-mutation.ts
+++ b/apps/studio/data/misc/reset-password-mutation.ts
@@ -1,9 +1,12 @@
+import * as Sentry from '@sentry/nextjs'
import { useMutation, UseMutationOptions } from '@tanstack/react-query'
import { toast } from 'react-hot-toast'
import { handleError, post } from 'data/fetchers'
import type { ResponseError } from 'types'
+const WHITELIST_ERRORS = ['email must be an email']
+
export type ResetPasswordVariables = {
email: string
hcaptchaToken: string | null
@@ -42,6 +45,9 @@ export const useResetPasswordMutation = ({
} else {
onError(data, variables, context)
}
+ if (!WHITELIST_ERRORS.some((error) => data.message.includes(error))) {
+ Sentry.captureMessage('[CRITICAL] Failed to send reset password email: ' + data.message)
+ }
},
...options,
}
diff --git a/apps/studio/data/misc/signup-mutation.ts b/apps/studio/data/misc/signup-mutation.ts
index c29f2e6eaa8..e00bfbebf15 100644
--- a/apps/studio/data/misc/signup-mutation.ts
+++ b/apps/studio/data/misc/signup-mutation.ts
@@ -1,9 +1,17 @@
+import * as Sentry from '@sentry/nextjs'
import { useMutation, UseMutationOptions } from '@tanstack/react-query'
import { toast } from 'react-hot-toast'
import { handleError, post } from 'data/fetchers'
import type { ResponseError } from 'types'
+const WHITELIST_ERRORS = [
+ 'A user with this email already exists',
+ 'Password should contain at least one character of each',
+ 'You attempted to send email to an inactive recipient',
+ 'email must be an email',
+]
+
export type SignUpVariables = {
email: string
password: string
@@ -38,6 +46,9 @@ export const useSignUpMutation = ({
} else {
onError(data, variables, context)
}
+ if (!WHITELIST_ERRORS.some((error) => data.message.includes(error))) {
+ Sentry.captureMessage('[CRITICAL] Failed to sign up: ' + data.message)
+ }
},
...options,
})
diff --git a/apps/studio/data/profile/mfa-challenge-and-verify-mutation.ts b/apps/studio/data/profile/mfa-challenge-and-verify-mutation.ts
index 29f3d141661..88b90bf0b24 100644
--- a/apps/studio/data/profile/mfa-challenge-and-verify-mutation.ts
+++ b/apps/studio/data/profile/mfa-challenge-and-verify-mutation.ts
@@ -1,8 +1,13 @@
+import * as Sentry from '@sentry/nextjs'
import type { AuthMFAVerifyResponse, MFAChallengeAndVerifyParams } from '@supabase/supabase-js'
-import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query'
+import { UseMutationOptions, useMutation, useQueryClient } from '@tanstack/react-query'
+import toast from 'react-hot-toast'
+
import { auth } from 'lib/gotrue'
import { profileKeys } from './keys'
+const WHITELIST_ERRORS = ['Invalid TOTP code entered']
+
interface MFAChallengeAndVerifyVariables extends MFAChallengeAndVerifyParams {
refreshFactors?: boolean
}
@@ -18,6 +23,7 @@ type CustomMFAVerifyError = NonNullable
export const useMfaChallengeAndVerifyMutation = ({
onSuccess,
+ onError,
...options
}: Omit<
UseMutationOptions,
@@ -42,6 +48,16 @@ export const useMfaChallengeAndVerifyMutation = ({
await onSuccess?.(data, variables, context)
},
+ async onError(data, variables, context) {
+ if (onError === undefined) {
+ toast.error(`Failed to sign in: ${data.message}`)
+ } else {
+ onError(data, variables, context)
+ }
+ if (!WHITELIST_ERRORS.some((error) => data.message.includes(error))) {
+ Sentry.captureMessage('[CRITICAL] Failed to sign in via MFA: ' + data.message)
+ }
+ },
...options,
}
)