From d8563cfc6a10a9ca8e05fe41d208e49b4ae289b1 Mon Sep 17 00:00:00 2001 From: Ali Waseem Date: Fri, 21 Aug 2026 08:27:37 -0600 Subject: [PATCH] fix(studio): require full authentication for the support form (#49318) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 ## 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. --- .../interfaces/SignIn/SignInMfaForm.tsx | 28 ++++++++----------- apps/studio/hooks/misc/withAuth.tsx | 18 ++++++------ apps/studio/pages/support/new.tsx | 2 +- 3 files changed, 21 insertions(+), 27 deletions(-) diff --git a/apps/studio/components/interfaces/SignIn/SignInMfaForm.tsx b/apps/studio/components/interfaces/SignIn/SignInMfaForm.tsx index 92bc0850ef8..2b437c0f802 100644 --- a/apps/studio/components/interfaces/SignIn/SignInMfaForm.tsx +++ b/apps/studio/components/interfaces/SignIn/SignInMfaForm.tsx @@ -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 | 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) => { Back to sign in } @@ -129,7 +127,14 @@ export const SignInMfaForm = ({ context = 'sign-in' }: SignInMfaFormProps) => { <> {isLoadingFactors && } - {isErrorFactors && } + {isErrorFactors && ( + + )} {isSuccessFactors && (
@@ -235,17 +240,6 @@ export const SignInMfaForm = ({ context = 'sign-in' }: SignInMfaFormProps) => { Force sign out and clear cookies -
  • - - Reach out to us via support - -
  • diff --git a/apps/studio/hooks/misc/withAuth.tsx b/apps/studio/hooks/misc/withAuth.tsx index b480149fbf9..67d4288e971 100644 --- a/apps/studio/hooks/misc/withAuth.tsx +++ b/apps/studio/hooks/misc/withAuth.tsx @@ -17,10 +17,9 @@ export function withAuth( 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( 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( 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(() => { diff --git a/apps/studio/pages/support/new.tsx b/apps/studio/pages/support/new.tsx index 6dadb2a5061..9a30b6f00f1 100644 --- a/apps/studio/pages/support/new.tsx +++ b/apps/studio/pages/support/new.tsx @@ -14,4 +14,4 @@ SupportPage.getLayout = (page) => ( ) -export default withAuth(SupportPage, { useHighestAAL: false }) +export default withAuth(SupportPage)