import { zodResolver } from '@hookform/resolvers/zod' import { PermissionAction } from '@supabase/shared-types/out/constants' import { useParams } from 'common' import { useEffect } from 'react' import { useForm } from 'react-hook-form' import { toast } from 'sonner' import { Button, Card, CardContent, CardFooter, Form, FormControl, FormField, Switch, Tooltip, TooltipContent, TooltipTrigger, } from 'ui' import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout' import { GenericSkeletonLoader } from 'ui-patterns/ShimmeringLoader' import { z } from 'zod' import { ScaffoldContainer, ScaffoldSection } from '@/components/layouts/Scaffold' import AlertError from '@/components/ui/AlertError' import { InlineLink } from '@/components/ui/InlineLink' import NoPermission from '@/components/ui/NoPermission' import { UpgradeToPro } from '@/components/ui/UpgradeToPro' 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 { useCheckEntitlements } from '@/hooks/misc/useCheckEntitlements' import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions' import { useProfile } from '@/lib/profile' import { useTrack } from '@/lib/telemetry/track' const schema = z.object({ enforceMfa: z.boolean(), }) export const SecuritySettings = () => { const { slug } = useParams() const { profile } = useProfile() const { data: members } = useOrganizationMembersQuery({ slug }) const { can: canReadMfaConfig, isLoading: isLoadingPermissions } = useAsyncCheckPermissions( PermissionAction.READ, 'organizations' ) const { can: canUpdateMfaConfig } = useAsyncCheckPermissions( PermissionAction.UPDATE, 'organizations' ) const track = useTrack() const { hasAccess: hasAccessToEnforceMfa, isLoading: isLoadingEntitlement } = useCheckEntitlements('security.enforce_mfa') const { data: mfaConfig, error: mfaError, isPending: isLoadingMfa, isError: isErrorMfa, isSuccess: isSuccessMfa, } = useOrganizationMfaQuery({ slug }, { enabled: hasAccessToEnforceMfa && canReadMfaConfig }) const { mutate: toggleMfa, isPending: 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') track('organization_mfa_enforcement_updated', { mfaEnforced: data.enforced }) }, }) 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 || !hasAccessToEnforceMfa) return toggleMfa({ slug, setEnforced: values.enforceMfa }) } return ( {!hasAccessToEnforceMfa && !isLoadingEntitlement ? ( ) : ( <> {isLoadingMfa || isLoadingPermissions || isLoadingEntitlement ? ( ) : !canReadMfaConfig ? ( ) : null} {(isErrorMfa || mfaError) && hasAccessToEnforceMfa && ( )} {isSuccessMfa && hasAccessToEnforceMfa && (
( {(!canUpdateMfaConfig || !hasMFAEnabled) && ( {!canUpdateMfaConfig ? ( "You don't have permission to update MFA settings" ) : ( <> Enable MFA{' '} on your own account first )} )} )} /> {form.formState.isDirty && ( )}
)} )}
) }