import { PermissionAction } from '@supabase/shared-types/out/constants' import { useParams } from 'common' import { Realtime } from 'icons' import { BookOpenText, Lightbulb, Lock, MoreVertical, PlusCircle, Unlock } from 'lucide-react' import Link from 'next/link' import { parseAsBoolean, useQueryState } from 'nuqs' import { useState } from 'react' import { toast } from 'sonner' import { Button, cn, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, Popover, PopoverContent, PopoverTrigger, Tooltip, TooltipContent, TooltipTrigger, } from 'ui' import { EnableIndexAdvisorDialog } from '../QueryPerformance/IndexAdvisor/EnableIndexAdvisorButton' import { RoleImpersonationPopover } from '../RoleImpersonationSelector/RoleImpersonationPopover' import { ExposedMaterializedViewDialog } from './ExposedMaterializedViewDialog' import { InsertButton } from './InsertButton' import { RealtimeToggleDialog } from './RealtimeToggleDialog' import { SecurityDefinerViewPopover } from './SecurityDefinerViewPopover' import { ViewEntityAutofixSecurityModal } from './ViewEntityAutofixSecurityModal' import { RefreshButton } from '@/components/grid/components/header/RefreshButton' import { useTableIndexAdvisor } from '@/components/grid/context/TableIndexAdvisorContext' import { RLSToggleDialog } from '@/components/interfaces/Database/RLSToggleDialog' import { getEntityLintDetails, getTablePoliciesUrl, } from '@/components/interfaces/TableGridEditor/TableEntity.utils' import { ButtonTooltip } from '@/components/ui/ButtonTooltip' import { useDatabasePoliciesQuery } from '@/data/database-policies/database-policies-query' import { useIsTableRealtimeEnabled } from '@/data/database-publications/database-publications-query' import { useProjectLintsQuery } from '@/data/lint/lint-query' import { Entity, isTableLike, isForeignTable as isTableLikeForeignTable, isMaterializedView as isTableLikeMaterializedView, isView as isTableLikeView, } from '@/data/table-editor/table-editor-types' import { useTableUpdateMutation } from '@/data/tables/table-update-mutation' import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions' import { useIsFeatureEnabled } from '@/hooks/misc/useIsFeatureEnabled' import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject' import { useIsProtectedSchema } from '@/hooks/useProtectedSchemas' import { DOCS_URL } from '@/lib/constants' import { useTrack } from '@/lib/telemetry/track' import { useAppStateSnapshot } from '@/state/app-state' import { useTableEditorTableStateSnapshot } from '@/state/table-editor-table' export interface GridHeaderActionsProps { table: Entity isRefetching: boolean } export const GridHeaderActions = ({ table, isRefetching }: GridHeaderActionsProps) => { const { ref } = useParams() const track = useTrack() const appSnap = useAppStateSnapshot() const snap = useTableEditorTableStateSnapshot() const { data: project } = useSelectedProjectQuery() const [rlsConfirmModalOpen, setRlsConfirmModalOpen] = useState(false) const [realtimeDialogOpen, setRealtimeDialogOpen] = useState(false) const [indexAdvisorDialogOpen, setIndexAdvisorDialogOpen] = useState(false) const [isAutofixViewSecurityModalOpen, setIsAutofixViewSecurityModalOpen] = useState(false) const [isExposedMaterializedViewDialogOpen, setIsExposedMaterializedViewDialogOpen] = useState(false) const [showWarning, setShowWarning] = useQueryState( 'showWarning', parseAsBoolean.withDefault(false) ) // need project lints to get security status for views const { data: lints = [] } = useProjectLintsQuery({ projectRef: project?.ref }) // Use table-specific index advisor context const { isAvailable: isIndexAdvisorAvailable, isEnabled: isIndexAdvisorEnabled } = useTableIndexAdvisor() const isTable = isTableLike(table) const isForeignTable = isTableLikeForeignTable(table) const isView = isTableLikeView(table) const isMaterializedView = isTableLikeMaterializedView(table) const { realtimeAll: realtimeEnabled } = useIsFeatureEnabled(['realtime:all']) const { isSchemaLocked } = useIsProtectedSchema({ schema: table.schema }) const isRealtimeEnabled = useIsTableRealtimeEnabled({ id: table.id }) const { mutateAsync: updateTable, isPending: isUpdatingTable } = useTableUpdateMutation({ onError: (error) => { toast.error(`Failed to toggle RLS: ${error.message}`) }, }) const showHeaderActions = snap.selectedRows.size === 0 const projectRef = project?.ref const { data } = useDatabasePoliciesQuery( { projectRef: project?.ref, connectionString: project?.connectionString, schemas: [table.schema], }, { enabled: !!table } ) const policies = (data ?? []).filter( (policy) => policy.schema === table.schema && policy.table === table.name ) const { can: canSqlWriteTables, isLoading: isLoadingPermissions } = useAsyncCheckPermissions( PermissionAction.TENANT_SQL_ADMIN_WRITE, 'tables' ) const { can: canSqlWriteColumns } = useAsyncCheckPermissions( PermissionAction.TENANT_SQL_ADMIN_WRITE, 'columns' ) const isReadOnly = !isLoadingPermissions && !canSqlWriteTables && !canSqlWriteColumns // This will change when we allow autogenerated API docs for schemas other than `public` const doesHaveAutoGeneratedAPIDocs = table.schema === 'public' const { hasLint: tableHasLints } = getEntityLintDetails( table.name, 'rls_disabled_in_public', ['ERROR'], lints, table.schema ) const { hasLint: viewHasLints, matchingLint: matchingViewLint } = getEntityLintDetails( table.name, 'security_definer_view', ['ERROR', 'WARN'], lints, table.schema ) const { hasLint: materializedViewHasLints, matchingLint: matchingMaterializedViewLint } = getEntityLintDetails( table.name, 'materialized_view_in_api', ['ERROR', 'WARN'], lints, table.schema ) const onViewAPIDocs = () => { appSnap.setActiveDocsSection(['entities', table.name]) appSnap.setShowProjectApiDocs(true) track('api_docs_opened', { source: 'table_editor' }) } const onToggleRLS = async () => { const payload = { id: table.id, rls_enabled: !(isTable && table.rls_enabled), } const updateTablePromise = updateTable({ projectRef: project?.ref!, connectionString: project?.connectionString, id: table.id, name: table.name, schema: table.schema, payload: payload, }) track('table_rls_enabled', { method: 'table_editor', schema_name: table.schema, table_name: table.name, }) return updateTablePromise } return (
You can restrict and control who can read, write and update data in this table using Row Level Security.
With RLS enabled, anonymous users will not be able to read/write data in the table.
{!isSchemaLocked && ( )}Because materialized views do not automatically enforce RLS policies from their source tables, exposing them through the API may reveal data that would otherwise be protected.
Since this materialized view is in a schema accessible to API roles, it can be queried through your project's APIs.
Foreign tables do not enforce RLS, which may allow unrestricted access. To secure them, either move foreign tables to a private schema not exposed by PostgREST, or disable PostgREST access entirely.