mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 09:25:06 +03:00
fix(studio): require full authentication for the support form (#49318)
The support form was exempted from the highest AAL check (since the original MFA rollout in #16813) so that users stuck on the MFA challenge could still file a ticket. The platform API now rejects AAL1 sessions with `403 Insufficient AAL: MFA required`, so for those users the form is simply broken — it renders an error toast and the submit would fail too. This requires AAL2 on `/support/new`, so an AAL1 session gets redirected to the MFA challenge and returns to the form afterwards, and removes the links to the support form from the MFA screen. A dedicated flow for users who can't get past MFA to reach us is being worked on separately and should be out soon! Fixes FE-4218 <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Added an “Email support” action for multi-factor authentication issues, with a prefilled subject line. - Provided clearer guidance when authentication factors cannot be retrieved. - **Bug Fixes** - Improved authentication error handling based on the session’s assurance level. - Reduced confusing permission and error messages for lower-assurance sessions. - Updated support page access to use standard authentication behavior for a more consistent sign-in experience. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
1 parent
eabe06be5b
commit
d8563cfc6a
3 files changed
+21
-27
No files matched your search
@@ -1,5 +1,4 @@
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import { SupportCategories } from '@supabase/shared-types/out/constants'
|
||||
import type { Factor } from '@supabase/supabase-js'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import { useAuthError } from 'common'
|
||||
@@ -13,7 +12,6 @@ import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
|
||||
import { GenericSkeletonLoader } from 'ui-patterns/ShimmeringLoader'
|
||||
import z from 'zod'
|
||||
|
||||
import { SupportLink } from '../Support/SupportLink'
|
||||
import { AlertError } from '@/components/ui/AlertError'
|
||||
import { useMfaChallengeAndVerifyMutation } from '@/data/profile/mfa-challenge-and-verify-mutation'
|
||||
import { useMfaListFactorsQuery } from '@/data/profile/mfa-list-factors-query'
|
||||
@@ -26,6 +24,8 @@ const schema = z.object({
|
||||
|
||||
const formId = 'sign-in-mfa-form'
|
||||
|
||||
const SUPPORT_EMAIL_HREF = `mailto:support@supabase.com?subject=${encodeURIComponent('Unable to sign in via MFA')}`
|
||||
|
||||
function getFactorDisplayName(factor: Pick<Factor, 'friendly_name'> | null | undefined): string {
|
||||
const name = factor?.friendly_name?.trim()
|
||||
return name && name.length > 0 ? name : 'your authenticator app'
|
||||
@@ -115,9 +115,7 @@ export const SignInMfaForm = ({ context = 'sign-in' }: SignInMfaFormProps) => {
|
||||
<Link href="/sign-in">Back to sign in</Link>
|
||||
</Button>
|
||||
<Button asChild variant="default">
|
||||
<Link href="https://supabase.com/support" target="_blank" rel="noreferrer">
|
||||
Contact support
|
||||
</Link>
|
||||
<a href={SUPPORT_EMAIL_HREF}>Email support</a>
|
||||
</Button>
|
||||
</>
|
||||
}
|
||||
@@ -129,7 +127,14 @@ export const SignInMfaForm = ({ context = 'sign-in' }: SignInMfaFormProps) => {
|
||||
<>
|
||||
{isLoadingFactors && <GenericSkeletonLoader />}
|
||||
|
||||
{isErrorFactors && <AlertError error={factorsError} subject="Failed to retrieve factors" />}
|
||||
{isErrorFactors && (
|
||||
<AlertError
|
||||
error={factorsError}
|
||||
subject="Failed to retrieve factors"
|
||||
description="Try refreshing your browser. If the issue persists, email support@supabase.com."
|
||||
hideContactSupport
|
||||
/>
|
||||
)}
|
||||
|
||||
{isSuccessFactors && (
|
||||
<Form {...form}>
|
||||
@@ -235,17 +240,6 @@ export const SignInMfaForm = ({ context = 'sign-in' }: SignInMfaFormProps) => {
|
||||
Force sign out and clear cookies
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<SupportLink
|
||||
className="text-sm transition text-foreground-light hover:text-foreground"
|
||||
queryParams={{
|
||||
subject: 'Unable to sign in via MFA',
|
||||
category: SupportCategories.LOGIN_ISSUES,
|
||||
}}
|
||||
>
|
||||
Reach out to us via support
|
||||
</SupportLink>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</>
|
||||
|
||||
@@ -17,10 +17,9 @@ export function withAuth<T>(
|
||||
options: {
|
||||
/**
|
||||
* The auth level used to check the user credentials. In most cases, if the user has MFA enabled
|
||||
* we want the highest level (which is 2) for all pages. For certain pages, the user should be
|
||||
* able to access them even if he didn't finished his login (typed in his MFA code), for example
|
||||
* the support page: We want the user to be able to submit a ticket even if he's not fully
|
||||
* signed in.
|
||||
* we want the highest level (which is 2) for all pages, as the platform API rejects sessions
|
||||
* that haven't completed the MFA challenge. Only opt out for pages that don't read from the
|
||||
* platform API and are meant to be reachable before the user has finished signing in.
|
||||
* @default true
|
||||
*/
|
||||
useHighestAAL: boolean
|
||||
@@ -43,9 +42,12 @@ export function withAuth<T>(
|
||||
isPending: isAALLoading,
|
||||
data: aalData,
|
||||
isError: isErrorAAL,
|
||||
isSuccess: isSuccessAAL,
|
||||
error: errorAAL,
|
||||
} = useAuthenticatorAssuranceLevelQuery()
|
||||
|
||||
const isAtHighestAAL = isSuccessAAL && aalData.currentLevel === aalData.nextLevel
|
||||
|
||||
useEffect(() => {
|
||||
if (isErrorAAL) {
|
||||
toast.error(
|
||||
@@ -57,19 +59,17 @@ export function withAuth<T>(
|
||||
const { isError: isErrorPermissions, error: errorPermissions } = usePermissionsQuery()
|
||||
|
||||
useEffect(() => {
|
||||
if (isErrorPermissions) {
|
||||
if (isErrorPermissions && isAtHighestAAL) {
|
||||
toast.error(
|
||||
`Failed to fetch permissions: ${errorPermissions?.message}. Try refreshing your browser, or reach out to us via a support ticket if the issue persists`
|
||||
)
|
||||
}
|
||||
}, [isErrorPermissions, errorPermissions])
|
||||
}, [isErrorPermissions, errorPermissions, isAtHighestAAL])
|
||||
|
||||
const isLoggedIn = Boolean(session)
|
||||
const isFinishedLoading = !isLoading && !isAALLoading
|
||||
|
||||
const isCorrectLevel = options.useHighestAAL
|
||||
? aalData?.currentLevel === aalData?.nextLevel
|
||||
: true
|
||||
const isCorrectLevel = options.useHighestAAL ? isAtHighestAAL : true
|
||||
const needsMfaElevation = isLoggedIn && !isCorrectLevel
|
||||
|
||||
const redirectToSignIn = useCallback(() => {
|
||||
|
||||
@@ -14,4 +14,4 @@ SupportPage.getLayout = (page) => (
|
||||
</AppLayout>
|
||||
)
|
||||
|
||||
export default withAuth(SupportPage, { useHighestAAL: false })
|
||||
export default withAuth(SupportPage)
|
||||
Reference in new issue
Block a user