mirror of
https://github.com/supabase/supabase.git
synced 2026-10-10 20:05:06 +03:00
## Context Continuing off from this PR: https://github.com/supabase/supabase/pull/45407 Shifting the "Auto enable RLS" toast into a banner that's dismissible, only can be minimised until action is taken Given that this is a security measure that we highly advise - otherwise there's no way for users to revisit this after dismissing We'll use the existing local storage key so it doesn't affect the behaviour for users who already dismissed the banner Starting state: (Not dismissed / not minimised) <img width="1450" height="424" alt="1" src="https://github.com/user-attachments/assets/de419812-69f5-4370-a553-88a83f27a6f7" /> If dismissed already / minimised: Shift CTA into a button tooltip <img width="1449" height="402" alt="2" src="https://github.com/user-attachments/assets/c404f39d-021f-43ba-b855-f83aea858162" /> Button will disappear entirely once the trigger is created Also added this to the database tables page <img width="1913" height="534" alt="image" src="https://github.com/user-attachments/assets/a0c65986-6e23-4795-8720-a33ec6eec3c1" /> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Redesigned the Row Level Security (RLS) notice with a new card-based layout and minimize button to reduce visual clutter while keeping the feature accessible. * Added a compact icon-only mode for the RLS notice in page headers, with tooltip descriptions for clarity. * RLS notice minimization state is now persisted locally per project, improving user experience across page navigation. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
185 lines
6.2 KiB
TypeScript
185 lines
6.2 KiB
TypeScript
import { PermissionAction } from '@supabase/shared-types/out/constants'
|
|
import { LOCAL_STORAGE_KEYS, useParams } from 'common'
|
|
import { ShieldCheck, X } from 'lucide-react'
|
|
import { useMemo, useState } from 'react'
|
|
import { toast } from 'sonner'
|
|
import {
|
|
Button,
|
|
Card,
|
|
CardContent,
|
|
Dialog,
|
|
DialogContent,
|
|
DialogDescription,
|
|
DialogFooter,
|
|
DialogHeader,
|
|
DialogSection,
|
|
DialogSectionSeparator,
|
|
DialogTitle,
|
|
DialogTrigger,
|
|
} from 'ui'
|
|
import { CodeBlock } from 'ui-patterns/CodeBlock'
|
|
|
|
import { AUTO_ENABLE_RLS_EVENT_TRIGGER_SQL } from '@/components/interfaces/Database/Triggers/EventTriggersList/EventTriggers.constants'
|
|
import { ButtonTooltip } from '@/components/ui/ButtonTooltip'
|
|
import { useDatabaseEventTriggerCreateMutation } from '@/data/database-event-triggers/database-event-trigger-create-mutation'
|
|
import { useDatabaseEventTriggersQuery } from '@/data/database-event-triggers/database-event-triggers-query'
|
|
import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions'
|
|
import { useLocalStorageQuery } from '@/hooks/misc/useLocalStorage'
|
|
import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
|
|
import { useTrack } from '@/lib/telemetry/track'
|
|
|
|
export const AutoEnableRLSNotice = ({ iconOnly }: { iconOnly?: boolean }) => {
|
|
const { ref } = useParams()
|
|
const { data: project } = useSelectedProjectQuery()
|
|
const projectRef = ref ?? project?.ref
|
|
|
|
// [Joshen] Changing the behaviour of this to not be dismissible, only minimized
|
|
// Given that its a security measure that we highly advise. Otherwise there's no way for users to revisit this
|
|
const [, setIsMinimized] = useLocalStorageQuery(
|
|
LOCAL_STORAGE_KEYS.RLS_EVENT_TRIGGER_BANNER_DISMISSED(projectRef ?? 'unknown'),
|
|
false
|
|
)
|
|
|
|
const { data: eventTriggers = [], isLoading: isLoadingEventTriggers } =
|
|
useDatabaseEventTriggersQuery({
|
|
projectRef: project?.ref,
|
|
connectionString: project?.connectionString,
|
|
})
|
|
const hasDefaultTrigger = useMemo(
|
|
() =>
|
|
eventTriggers.some(
|
|
(trigger) => trigger.name === 'ensure_rls' || trigger.function_name === 'rls_auto_enable'
|
|
),
|
|
[eventTriggers]
|
|
)
|
|
|
|
if (!projectRef || isLoadingEventTriggers || hasDefaultTrigger) return null
|
|
|
|
if (iconOnly) {
|
|
return <CreateEnsureRLSTriggerDialog iconOnly />
|
|
}
|
|
|
|
return (
|
|
<Card>
|
|
<CardContent className="flex items-center justify-between">
|
|
<div className="flex items-center gap-x-4">
|
|
<div className="rounded-lg bg-surface-300 text-foreground-light w-10 h-10 flex items-center justify-center">
|
|
<ShieldCheck size={18} />
|
|
</div>
|
|
<div className="text-sm">
|
|
<p>Auto-enable RLS for new tables</p>
|
|
<p className="text-foreground-lighter">
|
|
Create an event trigger that enables Row Level Security on all new tables
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex items-center gap-x-2">
|
|
<CreateEnsureRLSTriggerDialog />
|
|
<ButtonTooltip
|
|
icon={<X />}
|
|
type="text"
|
|
className="w-7"
|
|
tooltip={{ content: { side: 'bottom', text: 'Minimize' } }}
|
|
onClick={() => setIsMinimized(true)}
|
|
/>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
)
|
|
}
|
|
|
|
const CreateEnsureRLSTriggerDialog = ({ iconOnly }: { iconOnly?: boolean }) => {
|
|
const track = useTrack()
|
|
const { data: project } = useSelectedProjectQuery()
|
|
|
|
const [open, setOpen] = useState(false)
|
|
|
|
const { can: canCreateTriggers } = useAsyncCheckPermissions(
|
|
PermissionAction.TENANT_SQL_ADMIN_WRITE,
|
|
'triggers'
|
|
)
|
|
|
|
const { mutate: createEventTrigger, isPending: isCreating } =
|
|
useDatabaseEventTriggerCreateMutation({
|
|
onSuccess: () => {
|
|
toast.success(
|
|
'Successfully set up database trigger to automatically enable RLS on all new tables'
|
|
)
|
|
setOpen(false)
|
|
},
|
|
})
|
|
|
|
const handleCreateTrigger = () => {
|
|
if (!project) return
|
|
track('rls_event_trigger_banner_create_button_clicked')
|
|
createEventTrigger({
|
|
projectRef: project.ref,
|
|
connectionString: project.connectionString,
|
|
sql: AUTO_ENABLE_RLS_EVENT_TRIGGER_SQL,
|
|
})
|
|
}
|
|
|
|
return (
|
|
<Dialog open={open} onOpenChange={setOpen}>
|
|
<DialogTrigger asChild>
|
|
{iconOnly ? (
|
|
<ButtonTooltip
|
|
type="default"
|
|
icon={<ShieldCheck />}
|
|
className="w-7"
|
|
tooltip={{ content: { side: 'bottom', text: 'Auto-enable RLS for new tables' } }}
|
|
/>
|
|
) : (
|
|
<Button type="primary">Learn more</Button>
|
|
)}
|
|
</DialogTrigger>
|
|
<DialogContent size="large">
|
|
<DialogHeader>
|
|
<DialogTitle>Automatically enable RLS for newly created tables</DialogTitle>
|
|
<DialogDescription>Secure your data using Postgres Row Level Security</DialogDescription>
|
|
</DialogHeader>
|
|
|
|
<DialogSectionSeparator />
|
|
|
|
<DialogSection className="text-sm flex flex-col gap-y-2">
|
|
<p>
|
|
Tables in exposed schemas (default being the{' '}
|
|
<code className="text-code-inline">public</code> schema) are accessible to anyone.
|
|
Hence, we highly recommend enabling RLS on all such tables.
|
|
</p>
|
|
<p>
|
|
You can set up a database trigger to enable RLS automatically on all new tables with the
|
|
following SQL:
|
|
</p>
|
|
</DialogSection>
|
|
|
|
<CodeBlock language="sql" className="language-sql px-0 border-x-0 rounded-none h-64">
|
|
{AUTO_ENABLE_RLS_EVENT_TRIGGER_SQL.trim()}
|
|
</CodeBlock>
|
|
|
|
<DialogFooter>
|
|
<Button type="default" disabled={isCreating} onClick={() => setOpen(false)}>
|
|
Close
|
|
</Button>
|
|
<ButtonTooltip
|
|
disabled={!canCreateTriggers}
|
|
loading={isCreating}
|
|
onClick={handleCreateTrigger}
|
|
tooltip={{
|
|
content: {
|
|
side: 'bottom',
|
|
text: !canCreateTriggers
|
|
? 'You need additional permissions to create triggers'
|
|
: undefined,
|
|
},
|
|
}}
|
|
>
|
|
Create ensure_rls trigger
|
|
</ButtonTooltip>
|
|
</DialogFooter>
|
|
</DialogContent>
|
|
</Dialog>
|
|
)
|
|
}
|