chore: toast auth initialization errors (#37037)
* chore: toast auth initialization errors * Redirect back to sign in if authentication fails * add debug logs * remove debug logs * debug gotrue * remove gotrue debug
This commit is contained in:
@@ -6,7 +6,7 @@ import { AppBannerContextProvider } from 'components/interfaces/App/AppBannerWra
|
||||
export const AuthenticationLayout = ({ children }: PropsWithChildren<{}>) => {
|
||||
return (
|
||||
<AppBannerContextProvider>
|
||||
<div className="flex flex-col h-screen w-screen">
|
||||
<div className="flex flex-col min-h-screen w-screen">
|
||||
<AppBannerWrapper />
|
||||
<div className="flex flex-1 w-full overflow-y-hidden">
|
||||
<div className="flex-grow h-full overflow-y-auto">{children}</div>
|
||||
|
||||
@@ -21,7 +21,7 @@ const ForgotPasswordLayout = ({
|
||||
const { resolvedTheme } = useTheme()
|
||||
|
||||
return (
|
||||
<div className="flex-1 bg-studio flex flex-col gap-8 lg:gap-16 xl:gap-32">
|
||||
<div className="min-h-screen flex-1 bg-studio flex flex-col gap-8 lg:gap-16 xl:gap-32">
|
||||
<div className="sticky top-0 mx-auto w-full max-w-7xl px-8 pt-6 sm:px-6 lg:px-8">
|
||||
<nav className="relative flex items-center justify-between sm:h-10">
|
||||
<div className="flex flex-shrink-0 flex-grow items-center lg:flex-grow-0">
|
||||
|
||||
@@ -82,7 +82,7 @@ const SignInLayout = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="relative flex flex-col bg-alternative h-screen">
|
||||
<div className="relative flex flex-col bg-alternative min-h-screen">
|
||||
<div
|
||||
className={`absolute top-0 w-full px-8 mx-auto sm:px-6 lg:px-8 ${
|
||||
ongoingIncident ? 'mt-14' : 'mt-6'
|
||||
|
||||
@@ -1,27 +1,41 @@
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import { AuthProvider as AuthProviderInternal, clearLocalStorage, gotrueClient } from 'common'
|
||||
import {
|
||||
AuthProvider as AuthProviderInternal,
|
||||
clearLocalStorage,
|
||||
gotrueClient,
|
||||
useAuthError,
|
||||
} from 'common'
|
||||
import { PropsWithChildren, useCallback, useEffect } from 'react'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
import { GOTRUE_ERRORS, IS_PLATFORM } from './constants'
|
||||
|
||||
export const AuthProvider = ({ children }: PropsWithChildren<{}>) => {
|
||||
// Check for unverified GitHub users after a GitHub sign in
|
||||
useEffect(() => {
|
||||
async function handleEmailVerificationError() {
|
||||
const { error } = await gotrueClient.initialize()
|
||||
const AuthErrorToaster = ({ children }: PropsWithChildren) => {
|
||||
const error = useAuthError()
|
||||
|
||||
if (error?.message === GOTRUE_ERRORS.UNVERIFIED_GITHUB_USER) {
|
||||
useEffect(() => {
|
||||
if (error !== null) {
|
||||
// Check for unverified GitHub users after a GitHub sign in
|
||||
if (error.message === GOTRUE_ERRORS.UNVERIFIED_GITHUB_USER) {
|
||||
toast.error(
|
||||
'Please verify your email on GitHub first, then reach out to us at support@supabase.io to log into the dashboard'
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
toast.error(error.message)
|
||||
}
|
||||
}, [error])
|
||||
|
||||
handleEmailVerificationError()
|
||||
}, [])
|
||||
return children
|
||||
}
|
||||
|
||||
return <AuthProviderInternal alwaysLoggedIn={!IS_PLATFORM}>{children}</AuthProviderInternal>
|
||||
export const AuthProvider = ({ children }: PropsWithChildren) => {
|
||||
return (
|
||||
<AuthProviderInternal alwaysLoggedIn={!IS_PLATFORM}>
|
||||
<AuthErrorToaster>{children}</AuthErrorToaster>
|
||||
</AuthProviderInternal>
|
||||
)
|
||||
}
|
||||
|
||||
export { useAuth, useIsLoggedIn, useSession, useUser } from 'common'
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import * as Sentry from '@sentry/nextjs'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useEffect, useState } from 'react'
|
||||
@@ -65,8 +66,8 @@ const SignInMfaPage: NextPageWithLayout = () => {
|
||||
await queryClient.resetQueries()
|
||||
router.push(getReturnToPath())
|
||||
return
|
||||
}
|
||||
if (data.currentLevel !== data.nextLevel) {
|
||||
} else {
|
||||
// Show the MFA form
|
||||
setLoading(false)
|
||||
return
|
||||
}
|
||||
@@ -77,7 +78,13 @@ const SignInMfaPage: NextPageWithLayout = () => {
|
||||
return
|
||||
}
|
||||
})
|
||||
.catch(() => {}) // catch all errors thrown by auth methods
|
||||
.catch((error) => {
|
||||
Sentry.captureException(error)
|
||||
console.error('Auth initialization error:', error)
|
||||
toast.error('Failed to initialize authentication. Please try again.')
|
||||
setLoading(false)
|
||||
router.push({ pathname: '/sign-in', query: router.query })
|
||||
})
|
||||
}, [])
|
||||
|
||||
if (loading) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client'
|
||||
|
||||
import type { Session } from '@supabase/supabase-js'
|
||||
import type { AuthError, Session } from '@supabase/supabase-js'
|
||||
import {
|
||||
createContext,
|
||||
PropsWithChildren,
|
||||
@@ -10,8 +10,8 @@ import {
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react'
|
||||
import { gotrueClient, type User } from './gotrue'
|
||||
import { clearLocalStorage } from './constants/local-storage'
|
||||
import { gotrueClient, type User } from './gotrue'
|
||||
|
||||
export type { User }
|
||||
|
||||
@@ -43,10 +43,12 @@ const DEFAULT_SESSION: any = {
|
||||
type AuthState =
|
||||
| {
|
||||
session: Session | null
|
||||
error: AuthError | null
|
||||
isLoading: false
|
||||
}
|
||||
| {
|
||||
session: null
|
||||
error: AuthError | null
|
||||
isLoading: true
|
||||
}
|
||||
|
||||
@@ -54,6 +56,7 @@ export type AuthContext = { refreshSession: () => Promise<Session | null> } & Au
|
||||
|
||||
export const AuthContext = createContext<AuthContext>({
|
||||
session: null,
|
||||
error: null,
|
||||
isLoading: true,
|
||||
refreshSession: () => Promise.resolve(null),
|
||||
})
|
||||
@@ -66,14 +69,32 @@ export const AuthProvider = ({
|
||||
alwaysLoggedIn,
|
||||
children,
|
||||
}: PropsWithChildren<AuthProviderProps>) => {
|
||||
const [state, setState] = useState<AuthState>({ session: null, isLoading: true })
|
||||
const [state, setState] = useState<AuthState>({ session: null, error: null, isLoading: true })
|
||||
|
||||
useEffect(() => {
|
||||
let mounted = true
|
||||
gotrueClient.initialize().then(({ error }) => {
|
||||
if (mounted && error !== null) {
|
||||
setState((prev) => ({ ...prev, error }))
|
||||
}
|
||||
})
|
||||
|
||||
return () => {
|
||||
mounted = false
|
||||
}
|
||||
}, [])
|
||||
|
||||
// Keep the session in sync
|
||||
useEffect(() => {
|
||||
const {
|
||||
data: { subscription },
|
||||
} = gotrueClient.onAuthStateChange((_event, session) => {
|
||||
setState({ session, isLoading: false })
|
||||
setState((prev) => ({
|
||||
session,
|
||||
// If there is a session, we clear the error
|
||||
error: session !== null ? null : prev.error,
|
||||
isLoading: false,
|
||||
}))
|
||||
})
|
||||
|
||||
return subscription.unsubscribe
|
||||
@@ -91,7 +112,7 @@ export const AuthProvider = ({
|
||||
|
||||
const value = useMemo(() => {
|
||||
if (alwaysLoggedIn) {
|
||||
return { session: DEFAULT_SESSION, isLoading: false, refreshSession } as const
|
||||
return { session: DEFAULT_SESSION, error: null, isLoading: false, refreshSession } as const
|
||||
} else {
|
||||
return { ...state, refreshSession } as const
|
||||
}
|
||||
@@ -116,6 +137,8 @@ export const useIsLoggedIn = () => {
|
||||
return user !== null
|
||||
}
|
||||
|
||||
export const useAuthError = () => useAuth().error
|
||||
|
||||
export const useIsMFAEnabled = () => {
|
||||
const user = useUser()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user