Files
Joshen Lim 1de298ff31 Reinstate https://github.com/supabase/supabase/pull/45143 into latest master (#47433)
## Context

Previous PR was [here](https://github.com/supabase/supabase/pull/45143)
but it got stale with lots of conflicts so figured it'll be easier redo
it off the latest master

Moves policies page from Auth to Database under an Access Control
section along with Roles. This moves all existing files, applies
redirects, and updates urls to point to the new route

<img width="274" height="412" alt="image"
src="https://github.com/user-attachments/assets/7952c185-64ae-4355-ba36-45397efe1787"
/>

<img width="453" height="471" alt="image"
src="https://github.com/user-attachments/assets/04b3dcb3-48a5-4049-9893-d01109fb46a9"
/>


## To test
- [ ] Verify that policies now live under Database correctly

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added a quick navigation shortcut to open **Database > Policies
(RLS)**.
* **Bug Fixes**
* Updated Policies and RLS-related links across the product to open the
**Database policies** area (menus, command palette, context actions,
alerts, and link-outs).
* Added a permanent redirect from the old **auth policies** URL to the
new **database policies** URL.
* **Documentation**
* Updated RLS Dashboard and security checklist instructions to reference
**Database > Policies**.
* **Tests**
  * Adjusted automated tests to validate the new Policies route.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-30 18:49:33 +08:00

187 lines
5.8 KiB
TypeScript

import { Check, ChevronDown, Edit, X } from 'lucide-react'
import { useMemo } from 'react'
import { cn, Collapsible, CollapsibleContent, CollapsibleTrigger, WarningIcon } from 'ui'
import type { Policy } from '@/components/interfaces/Database/Policies/PolicyTableRow/PolicyTableRow.utils'
import { ButtonTooltip } from '@/components/ui/ButtonTooltip'
interface RLSTableCardProps {
table: { schema: string; name: string; isRLSEnabled: boolean }
role?: string
policies: Policy[]
handleSelectEditPolicy: (policy: Policy) => void
}
export const RLSTableCard = ({
table,
role,
policies,
handleSelectEditPolicy,
}: RLSTableCardProps) => {
const { schema, name, isRLSEnabled } = table
const trueOnlyPolicy = policies.find((x) => x.definition === 'true')
const falseOnlyPolicy = policies.find((x) => x.definition === 'false')
const noPolicies = isRLSEnabled && policies.length === 0
const tableAccessDescription = useMemo(() => {
if (!isRLSEnabled) {
return (
<p>
RLS is disabled and all data is publicly accessible. We highly recommend enabling RLS and
adding policies to restrict access.
</p>
)
}
if (noPolicies) {
return (
<p>
RLS is enabled but no policies exist for the{' '}
<code className="text-code-inline">{role}</code> role on this table - no data will be
returned.
</p>
)
}
if (trueOnlyPolicy) {
return (
<>
<p>
The policy "{trueOnlyPolicy.name}" for the{' '}
<code className="text-code-inline">{role}</code> role on this table evaluates to{' '}
<code className="text-code-inline">true</code>, so all data from this query is
accessible to this user.
</p>
<TableAccessPolicySummary
policies={policies}
handleSelectEditPolicy={handleSelectEditPolicy}
/>
</>
)
}
if (falseOnlyPolicy) {
return (
<>
<p>
The policy "{falseOnlyPolicy.name}" for the{' '}
<code className="text-code-inline">{role}</code> role on this table evaluates to{' '}
<code className="text-code-inline">false</code>, so no data from this query is
accessible to this user.
</p>
<TableAccessPolicySummary
policies={policies}
handleSelectEditPolicy={handleSelectEditPolicy}
/>
</>
)
}
return (
<>
<p>
{policies.length} {policies.length > 1 ? 'policies apply' : 'policy applies'} for the{' '}
<code className="text-code-inline">{role}</code> role on this table. Only rows that match{' '}
{policies.length > 1 ? 'these conditions' : 'this condition'} are returned.
</p>
<TableAccessPolicySummary
policies={policies}
handleSelectEditPolicy={handleSelectEditPolicy}
/>
</>
)
}, [
isRLSEnabled,
noPolicies,
trueOnlyPolicy,
falseOnlyPolicy,
policies,
role,
handleSelectEditPolicy,
])
return (
<Collapsible
className={cn('border rounded-sm', !isRLSEnabled && 'bg-warning-300 border-warning-500')}
>
<CollapsibleTrigger className="flex items-center justify-between px-3 py-2 w-full [&[data-state=open]>div>svg]:-rotate-180!">
<div className="w-full flex items-center justify-between">
<div className="flex items-center gap-x-2">
{!isRLSEnabled ? (
<WarningIcon />
) : noPolicies || falseOnlyPolicy ? (
<X size={16} className="text-destructive" />
) : (
<Check size={16} className="text-brand" />
)}
<p className={cn('text-xs font-mono', !isRLSEnabled && 'font-medium text-foreground')}>
{schema}.{name}
</p>
</div>
</div>
<div className="flex items-center gap-x-2">
<p
className={cn(
'text-xs text-foreground-light w-max',
!isRLSEnabled && 'text-foreground'
)}
>
{noPolicies || falseOnlyPolicy
? 'Returns no rows'
: !isRLSEnabled || !!trueOnlyPolicy
? 'Returns all rows'
: null}
</p>
<ChevronDown className="transition-transform duration-200" strokeWidth={1.5} size={14} />
</div>
</CollapsibleTrigger>
<CollapsibleContent
className={cn(
'border-t p-3 text-sm text-foreground-light',
!isRLSEnabled && 'border-warning-500'
)}
>
{tableAccessDescription}
</CollapsibleContent>
</Collapsible>
)
}
const TableAccessPolicySummary = ({
policies,
handleSelectEditPolicy,
}: {
policies: Policy[]
handleSelectEditPolicy: (policy: Policy) => void
}) => {
return (
<div className="border rounded-sm mt-4">
<p className="text-xs font-mono text-foreground-light uppercase border-b px-3 py-2">
{policies.length} {policies.length > 1 ? 'policies' : 'policy'} applied
</p>
<ul>
{policies.map((policy) => (
<li key={policy.id} className="px-3 py-2 flex justify-between items-center">
<div>
<p>{policy.name}</p>
<p className="text-foreground-lighter">
Show rows where:{' '}
<code className="text-code-inline text-foreground">{policy.definition}</code>
</p>
</div>
<ButtonTooltip
variant="text"
icon={<Edit />}
className="w-7"
tooltip={{ content: { side: 'bottom', text: 'Edit policy' } }}
onClick={() => {
handleSelectEditPolicy(policy)
}}
/>
</li>
))}
</ul>
</div>
)
}