import { acceptUntrustedSql, safeSql, type SafeSqlFragment, type UntrustedSqlFragment, } from '@supabase/pg-meta' import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrigger, SelectValue, } from '@ui/components/shadcn/ui/select' import { LOCAL_STORAGE_KEYS, useFlag } from 'common' import { Code, ExternalLink } from 'lucide-react' import { useEffect, useRef, useState } from 'react' import { Button, DialogSectionSeparator, Sheet, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetSection, SheetTitle, SheetTrigger, } from 'ui' import { Admonition } from 'ui-patterns' import { InferredSQLViewer } from './InferredSQLViewer' import { type ParseQueryResults } from './RLSTester.types' import { RLSTesterEmptyState } from './RLSTesterEmptyState' import { RLSTesterResults } from './RLSTesterResults' import { RoleSelector } from './RoleSelector' import { SandboxManagement } from './SandboxManagement' import { UserSelector } from './UserSelector' import { UserSqlEditor } from './UserSqlEditor' import { useTestQueryRLS } from './useTestQueryRLS' import type { Policy } from '@/components/interfaces/Auth/Policies/PolicyTableRow/PolicyTableRow.utils' import { SIDEBAR_KEYS } from '@/components/layouts/ProjectLayout/LayoutSidebar/LayoutSidebarProvider' import { AiAssistantDropdown } from '@/components/ui/AiAssistantDropdown' import { FeaturePreviewBadge } from '@/components/ui/FeaturePreviewBadge' import { useTrack } from '@/lib/telemetry/track' import { useAiAssistantStateSnapshot } from '@/state/ai-assistant-state' import { PostgresSandboxProvider } from '@/state/postgres-sandbox/sandbox' import { useRoleImpersonationStateSnapshot } from '@/state/role-impersonation-state' import { useSidebarManagerSnapshot } from '@/state/sidebar-manager-state' interface RLSTesterSheetProps { handleSelectEditPolicy: (policy: Policy) => void } export const RLSTesterSheet = (props: RLSTesterSheetProps) => { return ( ) } const RLSTesterSheetContents = ({ handleSelectEditPolicy }: RLSTesterSheetProps) => { const track = useTrack() const aiSnap = useAiAssistantStateSnapshot() const { openSidebar } = useSidebarManagerSnapshot() const { setRole } = useRoleImpersonationStateSnapshot() const sandboxEnabled = useFlag('rlsTesterSandbox') const [open, setOpen] = useState(false) const [selectedOption, setSelectedOption] = useState<'anon' | 'authenticated'>('anon') const [format, setFormat] = useState<'sql' | 'lib'>('sql') const [inferredSQL, setInferredSQL] = useState() const [value, setValue] = useState(safeSql``) const [results, setResults] = useState(null) const [autoLimit, setAutoLimit] = useState(false) const [parseQueryResults, setParseQueryResults] = useState() const { testQuery, inferSQLFromLib, isLoading, isInferring, executeSqlError, parseQueryError, parseClientCodeError, } = useTestQueryRLS() const debounceRef = useRef | null>(null) const handleValueChange = (sql: SafeSqlFragment) => { setValue(sql) if (format !== 'lib') return if (debounceRef.current !== null) clearTimeout(debounceRef.current) if (!sql) return debounceRef.current = setTimeout(() => { inferSQLFromLib(sql, setInferredSQL) }, 1500) } const executionCallbacks = { option: selectedOption, onExecuteSQL: ({ result, isAutoLimit }: { result: Object[] | null; isAutoLimit: boolean }) => { setResults(result) setAutoLimit(isAutoLimit) }, onParseQuery: setParseQueryResults, } const onRunQuery = async () => { if (format === 'lib') { if (!inferredSQL) return await testQuery({ value: acceptUntrustedSql(inferredSQL), ...executionCallbacks }) track('rls_tester_run_query_clicked', { type: 'inferred' }) } else { await testQuery({ value, ...executionCallbacks }) track('rls_tester_run_query_clicked', { type: 'raw' }) } } const assistantSql = format === 'lib' && inferredSQL ? acceptUntrustedSql(inferredSQL) : value const getDebugPrompt = ({ includeSql = false }: { includeSql?: boolean } = {}) => { const prompt = `Help me fix my RLS policy based on the attached SQL snippet that gave the following error: \n\n${executeSqlError?.message}\n\nEvaluate if the problem might be query first, before checking my RLS policies.` return includeSql ? `${prompt}\n\nSQL Query:\n\`\`\`sql\n${assistantSql}\n\`\`\`` : prompt } const onDebugWithAssistant = () => { const prompt = getDebugPrompt() openSidebar(SIDEBAR_KEYS.AI_ASSISTANT) aiSnap.newChat({ name: 'Debug RLS policies', sqlSnippets: [assistantSql], initialInput: prompt, }) setOpen(false) } useEffect(() => { if (open) { setRole({ type: 'postgrest', role: 'anon' }) } else { // Flip back to service role setRole(undefined) } }, [open, setRole]) return ( What data can my users see? See what data a user is allowed to read based on your RLS policies
{sandboxEnabled && }
{selectedOption === 'authenticated' && }

Query

{ if (!isInferring && !isLoading) onRunQuery() }, }, }} />
{format === 'lib' && (
)} {parseQueryError ? (
) : parseClientCodeError ? (
) : ( executeSqlError && (
getDebugPrompt({ includeSql: true })} onOpenAssistant={onDebugWithAssistant} />, ]} />
) )} {results === null ? ( !parseQueryError && !parseClientCodeError && !executeSqlError && ) : !!parseQueryResults ? ( ) : null}
) }