ViewEntityAutofixSecurityModal.tsx107 lines · main
1import { ident, safeSql } from '@supabase/pg-meta/src/pg-format'
2import { useQueryClient } from '@tanstack/react-query'
3import { toast } from 'sonner'
4import { ScrollArea } from 'ui'
5import { ConfirmationModal } from 'ui-patterns/Dialogs/ConfirmationModal'
6import { GenericSkeletonLoader } from 'ui-patterns/ShimmeringLoader'
7import { SimpleCodeBlock } from 'ui-patterns/SimpleCodeBlock'
8
9import { useViewDefinitionQuery } from '@/data/database/view-definition-query'
10import { lintKeys } from '@/data/lint/keys'
11import { useExecuteSqlMutation } from '@/data/sql/execute-sql-mutation'
12import { Entity, isViewLike } from '@/data/table-editor/table-editor-types'
13import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
14
15interface ViewEntityAutofixSecurityModalProps {
16 table: Entity
17 isAutofixViewSecurityModalOpen: boolean
18 setIsAutofixViewSecurityModalOpen: (isAutofixViewSecurityModalOpen: boolean) => void
19}
20
21export const ViewEntityAutofixSecurityModal = ({
22 table,
23 isAutofixViewSecurityModalOpen,
24 setIsAutofixViewSecurityModalOpen,
25}: ViewEntityAutofixSecurityModalProps) => {
26 const { data: project } = useSelectedProjectQuery()
27 const queryClient = useQueryClient()
28 const {
29 isSuccess,
30 isPending: isLoading,
31 data,
32 } = useViewDefinitionQuery(
33 {
34 id: table?.id,
35 projectRef: project?.ref,
36 connectionString: project?.connectionString,
37 },
38 {
39 enabled: isAutofixViewSecurityModalOpen && isViewLike(table),
40 }
41 )
42
43 const { mutate: execute } = useExecuteSqlMutation({
44 onSuccess: async () => {
45 toast.success('View security changed successfully')
46 setIsAutofixViewSecurityModalOpen(false)
47 await queryClient.invalidateQueries({ queryKey: lintKeys.lint(project?.ref) })
48 },
49 onError: (error) => {
50 toast.error(`Failed to autofix view security: ${error.message}`)
51 },
52 })
53
54 function handleConfirm() {
55 const sql = safeSql`ALTER VIEW ${ident(table.schema)}.${ident(table.name)} SET (security_invoker = on);`
56 execute({
57 projectRef: project?.ref,
58 connectionString: project?.connectionString,
59 sql,
60 })
61 }
62
63 if (!isViewLike(table)) {
64 return null
65 }
66
67 return (
68 <ConfirmationModal
69 visible={isAutofixViewSecurityModalOpen}
70 size="xlarge"
71 title="Confirm autofixing view security"
72 confirmLabel="Confirm"
73 onCancel={() => setIsAutofixViewSecurityModalOpen(false)}
74 onConfirm={() => handleConfirm()}
75 >
76 <p className="text-sm text-foreground-light">
77 Setting <code>security_invoker=on</code> ensures the View runs with the permissions of the
78 querying user, reducing the risk of unintended data exposure.
79 </p>
80 <div className="flex items-center gap-8 mt-8">
81 <div className=" border rounded-md w-1/2">
82 <div className="p-4 pb-0 bg-200 font-mono text-sm font-semibold">Existing query</div>
83 <ScrollArea className="h-[225px] px-4 py-2">
84 {isLoading && <GenericSkeletonLoader />}
85 {isSuccess && (
86 <SimpleCodeBlock>
87 {`create view ${table.schema}.${table.name} as\n ${data}`}
88 </SimpleCodeBlock>
89 )}
90 </ScrollArea>
91 </div>
92
93 <div className=" border rounded-md w-1/2">
94 <div className="p-4 pb-0 bg-200 font-mono text-sm font-semibold">Updated query</div>
95 <ScrollArea className="h-[225px] px-4 py-2">
96 {isLoading && <GenericSkeletonLoader />}
97 {isSuccess && (
98 <SimpleCodeBlock>
99 {`create view ${table.schema}.${table.name} with (security_invoker = on) as\n ${data}`}
100 </SimpleCodeBlock>
101 )}
102 </ScrollArea>
103 </div>
104 </div>
105 </ConfirmationModal>
106 )
107}