SSLConfiguration.tsx 8.9 KB

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