mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 17:35:10 +03:00
Adds a pre-execution warning in the SQL editor when a `CREATE TABLE` statement is run without enabling Row Level Security on the new table. Responds to the press call-out around SQL editor security. <img width="708" height="498" alt="Screenshot 2026-04-18 at 4 31 07 PM" src="https://github.com/user-attachments/assets/4f23ed5e-f32c-46f0-b0da-ac6d4c661c7c" /> **Added:** - Pre-execution check in `executeQuery` that detects `CREATE TABLE` statements without a matching `ALTER TABLE ... ENABLE ROW LEVEL SECURITY` in the same submitted SQL. - New "Run and enable RLS" action in the warning modal that rewrites the SQL to append `ALTER TABLE [schema.]<table> ENABLE ROW LEVEL SECURITY;` for each detected table before running. - Link in the modal to the RLS docs. **Changed:** - `RunQueryWarningModal` now renders `Dialog` directly (instead of `ConfirmationModal`) so it can show three buttons: Cancel / Run without RLS / Run and enable RLS. - `sqlEventParser` table-name regex now supports quoted identifiers containing spaces (e.g. `"My Table"`) and escaped quotes (e.g. `"user""table"`). The check runs against the SQL that's actually submitted, so partial-selection works correctly — selecting only the `CREATE TABLE` portion will trigger the warning even if there's a matching `ENABLE RLS` lower in the editor. ## To test - Open the SQL editor and run `create table foo (id int8 primary key);` → modal should appear with the RLS warning bullet and three buttons. - Click **Run and enable RLS** → query runs, table is created with RLS enabled. - Click **Run without RLS** → query runs as written, no RLS. - Run `create table foo (id int8); alter table foo enable row level security;` → no modal (RLS already enabled in same submission). - Run `create table public.bar (id int8); create table baz (id int8); alter table baz enable rls;` → modal flags only `public.bar`. - Select only the `create table` portion of a snippet that also enables RLS lower down and run the selection → modal should still fire. - Run an existing destructive query (`drop table x`) → modal still works as before with two buttons (Cancel / Run this query). <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * SQL editor now detects CREATE TABLE statements missing Row Level Security (RLS) and shows counts and dynamic table/schema details in a redesigned warning dialog with updated pluralization and a “Learn more” link. * New actions: “Run without RLS” and, when available, “Run and enable RLS” which applies RLS and runs the query; editor can execute an overridden SQL payload when applying RLS changes. * **Tests** * Added comprehensive unit and e2e tests covering RLS detection, SQL augmentation, trigger handling, identifier parsing, and the “Run and enable RLS” flow. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Alaister Young <10985857+alaister@users.noreply.github.com>
162 lines
5.8 KiB
TypeScript
162 lines
5.8 KiB
TypeScript
import {
|
|
Button,
|
|
cn,
|
|
Dialog,
|
|
DialogContent,
|
|
DialogDescription,
|
|
DialogHeader,
|
|
DialogSection,
|
|
DialogSectionSeparator,
|
|
DialogTitle,
|
|
} from 'ui'
|
|
import { Admonition } from 'ui-patterns'
|
|
|
|
import { PotentialIssues } from './SQLEditor.types'
|
|
import { DOCS_URL } from '@/lib/constants'
|
|
|
|
interface RunQueryWarningModalProps {
|
|
visible: boolean
|
|
potentialIssues: PotentialIssues | undefined
|
|
onCancel: () => void
|
|
onConfirm: () => void
|
|
onConfirmWithRLS?: () => void
|
|
}
|
|
|
|
export const RunQueryWarningModal = ({
|
|
visible,
|
|
potentialIssues,
|
|
onCancel,
|
|
onConfirm,
|
|
onConfirmWithRLS,
|
|
}: RunQueryWarningModalProps) => {
|
|
const {
|
|
hasDestructiveOperations,
|
|
hasUpdateWithoutWhere,
|
|
hasAlterDatabasePreventConnection,
|
|
createTablesMissingRLS,
|
|
} = potentialIssues || {}
|
|
|
|
const missingRLSTables = createTablesMissingRLS ?? []
|
|
const hasMissingRLS = missingRLSTables.length > 0
|
|
const issueCount =
|
|
(hasDestructiveOperations ? 1 : 0) +
|
|
(hasUpdateWithoutWhere ? 1 : 0) +
|
|
(hasAlterDatabasePreventConnection ? 1 : 0) +
|
|
(hasMissingRLS ? 1 : 0)
|
|
|
|
return (
|
|
<Dialog
|
|
open={visible}
|
|
onOpenChange={(open) => {
|
|
if (!open) onCancel()
|
|
}}
|
|
>
|
|
<DialogContent aria-describedby={undefined} className="p-0 gap-0 pb-5 !block" size="large">
|
|
<DialogHeader className={cn('border-b')} padding="small">
|
|
<DialogTitle>
|
|
{`Potential issue${issueCount > 1 ? 's' : ''} detected with your query`}
|
|
</DialogTitle>
|
|
<DialogDescription className="sr-only">
|
|
Review the warnings below before running this query.
|
|
</DialogDescription>
|
|
</DialogHeader>
|
|
|
|
<Admonition
|
|
type="warning"
|
|
label={
|
|
issueCount > 1
|
|
? 'The following potential issues have been detected:'
|
|
: 'The following potential issue has been detected:'
|
|
}
|
|
description="Ensure that these are intentional before executing this query"
|
|
className="border-x-0 rounded-none -mt-px"
|
|
/>
|
|
|
|
<DialogSection padding="small">
|
|
<div className="text-sm">
|
|
<ul className="border rounded-md grid bg-surface-200 divide-y">
|
|
{hasDestructiveOperations && (
|
|
<li className="grid pt-3 pb-2 px-4">
|
|
<span className="font-bold">Query has destructive operations</span>
|
|
<span className="text-foreground-light">
|
|
Make sure you are not accidentally removing something important.
|
|
</span>
|
|
</li>
|
|
)}
|
|
{hasUpdateWithoutWhere && (
|
|
<li className="grid pt-2 pb-3 px-4 gap-1">
|
|
<span className="font-bold">Query uses update without a where clause</span>
|
|
<span className="text-foreground-light">
|
|
Without a <code className="text-code-inline">where</code> clause, this could
|
|
update all rows in the table.
|
|
</span>
|
|
</li>
|
|
)}
|
|
{hasAlterDatabasePreventConnection && (
|
|
<li className="grid pt-2 pb-3 px-4 gap-1">
|
|
<span className="font-bold">Query will prevent connections to your database</span>
|
|
<span className="text-foreground-light">
|
|
The dashboard will no longer have access to your database, and you will need a
|
|
direct connection to your database to reconfigure this setting
|
|
</span>
|
|
</li>
|
|
)}
|
|
{hasMissingRLS && (
|
|
<li className="grid pt-2 pb-3 px-4 gap-1">
|
|
<span className="font-bold">
|
|
{missingRLSTables.length === 1
|
|
? 'New table will not have Row Level Security enabled'
|
|
: 'New tables will not have Row Level Security enabled'}
|
|
</span>
|
|
<span className="text-foreground-light">
|
|
Without RLS, any client using your project's anon or authenticated keys can read
|
|
and write to{' '}
|
|
{missingRLSTables.length === 1 ? (
|
|
<code className="text-code-inline">
|
|
{missingRLSTables[0].schema
|
|
? `${missingRLSTables[0].schema}.${missingRLSTables[0].tableName}`
|
|
: missingRLSTables[0].tableName}
|
|
</code>
|
|
) : (
|
|
'these tables'
|
|
)}
|
|
. Enable RLS and add policies before exposing this table via the API.{' '}
|
|
<a
|
|
href={`${DOCS_URL}/guides/database/postgres/row-level-security`}
|
|
target="_blank"
|
|
rel="noreferrer"
|
|
className="underline"
|
|
>
|
|
Learn more
|
|
</a>
|
|
.
|
|
</span>
|
|
</li>
|
|
)}
|
|
</ul>
|
|
</div>
|
|
<p className="mt-4 text-sm text-foreground-light">
|
|
Please confirm that you would like to execute this query.
|
|
</p>
|
|
</DialogSection>
|
|
|
|
<DialogSectionSeparator />
|
|
|
|
<div className="flex flex-wrap gap-2 px-5 pt-5">
|
|
<Button size="medium" type="default" onClick={() => onCancel()}>
|
|
Cancel
|
|
</Button>
|
|
<Button size="medium" type="warning" onClick={onConfirm} className="ml-auto">
|
|
{hasMissingRLS ? 'Run without RLS' : 'Run this query'}
|
|
</Button>
|
|
{hasMissingRLS && onConfirmWithRLS && (
|
|
<Button size="medium" type="primary" onClick={onConfirmWithRLS}>
|
|
Run and enable RLS
|
|
</Button>
|
|
)}
|
|
</div>
|
|
</DialogContent>
|
|
</Dialog>
|
|
)
|
|
}
|