AutoEnableRLSNotice.tsx 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. import { PermissionAction } from '@supabase/shared-types/out/constants'
  2. import { LOCAL_STORAGE_KEYS, useParams } from 'common'
  3. import { ShieldCheck, X } from 'lucide-react'
  4. import { useMemo, useState } from 'react'
  5. import { toast } from 'sonner'
  6. import {
  7. Button,
  8. Card,
  9. CardContent,
  10. Dialog,
  11. DialogContent,
  12. DialogDescription,
  13. DialogFooter,
  14. DialogHeader,
  15. DialogSection,
  16. DialogSectionSeparator,
  17. DialogTitle,
  18. DialogTrigger,
  19. } from 'ui'
  20. import { CodeBlock } from 'ui-patterns/CodeBlock'
  21. import { AUTO_ENABLE_RLS_EVENT_TRIGGER_SQL } from '@/components/interfaces/Database/Triggers/EventTriggersList/EventTriggers.constants'
  22. import { ButtonTooltip } from '@/components/ui/ButtonTooltip'
  23. import { useDatabaseEventTriggerCreateMutation } from '@/data/database-event-triggers/database-event-trigger-create-mutation'
  24. import { useDatabaseEventTriggersQuery } from '@/data/database-event-triggers/database-event-triggers-query'
  25. import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions'
  26. import { useLocalStorageQuery } from '@/hooks/misc/useLocalStorage'
  27. import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
  28. import { useTrack } from '@/lib/telemetry/track'
  29. export const AutoEnableRLSNotice = ({ iconOnly }: { iconOnly?: boolean }) => {
  30. const { ref } = useParams()
  31. const { data: project } = useSelectedProjectQuery()
  32. const projectRef = ref ?? project?.ref
  33. // [Joshen] Changing the behaviour of this to not be dismissible, only minimized
  34. // Given that its a security measure that we highly advise. Otherwise there's no way for users to revisit this
  35. const [, setIsMinimized] = useLocalStorageQuery(
  36. LOCAL_STORAGE_KEYS.RLS_EVENT_TRIGGER_BANNER_DISMISSED(projectRef ?? 'unknown'),
  37. false
  38. )
  39. const { data: eventTriggers = [], isLoading: isLoadingEventTriggers } =
  40. useDatabaseEventTriggersQuery({
  41. projectRef: project?.ref,
  42. connectionString: project?.connectionString,
  43. })
  44. const hasDefaultTrigger = useMemo(
  45. () =>
  46. eventTriggers.some(
  47. (trigger) => trigger.name === 'ensure_rls' || trigger.function_name === 'rls_auto_enable'
  48. ),
  49. [eventTriggers]
  50. )
  51. if (!projectRef || isLoadingEventTriggers || hasDefaultTrigger) return null
  52. if (iconOnly) {
  53. return <CreateEnsureRLSTriggerDialog iconOnly />
  54. }
  55. return (
  56. <Card>
  57. <CardContent className="flex items-center justify-between">
  58. <div className="flex items-center gap-x-4">
  59. <div className="rounded-lg bg-surface-300 text-foreground-light w-10 h-10 flex items-center justify-center">
  60. <ShieldCheck size={18} />
  61. </div>
  62. <div className="text-sm">
  63. <p>Auto-enable RLS for new tables</p>
  64. <p className="text-foreground-lighter">
  65. Create an event trigger that enables Row Level Security on all new tables
  66. </p>
  67. </div>
  68. </div>
  69. <div className="flex items-center gap-x-2">
  70. <CreateEnsureRLSTriggerDialog />
  71. <ButtonTooltip
  72. icon={<X />}
  73. type="text"
  74. className="w-7"
  75. tooltip={{ content: { side: 'bottom', text: 'Minimize' } }}
  76. onClick={() => setIsMinimized(true)}
  77. />
  78. </div>
  79. </CardContent>
  80. </Card>
  81. )
  82. }
  83. const CreateEnsureRLSTriggerDialog = ({ iconOnly }: { iconOnly?: boolean }) => {
  84. const track = useTrack()
  85. const { data: project } = useSelectedProjectQuery()
  86. const [open, setOpen] = useState(false)
  87. const { can: canCreateTriggers } = useAsyncCheckPermissions(
  88. PermissionAction.TENANT_SQL_ADMIN_WRITE,
  89. 'triggers'
  90. )
  91. const { mutate: createEventTrigger, isPending: isCreating } =
  92. useDatabaseEventTriggerCreateMutation({
  93. onSuccess: () => {
  94. toast.success(
  95. 'Successfully set up database trigger to automatically enable RLS on all new tables'
  96. )
  97. setOpen(false)
  98. },
  99. })
  100. const handleCreateTrigger = () => {
  101. if (!project) return
  102. track('rls_event_trigger_banner_create_button_clicked')
  103. createEventTrigger({
  104. projectRef: project.ref,
  105. connectionString: project.connectionString,
  106. sql: AUTO_ENABLE_RLS_EVENT_TRIGGER_SQL,
  107. })
  108. }
  109. return (
  110. <Dialog open={open} onOpenChange={setOpen}>
  111. <DialogTrigger asChild>
  112. {iconOnly ? (
  113. <ButtonTooltip
  114. type="default"
  115. icon={<ShieldCheck />}
  116. className="w-7"
  117. tooltip={{ content: { side: 'bottom', text: 'Auto-enable RLS for new tables' } }}
  118. />
  119. ) : (
  120. <Button type="primary">Learn more</Button>
  121. )}
  122. </DialogTrigger>
  123. <DialogContent size="large">
  124. <DialogHeader>
  125. <DialogTitle>Automatically enable RLS for newly created tables</DialogTitle>
  126. <DialogDescription>Secure your data using Postgres Row Level Security</DialogDescription>
  127. </DialogHeader>
  128. <DialogSectionSeparator />
  129. <DialogSection className="text-sm flex flex-col gap-y-2">
  130. <p>
  131. Tables in exposed schemas (default being the{' '}
  132. <code className="text-code-inline">public</code> schema) are accessible to anyone.
  133. Hence, we highly recommend enabling RLS on all such tables.
  134. </p>
  135. <p>
  136. You can set up a database trigger to enable RLS automatically on all new tables with the
  137. following SQL:
  138. </p>
  139. </DialogSection>
  140. <CodeBlock language="sql" className="language-sql px-0 border-x-0 rounded-none h-64">
  141. {AUTO_ENABLE_RLS_EVENT_TRIGGER_SQL.trim()}
  142. </CodeBlock>
  143. <DialogFooter>
  144. <Button type="default" disabled={isCreating} onClick={() => setOpen(false)}>
  145. Close
  146. </Button>
  147. <ButtonTooltip
  148. disabled={!canCreateTriggers}
  149. loading={isCreating}
  150. onClick={handleCreateTrigger}
  151. tooltip={{
  152. content: {
  153. side: 'bottom',
  154. text: !canCreateTriggers
  155. ? 'You need additional permissions to create triggers'
  156. : undefined,
  157. },
  158. }}
  159. >
  160. Create ensure_rls trigger
  161. </ButtonTooltip>
  162. </DialogFooter>
  163. </DialogContent>
  164. </Dialog>
  165. )
  166. }