| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236 |
- import { PermissionAction } from '@supabase/shared-types/out/constants'
- import { useParams } from 'common'
- import { template } from 'lodash'
- import { Download, Loader2 } from 'lucide-react'
- import { useEffect, useMemo, useState } from 'react'
- import { toast } from 'sonner'
- import {
- AlertDialog,
- AlertDialogAction,
- AlertDialogCancel,
- AlertDialogContent,
- AlertDialogDescription,
- AlertDialogFooter,
- AlertDialogHeader,
- AlertDialogTitle,
- AlertDialogTrigger,
- 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 { 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 { 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 { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
- import { DOCS_URL } from '@/lib/constants'
- export const SSLConfiguration = () => {
- const { ref } = useParams()
- const { data: project } = useSelectedProjectQuery()
- const [isEnforced, setIsEnforced] = useState(false)
- const { data: settings } = useProjectSettingsV2Query({ projectRef: ref })
- const {
- data: sslEnforcementConfiguration,
- isPending: isLoading,
- isSuccess,
- } = useSSLEnforcementQuery({
- projectRef: ref,
- })
- const { mutate: updateSSLEnforcement, isPending: isSubmitting } = useSSLEnforcementUpdateMutation(
- {
- onSuccess: () => {
- toast.success('Successfully updated SSL configuration')
- },
- onError: (error) => {
- setIsEnforced(initialIsEnforced)
- toast.error(`Failed to update SSL enforcement: ${error.message}`)
- },
- }
- )
- const { can: canUpdateSSLEnforcement } = useAsyncCheckPermissions(
- PermissionAction.UPDATE,
- 'projects',
- {
- resource: {
- project_id: project?.id,
- },
- }
- )
- const initialIsEnforced = isSuccess
- ? sslEnforcementConfiguration.appliedSuccessfully &&
- sslEnforcementConfiguration.currentConfig.database
- : false
- const hasAccessToSSLEnforcement = !(
- sslEnforcementConfiguration !== undefined &&
- 'isNotAllowed' in sslEnforcementConfiguration &&
- sslEnforcementConfiguration.isNotAllowed
- )
- 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]
- )
- useEffect(() => {
- if (!isLoading && sslEnforcementConfiguration) {
- setIsEnforced(initialIsEnforced)
- }
- }, [isLoading])
- const toggleSSLEnforcement = async () => {
- if (!ref) return console.error('Project ref is required')
- setIsEnforced(!isEnforced)
- updateSSLEnforcement({ projectRef: ref, requestedConfig: { database: !isEnforced } })
- }
- return (
- <PageSection id="ssl-configuration">
- <PageSectionMeta>
- <PageSectionSummary>
- <PageSectionTitle>SSL configuration</PageSectionTitle>
- </PageSectionSummary>
- <DocsButton href={`${DOCS_URL}/guides/platform/ssl-enforcement`} />
- </PageSectionMeta>
- <PageSectionContent>
- <Card>
- <CardContent className="space-y-4">
- <FormLayout
- layout="flex-row-reverse"
- label="Enforce SSL on incoming connections"
- description="Reject non-SSL connections to your database"
- >
- <AlertDialog>
- <AlertDialogTrigger asChild>
- <div className="flex items-center justify-end mt-2.5 space-x-2">
- {(isLoading || isSubmitting) && (
- <Loader2 className="animate-spin" strokeWidth={1.5} size={16} />
- )}
- {isSuccess && (
- <Tooltip>
- <TooltipTrigger asChild>
- {/* [Joshen] Added div as tooltip is messing with data state property of toggle */}
- <div>
- <Switch
- size="large"
- checked={isEnforced}
- disabled={
- isLoading ||
- isSubmitting ||
- !canUpdateSSLEnforcement ||
- !hasAccessToSSLEnforcement
- }
- />
- </div>
- </TooltipTrigger>
- {(!canUpdateSSLEnforcement || !hasAccessToSSLEnforcement) && (
- <TooltipContent side="bottom" className="w-64 text-center">
- {!canUpdateSSLEnforcement
- ? 'You need additional permissions to update SSL enforcement for your project'
- : !hasAccessToSSLEnforcement
- ? 'Your project does not have access to SSL enforcement'
- : undefined}
- </TooltipContent>
- )}
- </Tooltip>
- )}
- </div>
- </AlertDialogTrigger>
- <AlertDialogContent size="medium">
- <AlertDialogHeader>
- <AlertDialogTitle>
- Updating SSL enforcement involves a brief downtime
- </AlertDialogTitle>
- <AlertDialogDescription>
- A database restart is required for SSL enforcement changes to take place, and
- this involves a few minutes of downtime. Confirm to proceed now?
- </AlertDialogDescription>
- </AlertDialogHeader>
- <AlertDialogFooter>
- <AlertDialogCancel disabled={isSubmitting}>Cancel</AlertDialogCancel>
- <AlertDialogAction
- variant="warning"
- disabled={isSubmitting}
- onClick={toggleSSLEnforcement}
- >
- {!isEnforced ? 'Enable SSL' : 'Disable SSL'}
- </AlertDialogAction>
- </AlertDialogFooter>
- </AlertDialogContent>
- </AlertDialog>
- </FormLayout>
- {isSuccess && !sslEnforcementConfiguration?.appliedSuccessfully && (
- <Admonition
- type="warning"
- layout="horizontal"
- title="SSL enforcement was not updated successfully"
- description={
- <>
- Please try updating again, or contact{' '}
- <SupportLink className={InlineLinkClassName}>support</SupportLink> if this error
- persists
- </>
- }
- />
- )}
- </CardContent>
- <CardContent>
- <FormLayout
- layout="flex-row-reverse"
- label="SSL Certificate"
- description="Use this certificate when connecting to your database to prevent snooping and man-in-the-middle attacks."
- >
- <div className="flex items-end justify-end">
- {!hasSSLCertificate ? (
- <ButtonTooltip
- disabled
- type="default"
- icon={<Download />}
- tooltip={{
- content: {
- side: 'bottom',
- text: 'Projects before 15:08 (GMT+08), 29th April 2021 do not have SSL certificates installed',
- },
- }}
- >
- Download certificate
- </ButtonTooltip>
- ) : (
- <Button type="default" icon={<Download />}>
- <a href={sslCertificateUrl}>Download certificate</a>
- </Button>
- )}
- </div>
- </FormLayout>
- </CardContent>
- </Card>
- </PageSectionContent>
- </PageSection>
- )
- }
|