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:
Ali Waseem authored and GitHub committed 2026-08-21 08:27:37 -06:00
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>
</>
+9 -9
View File
@@ -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(() => {
+1 -1
View File
@@ -14,4 +14,4 @@ SupportPage.getLayout = (page) => (
</AppLayout>
)
export default withAuth(SupportPage, { useHighestAAL: false })
export default withAuth(SupportPage)