From a4e4c1b1bf4f6d5acef60ded37c596cd7a160718 Mon Sep 17 00:00:00 2001 From: Ignacio Dobronich Date: Tue, 3 Mar 2026 14:51:44 -0300 Subject: [PATCH] chore: organization mfa entitlement (#43309) Adds the entitlement check for organization MFA. ### Testing - With an Org on the Free Plan, head to `/org/_/security` - Assert that the upgrade prompt is shown: image - With an Org on the Pro Plan or above, head to `/org/_/security` - Assert that you can enable MFA: image --- .../Organization/SecuritySettings.tsx | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/apps/studio/components/interfaces/Organization/SecuritySettings.tsx b/apps/studio/components/interfaces/Organization/SecuritySettings.tsx index 51af92d27de..2777bfa9a7d 100644 --- a/apps/studio/components/interfaces/Organization/SecuritySettings.tsx +++ b/apps/studio/components/interfaces/Organization/SecuritySettings.tsx @@ -15,8 +15,8 @@ import { useOrganizationMembersQuery } from 'data/organizations/organization-mem import { useOrganizationMfaToggleMutation } from 'data/organizations/organization-mfa-mutation' import { useOrganizationMfaQuery } from 'data/organizations/organization-mfa-query' import { useSendEventMutation } from 'data/telemetry/send-event-mutation' +import { useCheckEntitlements } from 'hooks/misc/useCheckEntitlements' import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' -import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization' import { useProfile } from 'lib/profile' import { Button, @@ -41,7 +41,6 @@ const schema = z.object({ export const SecuritySettings = () => { const { slug } = useParams() const { profile } = useProfile() - const { data: selectedOrganization } = useSelectedOrganizationQuery() const { data: members } = useOrganizationMembersQuery({ slug }) const { can: canReadMfaConfig, isLoading: isLoadingPermissions } = useAsyncCheckPermissions( @@ -54,7 +53,8 @@ export const SecuritySettings = () => { ) const { mutate: sendEvent } = useSendEventMutation() - const isPaidPlan = selectedOrganization?.plan.id !== 'free' + const { hasAccess: hasAccessToEnforceMfa, isLoading: isLoadingEntitlement } = + useCheckEntitlements('security.enforce_mfa') const { data: mfaConfig, @@ -62,7 +62,7 @@ export const SecuritySettings = () => { isPending: isLoadingMfa, isError: isErrorMfa, isSuccess: isSuccessMfa, - } = useOrganizationMfaQuery({ slug }, { enabled: isPaidPlan && canReadMfaConfig }) + } = useOrganizationMfaQuery({ slug }, { enabled: hasAccessToEnforceMfa && canReadMfaConfig }) const { mutate: toggleMfa, isPending: isUpdatingMfa } = useOrganizationMfaToggleMutation({ onError: (error) => { @@ -100,14 +100,14 @@ export const SecuritySettings = () => { members?.find((member) => member.primary_email == profile?.primary_email)?.mfa_enabled || false const onSubmit = (values: { enforceMfa: boolean }) => { - if (!slug || !isPaidPlan) return + if (!slug || !hasAccessToEnforceMfa) return toggleMfa({ slug, setEnforced: values.enforceMfa }) } return ( - {!isPaidPlan ? ( + {!hasAccessToEnforceMfa && !isLoadingEntitlement ? ( { /> ) : ( <> - {isLoadingMfa || isLoadingPermissions ? ( + {isLoadingMfa || isLoadingPermissions || isLoadingEntitlement ? ( @@ -126,11 +126,11 @@ export const SecuritySettings = () => { ) : null} - {(isErrorMfa || mfaError) && isPaidPlan && ( + {(isErrorMfa || mfaError) && hasAccessToEnforceMfa && ( )} - {isSuccessMfa && isPaidPlan && ( + {isSuccessMfa && hasAccessToEnforceMfa && (
@@ -151,7 +151,7 @@ export const SecuritySettings = () => { checked={field.value} onCheckedChange={field.onChange} disabled={ - !isPaidPlan || + !hasAccessToEnforceMfa || !canUpdateMfaConfig || !hasMFAEnabled || isUpdatingMfa @@ -181,7 +181,9 @@ export const SecuritySettings = () => { @@ -190,7 +192,7 @@ export const SecuritySettings = () => { type="primary" htmlType="submit" disabled={ - !isPaidPlan || + !hasAccessToEnforceMfa || !canUpdateMfaConfig || isUpdatingMfa || isLoadingMfa ||