import { PermissionAction } from '@supabase/shared-types/out/constants' import { useParams } from 'common' import { template } from 'lodash' import { Download, Loader2 } from 'lucide-react' import { useMemo, useState } from 'react' import { toast } from 'sonner' import { Button, Card, CardContent, Switch, Tooltip, TooltipContent, TooltipTrigger } from 'ui' import { Admonition } from 'ui-patterns/Admonition' import { FormLayout } from 'ui-patterns/form/Layout/FormLayout' import { PageSection, PageSectionContent, PageSectionMeta, PageSectionSummary, PageSectionTitle, } from 'ui-patterns/PageSection' import { SSLEnforcementConfirmDialog } from './SSLEnforcementConfirmDialog' import { SupportLink } from '@/components/interfaces/Support/SupportLink' import { ButtonTooltip } from '@/components/ui/ButtonTooltip' import { DocsButton } from '@/components/ui/DocsButton' import { InlineLinkClassName } from '@/components/ui/InlineLink' import { useProjectSettingsV2Query } from '@/data/config/project-settings-v2-query' import { useJitDbAccessQuery } from '@/data/jit-db-access/jit-db-access-query' import { useSSLEnforcementQuery } from '@/data/ssl-enforcement/ssl-enforcement-query' import { useSSLEnforcementUpdateMutation } from '@/data/ssl-enforcement/ssl-enforcement-update-mutation' import { useCustomContent } from '@/hooks/custom-content/useCustomContent' import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions' import { useHighAvailability } from '@/hooks/misc/useHighAvailability' import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject' import { DOCS_URL } from '@/lib/constants' export const SSLConfiguration = () => { const { ref } = useParams() const { data: project } = useSelectedProjectQuery() const [isConfirmDialogOpen, setIsConfirmDialogOpen] = useState(false) const { data: settings } = useProjectSettingsV2Query({ projectRef: ref }) // High Availability projects always enforce SSL and the API rejects any // attempt to read or change the setting, so skip the query for them and // show the setting as always on. const { isHighAvailability, isPending: isHighAvailabilityPending } = useHighAvailability() const canLoadSSLEnforcement = !isHighAvailability && !isHighAvailabilityPending const { data: sslEnforcementConfiguration, isPending: isSSLEnforcementPending, isSuccess: isSSLEnforcementSuccess, } = useSSLEnforcementQuery({ projectRef: ref }, { enabled: canLoadSSLEnforcement }) const isLoading = isHighAvailabilityPending || (!isHighAvailability && isSSLEnforcementPending) const isSuccess = isHighAvailability || isSSLEnforcementSuccess const { data: jitDbAccessConfiguration } = useJitDbAccessQuery({ projectRef: ref }) const { mutateAsync: updateSSLEnforcement, isPending: isSubmitting } = useSSLEnforcementUpdateMutation({ onSuccess: () => { toast.success('Successfully updated SSL configuration') }, onError: (error) => { toast.error(`Failed to update SSL enforcement: ${error.message}`) }, }) const { can: canUpdateSSLEnforcement } = useAsyncCheckPermissions( PermissionAction.UPDATE, 'projects', { resource: { project_id: project?.id, }, } ) // Derived directly from the query so a refetch triggered elsewhere (e.g. // enabling SSL enforcement from the JIT DB access unavailable banner) is // reflected here too, instead of relying on a mirrored local state that // would only resync on the initial load. const isEnforced = isHighAvailability || (isSSLEnforcementSuccess && sslEnforcementConfiguration.appliedSuccessfully && sslEnforcementConfiguration.currentConfig.database) const hasAccessToSSLEnforcement = !( sslEnforcementConfiguration !== undefined && 'isNotAllowed' in sslEnforcementConfiguration && sslEnforcementConfiguration.isNotAllowed ) // Temporary access requires SSL enforcement to be enabled, so SSL enforcement // can't be turned off again while temporary access is still enabled. const isTemporaryAccessEnabled = jitDbAccessConfiguration?.state === 'enabled' && jitDbAccessConfiguration.appliedSuccessfully const isSwitchDisabled = isLoading || isSubmitting || isHighAvailability || !canUpdateSSLEnforcement || !hasAccessToSSLEnforcement || isTemporaryAccessEnabled let switchTooltipMessage: string | undefined if (isHighAvailability) { switchTooltipMessage = 'SSL is always enforced on High Availability projects' } else if (!canUpdateSSLEnforcement) { switchTooltipMessage = 'You need additional permissions to update SSL enforcement for your project' } else if (!hasAccessToSSLEnforcement) { switchTooltipMessage = 'Your project does not have access to SSL enforcement' } else if (isTemporaryAccessEnabled) { switchTooltipMessage = 'Temporary access must first be disabled before SSL enforcement can be disabled' } let loadingAnnouncement = '' if (isSubmitting) { loadingAnnouncement = 'Updating SSL enforcement' } else if (isLoading) { loadingAnnouncement = 'Loading SSL configuration' } const env = process.env.NEXT_PUBLIC_ENVIRONMENT === 'prod' ? 'prod' : 'staging' const hasSSLCertificate = settings?.inserted_at !== undefined && new Date(settings.inserted_at) >= new Date('2021-04-30') const { sslCertificateUrl: sslCertificateUrlTemplate } = useCustomContent(['ssl:certificate_url']) const sslCertificateUrl = useMemo( () => template(sslCertificateUrlTemplate ?? '')({ env }), [sslCertificateUrlTemplate, env] ) const toggleSSLEnforcement = async () => { if (!ref) return console.error('Project ref is required') await updateSSLEnforcement({ projectRef: ref, requestedConfig: { database: !isEnforced } }) } return ( SSL configuration
{(isLoading || isSubmitting) && (
{isSSLEnforcementSuccess && !sslEnforcementConfiguration.appliedSuccessfully && ( Please try updating again, or contact{' '} support if this error persists } /> )}
{!hasSSLCertificate ? ( } tooltip={{ content: { side: 'bottom', text: 'Projects before 15:08 (GMT+08), 29th April 2021 do not have SSL certificates installed', }, }} > Download certificate ) : ( )}
) }