Refine the policies page (#38204)

* refine the policies page

* minor fixes

* Fix TS

* Do not transform table name in policy row table header

---------

Co-authored-by: Joshen Lim <joshenlimek@gmail.com>
This commit is contained in:
Saxon FletcherandJoshen Lim authored and GitHub committed 2025-08-26 22:09:17 +07:00
1 parent 39df774af6
commit db4ae7e326
9 files changed
+287 -301

No files matched your search

@@ -1,7 +1,6 @@
import type { PostgresPolicy } from '@supabase/postgres-meta'
import { isEmpty } from 'lodash'
import { HelpCircle } from 'lucide-react'
import { useRouter } from 'next/router'
import Link from 'next/link'
import { useState } from 'react'
import { toast } from 'sonner'
@@ -11,32 +10,34 @@ import {
PolicyTableRowProps,
} from 'components/interfaces/Auth/Policies/PolicyTableRow'
import { ProtectedSchemaWarning } from 'components/interfaces/Database/ProtectedSchemaWarning'
import NoSearchResults from 'components/to-be-cleaned/NoSearchResults'
import ProductEmptyState from 'components/to-be-cleaned/ProductEmptyState'
import InformationBox from 'components/ui/InformationBox'
import { NoSearchResults } from 'components/ui/NoSearchResults'
import { useDatabasePolicyDeleteMutation } from 'data/database-policies/database-policy-delete-mutation'
import { useTableUpdateMutation } from 'data/tables/table-update-mutation'
import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
import { Button, Card, CardContent } from 'ui'
import ConfirmModal from 'ui-patterns/Dialogs/ConfirmDialog'
interface PoliciesProps {
search?: string
schema: string
tables: PolicyTableRowProps['table'][]
hasTables: boolean
isLocked: boolean
onSelectCreatePolicy: (table: string) => void
onSelectEditPolicy: (policy: PostgresPolicy) => void
onResetSearch?: () => void
}
const Policies = ({
export const Policies = ({
search,
schema,
tables,
hasTables,
isLocked,
onSelectCreatePolicy,
onSelectEditPolicy: onSelectEditPolicyAI,
onResetSearch,
}: PoliciesProps) => {
const router = useRouter()
const { ref } = useParams()
const { data: project } = useSelectedProjectQuery()
@@ -115,40 +116,21 @@ const Policies = ({
})
}
if (tables.length === 0) {
if (!hasTables) {
return (
<div className="flex-grow flex items-center justify-center">
<ProductEmptyState
size="large"
title="Row-Level Security (RLS) Policies"
ctaButtonLabel="Create a table"
infoButtonLabel="What is RLS?"
infoButtonUrl="https://supabase.com/docs/guides/auth/row-level-security"
onClickCta={() => router.push(`/project/${ref}/editor`)}
>
<div className="space-y-4">
<InformationBox
title="What are policies?"
icon={<HelpCircle size={14} strokeWidth={2} />}
description={
<div className="space-y-2">
<p className="text-sm">
Policies restrict, on a per-user basis, which rows can be returned by normal
queries, or inserted, updated, or deleted by data modification commands.
</p>
<p className="text-sm">
This is also known as Row-Level Security (RLS). Each policy is attached to a
table, and the policy is executed each time its accessed.
</p>
</div>
}
/>
<p className="text-sm text-foreground-light">
Create a table in this schema first before creating a policy.
</p>
</div>
</ProductEmptyState>
</div>
<Card className="w-full bg-transparent">
<CardContent className="flex flex-col items-center justify-center p-8">
<h2 className="heading-default">No tables to create policies for</h2>
<p className="text-sm text-foreground-light text-center mb-4">
RLS Policies control per-user access to table rows. Create a table in this schema first
before creating a policy.
</p>
<Button asChild type="default">
<Link href={`/project/${ref}/editor`}>Create a table</Link>
</Button>
</CardContent>
</Card>
)
}
@@ -170,7 +152,7 @@ const Policies = ({
</section>
))
) : hasTables ? (
<NoSearchResults />
<NoSearchResults searchString={search ?? ''} onResetFilter={onResetSearch} />
) : null}
</div>
@@ -202,5 +184,3 @@ const Policies = ({
</>
)
}
export default Policies
@@ -4,7 +4,6 @@ import { noop } from 'lodash'
import { Edit, MoreVertical, Trash } from 'lucide-react'
import { DropdownMenuItemTooltip } from 'components/ui/DropdownMenuItemTooltip'
import Panel from 'components/ui/Panel'
import { useAuthConfigQuery } from 'data/auth/auth-config-query'
import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions'
import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
@@ -13,6 +12,8 @@ import {
Badge,
Button,
cn,
TableRow,
TableCell,
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
@@ -57,58 +58,50 @@ const PolicyRow = ({
(policy.roles.includes('authenticated') || policy.roles.includes('public'))
return (
<Panel.Content
className={cn(
'flex border-overlay',
'w-full last:border-0 space-x-4 border-b py-4 lg:items-center'
)}
>
<div className="flex grow flex-col gap-y-1">
<div className="flex items-start gap-x-4">
<p className="font-mono text-xs text-foreground-light translate-y-[2px] min-w-12">
{policy.command}
</p>
<div className="flex flex-col gap-y-1">
<Button
type="text"
className="h-auto text-foreground text-sm border-none p-0 hover:bg-transparent justify-start"
onClick={() => onSelectEditPolicy(policy)}
>
{policy.name}
</Button>
<div className="flex items-center gap-x-1">
<div className="text-foreground-lighter text-sm">
Applied to:{' '}
{policy.roles.slice(0, 3).map((role, i) => (
<span key={`policy-${role}-${i}`}>
<code className="text-foreground-light text-xs">{role}</code>
{i < Math.min(policy.roles.length, 3) - 1 ? ', ' : ' '}
</span>
))}
{policy.roles.length > 1 ? 'roles' : 'role'}
</div>
{policy.roles.length > 3 && (
<Tooltip>
<TooltipTrigger asChild>
<code key="policy-etc" className="text-foreground-light text-xs">
+ {policy.roles.length - 3} more roles
</code>
</TooltipTrigger>
<TooltipContent side="bottom" align="center">
{policy.roles.slice(3).join(', ')}
</TooltipContent>
</Tooltip>
)}
</div>
</div>
<TableRow>
<TableCell className="w-[40%] truncate">
<div className="flex items-center gap-x-2 min-w-0">
<Button
type="text"
className="text-foreground text-sm p-0 hover:bg-transparent w-full truncate justify-start"
onClick={() => onSelectEditPolicy(policy)}
>
{policy.name}
</Button>
{appliesToAnonymousUsers ? (
<Badge color="yellow">Applies to anonymous users</Badge>
) : null}
</div>
</div>
<div>
</TableCell>
<TableCell className="w-[20%] truncate">
<code className="text-foreground-light text-xs">{policy.command}</code>
</TableCell>
<TableCell className="w-[30%] truncate">
<div className="flex items-center gap-x-1">
<div className="text-foreground-lighter text-sm">
{policy.roles.slice(0, 3).map((role, i) => (
<span key={`policy-${role}-${i}`}>
<code className="text-foreground-light text-xs">{role}</code>
{i < Math.min(policy.roles.length, 3) - 1 ? ', ' : ' '}
</span>
))}
{policy.roles.length > 1 ? 'roles' : 'role'}
</div>
{policy.roles.length > 3 && (
<Tooltip>
<TooltipTrigger asChild>
<code key="policy-etc" className="text-foreground-light text-xs">
+ {policy.roles.length - 3} more roles
</code>
</TooltipTrigger>
<TooltipContent side="bottom" align="center">
{policy.roles.slice(3).join(', ')}
</TooltipContent>
</Tooltip>
)}
</div>
</TableCell>
<TableCell className="w-0 text-right whitespace-nowrap">
{!isLocked && (
<DropdownMenu>
<DropdownMenuTrigger asChild>
@@ -127,9 +120,9 @@ const PolicyRow = ({
name: `Update policy ${policy.name}`,
open: true,
sqlSnippets: [sql],
initialInput: `Update the policy with name "${policy.name}" in the ${policy.schema} schema on the ${policy.table} table. It should...`,
initialInput: `Update the policy with name \"${policy.name}\" in the ${policy.schema} schema on the ${policy.table} table. It should...`,
suggestions: {
title: `I can help you make a change to the policy "${policy.name}" in the ${policy.schema} schema on the ${policy.table} table, here are a few example prompts to get you started:`,
title: `I can help you make a change to the policy \"${policy.name}\" in the ${policy.schema} schema on the ${policy.table} table, here are a few example prompts to get you started:`,
prompts: [
{
label: 'Improve Policy',
@@ -169,8 +162,8 @@ const PolicyRow = ({
</DropdownMenuContent>
</DropdownMenu>
)}
</div>
</Panel.Content>
</TableCell>
</TableRow>
)
}
@@ -1,13 +1,13 @@
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { noop } from 'lodash'
import { Lock, Unlock } from 'lucide-react'
import { Lock, Table } from 'lucide-react'
import { useParams } from 'common'
import { ButtonTooltip } from 'components/ui/ButtonTooltip'
import { EditorTablePageLink } from 'data/prefetchers/project.$ref.editor.$id'
import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions'
import { useAiAssistantStateSnapshot } from 'state/ai-assistant-state'
import { AiIconAnimation, Badge } from 'ui'
import { AiIconAnimation, Badge, CardTitle } from 'ui'
interface PolicyTableRowHeaderProps {
table: {
@@ -54,28 +54,19 @@ const PolicyTableRowHeader = ({
<EditorTablePageLink
projectRef={ref}
id={String(table.id)}
className="flex items-center gap-x-2"
className="flex items-center gap-x-3"
>
{table.rls_enabled ? (
<div className="flex items-center gap-x-1 text-xs">
<Lock size={14} strokeWidth={2} className="text-brand" />
</div>
) : (
<div className="flex items-center gap-x-1 text-xs">
<Unlock size={14} strokeWidth={2} className="text-warning-600" />
</div>
)}
<h4 className="m-0">{table.name}</h4>
<Table strokeWidth={1.5} size={16} className="text-foreground-muted" />
<CardTitle className="m-0 normal-case">{table.name}</CardTitle>
{!table.rls_enabled && <Badge variant="warning">RLS Disabled</Badge>}
</EditorTablePageLink>
<div className="flex items-center gap-x-2">
{isTableLocked && (
<Badge>
<span className="flex gap-2 items-center text-xs uppercase text-foreground-lighter">
<Lock size={12} /> Locked
</span>
</Badge>
)}
</div>
{isTableLocked && (
<Badge>
<span className="flex gap-2 items-center text-xs uppercase text-foreground-lighter">
<Lock size={12} /> Locked
</span>
</Badge>
)}
</div>
{!isTableLocked && (
<div className="flex-1">
@@ -1,14 +1,24 @@
import type { PostgresPolicy } from '@supabase/postgres-meta'
import { noop } from 'lodash'
import { Info } from 'lucide-react'
import AlertError from 'components/ui/AlertError'
import Panel from 'components/ui/Panel'
import { useProjectPostgrestConfigQuery } from 'data/config/project-postgrest-config-query'
import { useDatabasePoliciesQuery } from 'data/database-policies/database-policies-query'
import { useTableRolesAccessQuery } from 'data/tables/table-roles-access-query'
import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
import { cn, Tooltip, TooltipContent, TooltipTrigger } from 'ui'
import {
Alert_Shadcn_,
AlertDescription_Shadcn_,
Card,
CardContent,
CardHeader,
cn,
Table,
TableBody,
TableHead,
TableHeader,
TableRow,
} from 'ui'
import ShimmeringLoader from 'ui-patterns/ShimmeringLoader'
import PolicyRow from './PolicyRow'
import PolicyTableRowHeader from './PolicyTableRowHeader'
@@ -73,85 +83,86 @@ export const PolicyTableRow = ({
.filter((policy) => policy.schema === table.schema && policy.table === table.name)
.sort((a, b) => a.name.localeCompare(b.name))
const rlsEnabledNoPolicies = isRLSEnabled && policies.length === 0
const isRealtimeSchema = table.schema === 'realtime'
const isRealtimeMessagesTable = isRealtimeSchema && table.name === 'messages'
const isTableLocked = isRealtimeSchema ? !isRealtimeMessagesTable : isLocked
return (
<Panel
className="!m-0"
title={
<Card className={cn(isPubliclyReadableWritable && 'border-warning-500')}>
<CardHeader
className={cn(
'py-3 px-4',
(isPubliclyReadableWritable || rlsEnabledNoPolicies) && 'border-b-0'
)}
>
<PolicyTableRowHeader
table={table}
isLocked={isLocked}
onSelectToggleRLS={onSelectToggleRLS}
onSelectCreatePolicy={onSelectCreatePolicy}
/>
}
>
</CardHeader>
{(isPubliclyReadableWritable || rlsEnabledNoPolicies) && (
<div
className={cn(
'dark:bg-alternative-200 bg-surface-200 px-6 py-2 text-xs flex items-center gap-2',
policies.length === 0 ? '' : 'border-b'
)}
<Alert_Shadcn_
className="border-0 rounded-none mb-0 border-b border-t"
variant={isPubliclyReadableWritable ? 'warning' : 'default'}
>
<div
className={cn(
'w-1.5 h-1.5 rounded-full bg-warning-600 ',
rlsEnabledNoPolicies && 'bg-selection'
)}
/>
<span
className={cn('font-bold text-warning-600', rlsEnabledNoPolicies && 'text-foreground')}
>
{isPubliclyReadableWritable ? 'Warning' : 'Note'}:
</span>{' '}
<span className="text-foreground-light">
<AlertDescription_Shadcn_>
{isPubliclyReadableWritable
? 'Row Level Security is disabled. Your table is publicly readable and writable.'
: 'Row Level Security is enabled, but no policies exist. No data will be selectable via Supabase APIs.'}
</span>
{isPubliclyReadableWritable && (
<Tooltip>
<TooltipTrigger asChild>
<Info className="w-3 h-3" />
</TooltipTrigger>
<TooltipContent className="w-[400px]">
Anyone with the project's anonymous key can modify or delete your data. Enable RLS
and create access policies to keep your data secure.
</TooltipContent>
</Tooltip>
)}
</div>
? "Anyone with your project's anonymous key can read, modify, or delete your data."
: 'No data will be selectable via Supabase APIs because RLS is enabled but no policies have been created yet.'}
</AlertDescription_Shadcn_>
</Alert_Shadcn_>
)}
{isLoading && (
<div className="px-6 py-4">
<CardContent>
<ShimmeringLoader />
</div>
</CardContent>
)}
{isError && (
<AlertError
className="border-0 rounded-none"
error={error}
subject="Failed to retrieve policies"
/>
<CardContent>
<AlertError
className="border-0 rounded-none"
error={error}
subject="Failed to retrieve policies"
/>
</CardContent>
)}
{isSuccess && (
<>
{policies.length === 0 && (
<div className="px-6 py-4 flex flex-col gap-y-3">
<p className="text-foreground-lighter text-sm">No policies created yet</p>
</div>
<CardContent className="p-0">
{policies.length === 0 ? (
<p className="text-foreground-lighter text-sm p-4">No policies created yet</p>
) : (
<Table className="table-fixed">
<TableHeader>
<TableRow>
<TableHead className="w-[40%]">Name</TableHead>
<TableHead className="w-[20%]">Command</TableHead>
<TableHead className="w-[30%]">Applied to</TableHead>
<TableHead className="w-0 text-right">
<span className="sr-only">Actions</span>
</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{policies.map((policy) => (
<PolicyRow
key={policy.id}
policy={policy}
isLocked={isTableLocked}
onSelectEditPolicy={onSelectEditPolicy}
onSelectDeletePolicy={onSelectDeletePolicy}
/>
))}
</TableBody>
</Table>
)}
{policies?.map((policy) => (
<PolicyRow
key={policy.id}
isLocked={isLocked}
policy={policy}
onSelectEditPolicy={onSelectEditPolicy}
onSelectDeletePolicy={onSelectDeletePolicy}
/>
))}
</>
</CardContent>
)}
</Panel>
</Card>
)
}
@@ -1,7 +1,7 @@
import { PostgresPolicy } from '@supabase/postgres-meta'
import { useState } from 'react'
import Policies from 'components/interfaces/Auth/Policies/Policies'
import { Policies } from 'components/interfaces/Auth/Policies/Policies'
import { PolicyEditorPanel } from 'components/interfaces/Auth/Policies/PolicyEditorPanel'
import AlertError from 'components/ui/AlertError'
import { FormHeader } from 'components/ui/Forms/FormHeader'
@@ -1,8 +1,9 @@
import SVG from 'react-inlinesvg'
import { BASE_PATH } from 'lib/constants'
import SVG from 'react-inlinesvg'
// [Joshen] To be deprecated in favor of NoSearchResults in components/ui
/**
* To be deprecated in favor of NoSearchResults in components/ui
*/
export const NoSearchResults = () => {
return (
<div className="flex h-64 flex-col items-center justify-center">
@@ -6,7 +6,11 @@ export interface NoSearchResultsProps {
className?: string
}
const NoSearchResults = ({ searchString, onResetFilter, className }: NoSearchResultsProps) => {
export const NoSearchResults = ({
searchString,
onResetFilter,
className,
}: NoSearchResultsProps) => {
return (
<div
className={cn(
+3 -1
View File
@@ -35,6 +35,7 @@ interface SchemaSelectorProps {
onSelectSchema: (name: string) => void
onSelectCreateSchema?: () => void
portal?: boolean
align?: 'start' | 'end'
}
const SchemaSelector = ({
@@ -48,6 +49,7 @@ const SchemaSelector = ({
onSelectSchema,
onSelectCreateSchema,
portal = true,
align = 'start',
}: SchemaSelectorProps) => {
const [open, setOpen] = useState(false)
const { can: canCreateSchemas } = useAsyncCheckProjectPermissions(
@@ -130,7 +132,7 @@ const SchemaSelector = ({
<PopoverContent_Shadcn_
className="p-0 min-w-[200px] pointer-events-auto"
side="bottom"
align="start"
align={align}
portal={portal}
sameWidthAsTrigger
>
+118 -114
View File
@@ -3,14 +3,15 @@ import { PermissionAction } from '@supabase/shared-types/out/constants'
import { Search } from 'lucide-react'
import { useState } from 'react'
import { useParams } from 'common'
import { useIsInlineEditorEnabled } from 'components/interfaces/App/FeaturePreview/FeaturePreviewContext'
import Policies from 'components/interfaces/Auth/Policies/Policies'
import { Policies } from 'components/interfaces/Auth/Policies/Policies'
import { getGeneralPolicyTemplates } from 'components/interfaces/Auth/Policies/PolicyEditorModal/PolicyEditorModal.constants'
import { PolicyEditorPanel } from 'components/interfaces/Auth/Policies/PolicyEditorPanel'
import { generatePolicyUpdateSQL } from 'components/interfaces/Auth/Policies/PolicyTableRow/PolicyTableRow.utils'
import AuthLayout from 'components/layouts/AuthLayout/AuthLayout'
import DefaultLayout from 'components/layouts/DefaultLayout'
import { PageLayout } from 'components/layouts/PageLayout/PageLayout'
import { ScaffoldContainer, ScaffoldSection } from 'components/layouts/Scaffold'
import AlertError from 'components/ui/AlertError'
import { DocsButton } from 'components/ui/DocsButton'
import { EditorPanel } from 'components/ui/EditorPanel/EditorPanel'
@@ -24,7 +25,7 @@ import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
import { useUrlState } from 'hooks/ui/useUrlState'
import { useIsProtectedSchema } from 'hooks/useProtectedSchemas'
import type { NextPageWithLayout } from 'types'
import { Input } from 'ui'
import { Input } from 'ui-patterns/DataInputs/Input'
/**
* Filter tables by table name and policy name
@@ -62,7 +63,6 @@ const onFilterTables = (
}
const AuthPoliciesPage: NextPageWithLayout = () => {
const { ref } = useParams()
const [params, setParams] = useUrlState<{
schema?: string
search?: string
@@ -106,140 +106,144 @@ const AuthPoliciesPage: NextPageWithLayout = () => {
}
return (
<div className="flex flex-col h-full">
<div className="mb-4">
<div className="w-full flex flex-col lg:flex-row items-center justify-between gap-2">
<ScaffoldContainer size="full">
<ScaffoldSection isFullWidth>
<div className="mb-4 flex flex-row gap-2 justify-between">
<Input
size="tiny"
placeholder="Filter tables and policies"
className="block w-full lg:w-52"
containerClassName="[&>div>svg]:-mt-0.5"
value={searchString || ''}
onChange={(e) => {
const str = e.target.value
setParams({ ...params, search: str === '' ? undefined : str })
}}
icon={<Search size={14} />}
/>
<SchemaSelector
className="w-full lg:w-[180px]"
size="tiny"
align="end"
showError={false}
selectedSchemaName={schema}
onSelectSchema={(schema) => {
setParams({ ...params, search: undefined, schema })
}}
/>
<div className="w-full flex-grow flex items-center justify-between gap-2 lg:gap-4">
<Input
size="tiny"
placeholder="Filter tables and policies"
className="block w-full lg:w-52"
value={searchString || ''}
onChange={(e) => {
const str = e.target.value
setParams({ ...params, search: str === '' ? undefined : str })
}}
icon={<Search size={14} />}
/>
<DocsButton href="https://supabase.com/docs/learn/auth-deep-dive/auth-row-level-security" />
</div>
</div>
</div>
{isLoading && <GenericSkeletonLoader />}
{isLoading && <GenericSkeletonLoader />}
{isError && <AlertError error={error} subject="Failed to retrieve tables" />}
{isError && <AlertError error={error} subject="Failed to retrieve tables" />}
{isSuccess && (
<Policies
{isSuccess && (
<Policies
search={searchString}
schema={schema}
tables={filteredTables}
hasTables={tables.length > 0}
isLocked={isSchemaLocked}
onSelectCreatePolicy={(table: string) => {
setSelectedTable(table)
setSelectedPolicyToEdit(undefined)
if (isInlineEditorEnabled) {
setEditorPanelOpen(true)
} else {
setShowPolicyAiEditor(true)
}
}}
onSelectEditPolicy={(policy) => {
setSelectedPolicyToEdit(policy)
setSelectedTable(undefined)
if (isInlineEditorEnabled) {
setEditorPanelOpen(true)
} else {
setShowPolicyAiEditor(true)
}
}}
onResetSearch={() => setParams({ ...params, search: undefined })}
/>
)}
<PolicyEditorPanel
visible={showPolicyAiEditor}
schema={schema}
tables={filteredTables}
hasTables={tables.length > 0}
isLocked={isSchemaLocked}
onSelectCreatePolicy={(table: string) => {
setSelectedTable(table)
setSelectedPolicyToEdit(undefined)
if (isInlineEditorEnabled) {
setEditorPanelOpen(true)
} else {
setShowPolicyAiEditor(true)
}
}}
onSelectEditPolicy={(policy) => {
setSelectedPolicyToEdit(policy)
searchString={searchString}
selectedTable={selectedTable}
selectedPolicy={selectedPolicyToEdit}
onSelectCancel={() => {
setSelectedTable(undefined)
if (isInlineEditorEnabled) {
setEditorPanelOpen(true)
} else {
setShowPolicyAiEditor(true)
}
setShowPolicyAiEditor(false)
setSelectedPolicyToEdit(undefined)
}}
authContext="database"
/>
)}
<PolicyEditorPanel
visible={showPolicyAiEditor}
schema={schema}
searchString={searchString}
selectedTable={selectedTable}
selectedPolicy={selectedPolicyToEdit}
onSelectCancel={() => {
setSelectedTable(undefined)
setShowPolicyAiEditor(false)
setSelectedPolicyToEdit(undefined)
}}
authContext="database"
/>
<EditorPanel
open={editorPanelOpen}
onClose={() => {
setEditorPanelOpen(false)
setSelectedPolicyToEdit(undefined)
setSelectedTable(undefined)
}}
onRunSuccess={() => {
setEditorPanelOpen(false)
setSelectedPolicyToEdit(undefined)
setSelectedTable(undefined)
}}
initialValue={
selectedPolicyToEdit
? generatePolicyUpdateSQL(selectedPolicyToEdit)
: selectedTable
? `create policy "replace_with_policy_name"
on ${schema}.${selectedTable}
for select
to authenticated
using (
true -- Write your policy condition here
);`
: ''
}
label={
selectedPolicyToEdit
? 'RLS policies are just SQL statements that you can alter'
: selectedTable
? `Create new RLS policy on "${selectedTable}"`
: ''
}
initialPrompt={
selectedPolicyToEdit
? `Update the policy with name "${selectedPolicyToEdit.name}" in the ${selectedPolicyToEdit.schema} schema on the ${selectedPolicyToEdit.table} table. It should...`
: selectedTable
? `Create and name a entirely new RLS policy for the "${selectedTable}" table in the ${schema} schema. The policy should...`
: ''
}
templates={
selectedPolicyToEdit
? getGeneralPolicyTemplates(
selectedPolicyToEdit.schema,
selectedPolicyToEdit.table
).map((template) => ({
name: template.templateName,
description: template.description,
content: template.statement,
}))
: []
}
/>
</div>
<EditorPanel
open={editorPanelOpen}
onClose={() => {
setEditorPanelOpen(false)
setSelectedPolicyToEdit(undefined)
setSelectedTable(undefined)
}}
onRunSuccess={() => {
setEditorPanelOpen(false)
setSelectedPolicyToEdit(undefined)
setSelectedTable(undefined)
}}
initialValue={
selectedPolicyToEdit
? generatePolicyUpdateSQL(selectedPolicyToEdit)
: selectedTable
? `create policy "replace_with_policy_name"\n on ${schema}.${selectedTable}\n for select\n to authenticated\n using (\n true -- Write your policy condition here\n);`
: ''
}
label={
selectedPolicyToEdit
? 'RLS policies are just SQL statements that you can alter'
: selectedTable
? `Create new RLS policy on "${selectedTable}"`
: ''
}
initialPrompt={
selectedPolicyToEdit
? `Update the policy with name "${selectedPolicyToEdit.name}" in the ${selectedPolicyToEdit.schema} schema on the ${selectedPolicyToEdit.table} table. It should...`
: selectedTable
? `Create and name a entirely new RLS policy for the "${selectedTable}" table in the ${schema} schema. The policy should...`
: ''
}
templates={
selectedPolicyToEdit
? getGeneralPolicyTemplates(
selectedPolicyToEdit.schema,
selectedPolicyToEdit.table
).map((template) => ({
name: template.templateName,
description: template.description,
content: template.statement,
}))
: []
}
/>
</ScaffoldSection>
</ScaffoldContainer>
)
}
AuthPoliciesPage.getLayout = (page) => (
<DefaultLayout>
<AuthLayout>
<div className="h-full p-4">{page}</div>
<PageLayout
title="Policies"
subtitle="Manage Row Level Security policies for your tables"
secondaryActions={
<DocsButton href="https://supabase.com/docs/learn/auth-deep-dive/auth-row-level-security" />
}
size="large"
>
{page}
</PageLayout>
</AuthLayout>
</DefaultLayout>
)