SSLConfiguration.tsx236 lines · main
1import { PermissionAction } from '@supabase/shared-types/out/constants'
2import { useParams } from 'common'
3import { template } from 'lodash'
4import { Download, Loader2 } from 'lucide-react'
5import { useEffect, useMemo, useState } from 'react'
6import { toast } from 'sonner'
7import {
8 AlertDialog,
9 AlertDialogAction,
10 AlertDialogCancel,
11 AlertDialogContent,
12 AlertDialogDescription,
13 AlertDialogFooter,
14 AlertDialogHeader,
15 AlertDialogTitle,
16 AlertDialogTrigger,
17 Button,
18 Card,
19 CardContent,
20 Switch,
21 Tooltip,
22 TooltipContent,
23 TooltipTrigger,
24} from 'ui'
25import { Admonition } from 'ui-patterns/admonition'
26import { FormLayout } from 'ui-patterns/form/Layout/FormLayout'
27import {
28 PageSection,
29 PageSectionContent,
30 PageSectionMeta,
31 PageSectionSummary,
32 PageSectionTitle,
33} from 'ui-patterns/PageSection'
34
35import { SupportLink } from '@/components/interfaces/Support/SupportLink'
36import { ButtonTooltip } from '@/components/ui/ButtonTooltip'
37import { DocsButton } from '@/components/ui/DocsButton'
38import { InlineLinkClassName } from '@/components/ui/InlineLink'
39import { useProjectSettingsV2Query } from '@/data/config/project-settings-v2-query'
40import { useSSLEnforcementQuery } from '@/data/ssl-enforcement/ssl-enforcement-query'
41import { useSSLEnforcementUpdateMutation } from '@/data/ssl-enforcement/ssl-enforcement-update-mutation'
42import { useCustomContent } from '@/hooks/custom-content/useCustomContent'
43import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions'
44import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
45import { DOCS_URL } from '@/lib/constants'
46
47export const SSLConfiguration = () => {
48 const { ref } = useParams()
49 const { data: project } = useSelectedProjectQuery()
50 const [isEnforced, setIsEnforced] = useState(false)
51
52 const { data: settings } = useProjectSettingsV2Query({ projectRef: ref })
53 const {
54 data: sslEnforcementConfiguration,
55 isPending: isLoading,
56 isSuccess,
57 } = useSSLEnforcementQuery({
58 projectRef: ref,
59 })
60 const { mutate: updateSSLEnforcement, isPending: isSubmitting } = useSSLEnforcementUpdateMutation(
61 {
62 onSuccess: () => {
63 toast.success('Successfully updated SSL configuration')
64 },
65 onError: (error) => {
66 setIsEnforced(initialIsEnforced)
67 toast.error(`Failed to update SSL enforcement: ${error.message}`)
68 },
69 }
70 )
71
72 const { can: canUpdateSSLEnforcement } = useAsyncCheckPermissions(
73 PermissionAction.UPDATE,
74 'projects',
75 {
76 resource: {
77 project_id: project?.id,
78 },
79 }
80 )
81 const initialIsEnforced = isSuccess
82 ? sslEnforcementConfiguration.appliedSuccessfully &&
83 sslEnforcementConfiguration.currentConfig.database
84 : false
85
86 const hasAccessToSSLEnforcement = !(
87 sslEnforcementConfiguration !== undefined &&
88 'isNotAllowed' in sslEnforcementConfiguration &&
89 sslEnforcementConfiguration.isNotAllowed
90 )
91 const env = process.env.NEXT_PUBLIC_ENVIRONMENT === 'prod' ? 'prod' : 'staging'
92 const hasSSLCertificate =
93 settings?.inserted_at !== undefined && new Date(settings.inserted_at) >= new Date('2021-04-30')
94
95 const { sslCertificateUrl: sslCertificateUrlTemplate } = useCustomContent(['ssl:certificate_url'])
96 const sslCertificateUrl = useMemo(
97 () => template(sslCertificateUrlTemplate ?? '')({ env }),
98 [sslCertificateUrlTemplate, env]
99 )
100
101 useEffect(() => {
102 if (!isLoading && sslEnforcementConfiguration) {
103 setIsEnforced(initialIsEnforced)
104 }
105 }, [isLoading])
106
107 const toggleSSLEnforcement = async () => {
108 if (!ref) return console.error('Project ref is required')
109 setIsEnforced(!isEnforced)
110 updateSSLEnforcement({ projectRef: ref, requestedConfig: { database: !isEnforced } })
111 }
112
113 return (
114 <PageSection id="ssl-configuration">
115 <PageSectionMeta>
116 <PageSectionSummary>
117 <PageSectionTitle>SSL configuration</PageSectionTitle>
118 </PageSectionSummary>
119 <DocsButton href={`${DOCS_URL}/guides/platform/ssl-enforcement`} />
120 </PageSectionMeta>
121 <PageSectionContent>
122 <Card>
123 <CardContent className="space-y-4">
124 <FormLayout
125 layout="flex-row-reverse"
126 label="Enforce SSL on incoming connections"
127 description="Reject non-SSL connections to your database"
128 >
129 <AlertDialog>
130 <AlertDialogTrigger asChild>
131 <div className="flex items-center justify-end mt-2.5 space-x-2">
132 {(isLoading || isSubmitting) && (
133 <Loader2 className="animate-spin" strokeWidth={1.5} size={16} />
134 )}
135 {isSuccess && (
136 <Tooltip>
137 <TooltipTrigger asChild>
138 {/* [Joshen] Added div as tooltip is messing with data state property of toggle */}
139 <div>
140 <Switch
141 size="large"
142 checked={isEnforced}
143 disabled={
144 isLoading ||
145 isSubmitting ||
146 !canUpdateSSLEnforcement ||
147 !hasAccessToSSLEnforcement
148 }
149 />
150 </div>
151 </TooltipTrigger>
152 {(!canUpdateSSLEnforcement || !hasAccessToSSLEnforcement) && (
153 <TooltipContent side="bottom" className="w-64 text-center">
154 {!canUpdateSSLEnforcement
155 ? 'You need additional permissions to update SSL enforcement for your project'
156 : !hasAccessToSSLEnforcement
157 ? 'Your project does not have access to SSL enforcement'
158 : undefined}
159 </TooltipContent>
160 )}
161 </Tooltip>
162 )}
163 </div>
164 </AlertDialogTrigger>
165 <AlertDialogContent size="medium">
166 <AlertDialogHeader>
167 <AlertDialogTitle>
168 Updating SSL enforcement involves a brief downtime
169 </AlertDialogTitle>
170 <AlertDialogDescription>
171 A database restart is required for SSL enforcement changes to take place, and
172 this involves a few minutes of downtime. Confirm to proceed now?
173 </AlertDialogDescription>
174 </AlertDialogHeader>
175 <AlertDialogFooter>
176 <AlertDialogCancel disabled={isSubmitting}>Cancel</AlertDialogCancel>
177 <AlertDialogAction
178 variant="warning"
179 disabled={isSubmitting}
180 onClick={toggleSSLEnforcement}
181 >
182 {!isEnforced ? 'Enable SSL' : 'Disable SSL'}
183 </AlertDialogAction>
184 </AlertDialogFooter>
185 </AlertDialogContent>
186 </AlertDialog>
187 </FormLayout>
188 {isSuccess && !sslEnforcementConfiguration?.appliedSuccessfully && (
189 <Admonition
190 type="warning"
191 layout="horizontal"
192 title="SSL enforcement was not updated successfully"
193 description={
194 <>
195 Please try updating again, or contact{' '}
196 <SupportLink className={InlineLinkClassName}>support</SupportLink> if this error
197 persists
198 </>
199 }
200 />
201 )}
202 </CardContent>
203 <CardContent>
204 <FormLayout
205 layout="flex-row-reverse"
206 label="SSL Certificate"
207 description="Use this certificate when connecting to your database to prevent snooping and man-in-the-middle attacks."
208 >
209 <div className="flex items-end justify-end">
210 {!hasSSLCertificate ? (
211 <ButtonTooltip
212 disabled
213 type="default"
214 icon={<Download />}
215 tooltip={{
216 content: {
217 side: 'bottom',
218 text: 'Projects before 15:08 (GMT+08), 29th April 2021 do not have SSL certificates installed',
219 },
220 }}
221 >
222 Download certificate
223 </ButtonTooltip>
224 ) : (
225 <Button type="default" icon={<Download />}>
226 <a href={sslCertificateUrl}>Download certificate</a>
227 </Button>
228 )}
229 </div>
230 </FormLayout>
231 </CardContent>
232 </Card>
233 </PageSectionContent>
234 </PageSection>
235 )
236}