Skip to content

Commit 4e12a1f

Browse files
saltcodcharislam
andauthored
Chore/autofix views (supabase#33363)
* Autofix security status of views * Update apps/studio/components/interfaces/TableGridEditor/ViewEntityAutofixSecurityModal.tsx Co-authored-by: Charis <[email protected]> --------- Co-authored-by: Charis <[email protected]>
1 parent dd7da21 commit 4e12a1f

File tree

2 files changed

+110
-2
lines changed

2 files changed

+110
-2
lines changed

apps/studio/components/interfaces/TableGridEditor/GridHeaderActions.tsx

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import {
3838
import ConfirmModal from 'ui-patterns/Dialogs/ConfirmDialog'
3939
import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal'
4040
import { RoleImpersonationPopover } from '../RoleImpersonationSelector'
41+
import ViewEntityAutofixSecurityModal from './ViewEntityAutofixSecurityModal'
4142

4243
export interface GridHeaderActionsProps {
4344
table: Entity
@@ -73,7 +74,7 @@ const GridHeaderActions = ({ table }: GridHeaderActionsProps) => {
7374
const [open, setOpen] = useState(false)
7475
const [showEnableRealtime, setShowEnableRealtime] = useState(false)
7576
const [rlsConfirmModalOpen, setRlsConfirmModalOpen] = useState(false)
76-
77+
const [isAutofixViewSecurityModalOpen, setIsAutofixViewSecurityModalOpen] = useState(false)
7778
const state = useTrackedState()
7879
const { selectedRows } = state
7980
const showHeaderActions = selectedRows.size === 0
@@ -299,7 +300,15 @@ const GridHeaderActions = ({ table }: GridHeaderActionsProps) => {
299300
APIs.
300301
</p>
301302

302-
<div className="mt-2">
303+
<div className="mt-2 flex items-center gap-2">
304+
<Button
305+
type="secondary"
306+
onClick={() => {
307+
setIsAutofixViewSecurityModalOpen(true)
308+
}}
309+
>
310+
Autofix
311+
</Button>
303312
<Button type="default" asChild>
304313
<Link
305314
target="_blank"
@@ -424,6 +433,13 @@ const GridHeaderActions = ({ table }: GridHeaderActionsProps) => {
424433
)}
425434
</div>
426435
</ConfirmationModal>
436+
437+
<ViewEntityAutofixSecurityModal
438+
table={table}
439+
isAutofixViewSecurityModalOpen={isAutofixViewSecurityModalOpen}
440+
setIsAutofixViewSecurityModalOpen={setIsAutofixViewSecurityModalOpen}
441+
/>
442+
427443
{isTable && (
428444
<ConfirmModal
429445
danger={table.rls_enabled}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import { useQueryClient } from '@tanstack/react-query'
2+
import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
3+
import { useViewDefinitionQuery } from 'data/database/view-definition-query'
4+
import { useExecuteSqlMutation } from 'data/sql/execute-sql-mutation'
5+
import { Entity } from 'data/table-editor/table-editor-types'
6+
import { toast } from 'sonner'
7+
import { ScrollArea, SimpleCodeBlock } from 'ui'
8+
import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal'
9+
import { lintKeys } from '../../../data/lint/keys'
10+
11+
interface ViewEntityAutofixSecurityModalProps {
12+
table: Entity
13+
isAutofixViewSecurityModalOpen: boolean
14+
setIsAutofixViewSecurityModalOpen: (isAutofixViewSecurityModalOpen: boolean) => void
15+
}
16+
17+
export default function ViewEntityAutofixSecurityModal({
18+
table,
19+
isAutofixViewSecurityModalOpen,
20+
setIsAutofixViewSecurityModalOpen,
21+
}: ViewEntityAutofixSecurityModalProps) {
22+
const { project } = useProjectContext()
23+
const queryClient = useQueryClient()
24+
const viewDefinition = useViewDefinitionQuery({
25+
id: table?.id,
26+
projectRef: project?.ref,
27+
connectionString: project?.connectionString,
28+
})
29+
30+
const { mutate: execute } = useExecuteSqlMutation({
31+
onSuccess: async () => {
32+
toast.success('View security changed successfully')
33+
setIsAutofixViewSecurityModalOpen(false)
34+
await queryClient.invalidateQueries(lintKeys.lint(project?.ref))
35+
},
36+
onError: (error) => {
37+
toast.error(`Failed to autofix view security: ${error.message}`)
38+
},
39+
})
40+
41+
function handleConfirm() {
42+
const sql = `
43+
ALTER VIEW "${table.schema}"."${table.name}" SET (security_invoker = on);
44+
`
45+
execute({
46+
projectRef: project?.ref,
47+
connectionString: project?.connectionString,
48+
sql,
49+
})
50+
}
51+
52+
return (
53+
<ConfirmationModal
54+
visible={isAutofixViewSecurityModalOpen}
55+
size="xlarge"
56+
title="Confirm autofixing view security"
57+
confirmLabel="Confirm"
58+
onCancel={() => setIsAutofixViewSecurityModalOpen(false)}
59+
onConfirm={() => {
60+
handleConfirm()
61+
}}
62+
>
63+
<p className="text-sm text-foreground-light">
64+
Setting <code>security_invoker=on</code> ensures the View runs with the permissions of the
65+
querying user, reducing the risk of unintended data exposure.
66+
</p>
67+
<div className="flex items-center gap-8 mt-8">
68+
<div className=" border rounded-md w-1/2">
69+
<div className="p-4 bg-200 font-mono text-sm font-semibold">Existing query</div>
70+
<ScrollArea className="h-[225px] px-4 py-2">
71+
{viewDefinition.data && (
72+
<SimpleCodeBlock>
73+
{`create view ${table.schema}.${table.name} as\n ${viewDefinition.data}`}
74+
</SimpleCodeBlock>
75+
)}
76+
</ScrollArea>
77+
</div>
78+
79+
<div className=" border rounded-md w-1/2">
80+
<div className="p-4 bg-200 font-mono text-sm font-semibold">Updated query</div>
81+
<ScrollArea className="h-[225px] px-4 py-2">
82+
{viewDefinition.data && (
83+
<SimpleCodeBlock>
84+
{`create view ${table.schema}.${table.name} with (security_invoker = on) as\n ${viewDefinition.data}`}
85+
</SimpleCodeBlock>
86+
)}
87+
</ScrollArea>
88+
</div>
89+
</div>
90+
</ConfirmationModal>
91+
)
92+
}

0 commit comments

Comments
 (0)