| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187 |
- import { useParams } from 'common'
- import { noop } from 'lodash'
- import { memo, useMemo } from 'react'
- import {
- Card,
- CardContent,
- CardHeader,
- cn,
- Table,
- TableBody,
- TableHead,
- TableHeader,
- TableRow,
- } from 'ui'
- import { Admonition } from 'ui-patterns'
- import { ShimmeringLoader } from 'ui-patterns/ShimmeringLoader'
- import { usePoliciesData } from '../PoliciesDataContext'
- import { PolicyRow } from './PolicyRow'
- import type { PolicyTable } from './PolicyTableRow.types'
- import type { Policy } from './PolicyTableRow.utils'
- import { getTableAdmonitionMessage, getTableDataApiStatus } from './PolicyTableRow.utils'
- import { PolicyTableRowHeader } from './PolicyTableRowHeader'
- import AlertError from '@/components/ui/AlertError'
- import { InlineLink } from '@/components/ui/InlineLink'
- import { useTableApiAccessQuery } from '@/data/privileges/table-api-access-query'
- import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
- export interface PolicyTableRowProps {
- table: PolicyTable
- isLocked: boolean
- onSelectToggleRLS: (table: PolicyTable) => void
- onSelectCreatePolicy: (table: PolicyTable) => void
- onSelectEditPolicy: (policy: Policy) => void
- onSelectDeletePolicy: (policy: Policy) => void
- }
- const PolicyTableRowComponent = ({
- table,
- isLocked,
- onSelectToggleRLS = noop,
- onSelectCreatePolicy = noop,
- onSelectEditPolicy = noop,
- onSelectDeletePolicy = noop,
- }: PolicyTableRowProps) => {
- const { ref } = useParams()
- const { data: project } = useSelectedProjectQuery()
- const { getPoliciesForTable, isPoliciesLoading, isPoliciesError, policiesError, exposedSchemas } =
- usePoliciesData()
- const policies = useMemo(
- () => getPoliciesForTable(table.schema, table.name),
- [getPoliciesForTable, table.schema, table.name]
- )
- // [Joshen] Classification is more granular than "RLS on/off" alone — it also considers
- // schema exposure and whether anon/authenticated/service_role actually have grants.
- // Ideally we'd rely on the security lints, but they only look at the public schema and
- // ignore roles. Once the lints cover both, we can switch to them as the source of truth.
- const tableNames = useMemo(() => [table.name], [table.name])
- const {
- data: apiAccessMap,
- isPending: isLoadingRolesAccess,
- isError: isRolesAccessError,
- } = useTableApiAccessQuery({
- projectRef: project?.ref,
- connectionString: project?.connectionString,
- schemaName: table.schema,
- tableNames,
- })
- const status = useMemo(
- () =>
- getTableDataApiStatus({
- isSchemaExposed: exposedSchemas.has(table.schema),
- apiAccessData: apiAccessMap?.[table.name],
- isRLSEnabled: table.rls_enabled,
- policiesCount: policies.length,
- }),
- [exposedSchemas, apiAccessMap, table.schema, table.name, table.rls_enabled, policies.length]
- )
- const hasApiAccess =
- status === 'publicly-readable' || status === 'locked-by-rls' || status === 'secured'
- const isPubliclyReadable = status === 'publicly-readable'
- const isRealtimeSchema = table.schema === 'realtime'
- const isRealtimeMessagesTable = isRealtimeSchema && table.name === 'messages'
- const isTableLocked = isRealtimeSchema ? !isRealtimeMessagesTable : isLocked
- const showPolicies = !isPoliciesLoading && !isPoliciesError && !isLoadingRolesAccess
- const admonitionMessage = useMemo(() => getTableAdmonitionMessage(status), [status])
- return (
- <Card className={cn(isPubliclyReadable && 'border-warning-500')}>
- <CardHeader className={cn('py-3 px-4', status !== 'secured' && 'border-b-0')}>
- <PolicyTableRowHeader
- table={table}
- isLocked={isLocked}
- hasApiAccess={hasApiAccess}
- isLoadingApiAccess={isLoadingRolesAccess}
- onSelectToggleRLS={onSelectToggleRLS}
- onSelectCreatePolicy={onSelectCreatePolicy}
- />
- </CardHeader>
- {!isLoadingRolesAccess && !isRolesAccessError && status === 'schema-not-exposed' && (
- <Admonition
- showIcon={false}
- type="warning"
- className="border-0 border-y rounded-none min-h-12 flex items-center"
- >
- <p className="text-foreground-light">
- No data will be selectable via Briven APIs as this schema is not exposed. You may
- configure this in your project’s{' '}
- <InlineLink href={`/project/${ref}/integrations/data_api/settings`}>
- API settings
- </InlineLink>
- .
- </p>
- </Admonition>
- )}
- {!isLoadingRolesAccess && !isRolesAccessError && admonitionMessage !== null && (
- <Admonition
- showIcon={false}
- type={isPubliclyReadable ? 'warning' : 'default'}
- className="border-0 border-y rounded-none min-h-12 flex items-center"
- >
- <p>{admonitionMessage}</p>
- </Admonition>
- )}
- {(isPoliciesLoading || isLoadingRolesAccess) && (
- <CardContent>
- <ShimmeringLoader />
- </CardContent>
- )}
- {isPoliciesError && (
- <CardContent>
- <AlertError
- className="border-0 rounded-none"
- error={policiesError}
- subject="Failed to retrieve policies"
- />
- </CardContent>
- )}
- {showPolicies && (
- <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="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>
- )}
- </CardContent>
- )}
- </Card>
- )
- }
- export const PolicyTableRow = memo(PolicyTableRowComponent)
- PolicyTableRow.displayName = 'PolicyTableRow'
|