import { zodResolver } from '@hookform/resolvers/zod' import { PermissionAction } from '@supabase/shared-types/out/constants' import Link from 'next/link' import { useEffect } from 'react' import { useForm } from 'react-hook-form' import { toast } from 'sonner' import { z } from 'zod' import { useParams } from 'common' import { ScaffoldContainerLegacy } from 'components/layouts/Scaffold' import AlertError from 'components/ui/AlertError' import { InlineLink } from 'components/ui/InlineLink' import NoPermission from 'components/ui/NoPermission' import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader' import { useOrganizationMembersQuery } from 'data/organizations/organization-members-query' 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 { useCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization' import { useProfile } from 'lib/profile' import { Alert_Shadcn_, AlertDescription_Shadcn_, AlertTitle_Shadcn_, Button, Card, CardContent, CardFooter, Form_Shadcn_, FormControl_Shadcn_, FormField_Shadcn_, Switch, Tooltip, TooltipContent, TooltipTrigger, WarningIcon, } from 'ui' import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout' const schema = z.object({ enforceMfa: z.boolean(), }) const SecuritySettings = () => { const { slug } = useParams() const { profile } = useProfile() const { data: selectedOrganization } = useSelectedOrganizationQuery() const { data: members } = useOrganizationMembersQuery({ slug }) const canReadMfaConfig = useCheckPermissions(PermissionAction.READ, 'organizations') const canUpdateMfaConfig = useCheckPermissions(PermissionAction.UPDATE, 'organizations') const { mutate: sendEvent } = useSendEventMutation() const isPaidPlan = selectedOrganization?.plan.id !== 'free' const { data: mfaConfig, error: mfaError, isLoading: isLoadingMfa, isError: isErrorMfa, isSuccess: isSuccessMfa, } = useOrganizationMfaQuery({ slug }, { enabled: canReadMfaConfig }) const { mutate: toggleMfa, isLoading: isUpdatingMfa } = useOrganizationMfaToggleMutation({ onError: (error) => { toast.error(`Failed to update MFA enforcement: ${error.message}`) if (mfaConfig !== undefined) form.reset({ enforceMfa: mfaConfig }) }, onSuccess: (data) => { toast.success('Successfully updated organization MFA settings') sendEvent({ action: 'organization_mfa_enforcement_updated', properties: { mfaEnforced: data.enforced, }, groups: { organization: slug ?? 'Unknown', }, }) }, }) const form = useForm>({ resolver: zodResolver(schema), defaultValues: { enforceMfa: false, }, }) useEffect(() => { if (mfaConfig !== undefined) { form.reset({ enforceMfa: mfaConfig }) } }, [mfaConfig, form]) const hasMFAEnabled = members?.find((member) => member.primary_email == profile?.primary_email)?.mfa_enabled || false const onSubmit = (values: { enforceMfa: boolean }) => { if (!slug || !isPaidPlan) return toggleMfa({ slug, setEnforced: values.enforceMfa }) } if (!canReadMfaConfig) { return ( ) } return ( {!isPaidPlan && (
Organization MFA enforcement is not available on Free plan

Upgrade to Pro or above to enforce MFA requirements for your organization.

)} {(isErrorMfa || mfaError) && isPaidPlan && ( )} {isLoadingMfa && ( )} {isSuccessMfa && isPaidPlan && (
(
{(!canUpdateMfaConfig || !hasMFAEnabled) && ( {!canUpdateMfaConfig ? ( "You don't have permission to update MFA settings" ) : ( <> Enable MFA on your own account first )} )}
)} />
{form.formState.isDirty && ( )}
)}
) } export default SecuritySettings