| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186 |
- 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/Auth/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
- type="text"
- icon={<Edit />}
- className="w-7"
- tooltip={{ content: { side: 'bottom', text: 'Edit policy' } }}
- onClick={() => {
- handleSelectEditPolicy(policy)
- }}
- />
- </li>
- ))}
- </ul>
- </div>
- )
- }
|