This commit is contained in:
Joshen Lim committed 2026-04-22 23:30:49 +08:00
1 parent 2a63ea8081
commit 0e9012c04f
3 files changed
+106 -46

No files matched your search

@@ -8,9 +8,9 @@ import {
CollapsibleTrigger_Shadcn_,
WarningIcon,
} from 'ui'
import { Admonition } from 'ui-patterns'
import { ButtonTooltip } from '@/components/ui/ButtonTooltip'
import { useRoleImpersonationStateSnapshot } from '@/state/role-impersonation-state'
interface RLSTableCardProps {
table: { schema: string; name: string; isRLSEnabled: boolean }
@@ -53,9 +53,10 @@ export const RLSTableCard = ({
<>
{!!trueOnlyPolicy ? (
<p>
A policy exists for the <code className="text-code-inline">{role}</code> role on this
table that evaluates to <code className="text-code-inline">true</code>, so all data is
accessible to the role.
The policy "{trueOnlyPolicy.name}" for the{' '}
<code className="text-code-inline">{role}</code> role on this table that evaluates to{' '}
<code className="text-code-inline">true</code>, so all data from this query is
accessible to this user.
</p>
) : (
<p>
@@ -65,31 +66,33 @@ export const RLSTableCard = ({
</p>
)}
<p className="text-xs font-mono text-foreground-light uppercase mt-4 mb-2">
Evaluated policies
</p>
<ul className="border rounded">
{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 className="border rounded 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 className="">
{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>
</>
)
}, [isRLSEnabled, noPolicies, trueOnlyPolicy, role, policies, handleSelectEditPolicy])
@@ -97,7 +100,7 @@ export const RLSTableCard = ({
return (
<Collapsible_Shadcn_
className={cn('border rounded', !isRLSEnabled && 'bg-warning-300 border-warning-500')}
defaultOpen={!isRLSEnabled || noPolicies}
// defaultOpen={!isRLSEnabled || noPolicies}
>
<CollapsibleTrigger_Shadcn_ 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">
@@ -35,6 +35,7 @@ import { Results } from '../../SQLEditor/UtilityPanel/Results'
import { RLSTableCard } from './RLSTableCard'
import { CodeEditor } from '@/components/ui/CodeEditor/CodeEditor'
import { useParseClientCodeMutation } from '@/data/ai/parse-client-code-mutation'
import type { User } from '@/data/auth/users-infinite-query'
import { useDatabasePoliciesQuery } from '@/data/database-policies/database-policies-query'
import { useCheckTableRLSStatusMutation } from '@/data/database/table-check-rls-mutation'
import {
@@ -61,6 +62,8 @@ type ParseQueryResults = {
}[]
operation: ParseSQLQueryResponse['operation']
role?: string
user?: User
externalAuth?: string
}
interface RLSTesterSheetProps {
@@ -105,7 +108,7 @@ export const RLSTesterSheet = ({ handleSelectEditPolicy }: RLSTesterSheetProps)
const { mutateAsync: getTableRLSStatus } = useCheckTableRLSStatusMutation()
const isServiceRole = parseQueryResults?.role === undefined
const hasRLSEnabledButNoPolicies = parseQueryResults?.tables.some(
const tableWithRLSEnabledButNoPolicies = parseQueryResults?.tables.find(
(x) => x.isRLSEnabled && x.tablePolicies.length === 0
)
@@ -174,7 +177,26 @@ export const RLSTesterSheet = ({ handleSelectEditPolicy }: RLSTesterSheetProps)
queryKey: ['rls-tester'],
})
setParseQueryResults({ tables, operation: data.operation, role: role?.role })
const user =
impersonatedRoleState.role?.type === 'postgrest' &&
impersonatedRoleState.role.role === 'authenticated' &&
impersonatedRoleState.role.userType === 'native'
? impersonatedRoleState.role.user
: undefined
const externalAuth =
impersonatedRoleState.role?.type === 'postgrest' &&
impersonatedRoleState.role.role === 'authenticated' &&
impersonatedRoleState.role.userType === 'external' &&
impersonatedRoleState.role.externalAuth
? impersonatedRoleState.role.externalAuth.sub
: undefined
setParseQueryResults({
tables,
operation: data.operation,
role: role?.role,
user,
externalAuth,
})
} else {
toast('Only SELECT statements are supported for now')
}
@@ -316,32 +338,67 @@ export const RLSTesterSheet = ({ handleSelectEditPolicy }: RLSTesterSheetProps)
<>
<div className="p-5 pt-4">
<p className="mb-2 text-sm font-mono uppercase tracking-tight text-foreground-light">
Table access summary
Summary
</p>
{!isServiceRole && hasRLSEnabledButNoPolicies && (
<Admonition showIcon={false} className="mb-4" type="default">
{!!parseQueryResults && (
<div className="border rounded flex items-center justify-between px-3 py-2 mb-4">
<div className="flex items-center gap-x-2">
<p className="text-xs text-foreground-light">Ran as</p>
{!parseQueryResults.role ? (
<code className="text-code-inline">postgres</code>
) : parseQueryResults.user ? (
<p className="text-sm truncate max-w-52">{parseQueryResults.user.email}</p>
) : parseQueryResults.externalAuth ? (
<p className="text-sm truncate max-w-52">
{parseQueryResults.externalAuth}
</p>
) : (
<code className="text-code-inline">{parseQueryResults.role}</code>
)}
</div>
{parseQueryResults.role === 'anon' && (
<p className="text-foreground-light text-xs">Not logged in user</p>
)}
{!!parseQueryResults.user && (
<code className="text-code-inline">ID: {parseQueryResults.user.id}</code>
)}
</div>
)}
{!isServiceRole && !!tableWithRLSEnabledButNoPolicies && (
<Admonition showIcon={false} type="default" className="mb-4">
<p className="!mb-0.5">
Query returns no rows for the{' '}
<code className="text-code-inline">{parseQueryResults.role}</code> role
</p>
<p className="text-foreground-light">
One of the tables has RLS enabled, but no policies set up for this role
The table{' '}
<code className="text-code-inline">
{tableWithRLSEnabledButNoPolicies.schema}.
{tableWithRLSEnabledButNoPolicies.table}
</code>{' '}
has RLS enabled but no policies set up for this role.
</p>
</Admonition>
)}
{isServiceRole ? (
<Admonition
showIcon={false}
type="default"
title="Query returns all rows for the Postgres role"
description="Role has admin privileges and bypasses all RLS policies, all rows will be returned"
/>
<Admonition showIcon={false} type="default" className="mb-4">
<p className="!mb-0.5">
Query returns all rows for the{' '}
<code className="text-code-inline">postgres</code> role
</p>
<p className="text-foreground-light">
The role has admin privileges and bypasses all RLS policies.
</p>
</Admonition>
) : (
<>
{/* <p className="text-xs font-mono uppercase tracking-tight text-foreground-light mb-2">
Table access
</p> */}
<p className="text-sm font-mono uppercase tracking-tight text-foreground-light mb-2">
Policies applied
</p>
<div className="flex flex-col gap-y-2">
{parseQueryResults?.tables.map((x) => {
const { schema, table, tablePolicies, isRLSEnabled } = x
@@ -450,7 +450,7 @@ export const EdgeFunctionTesterSheet = ({ visible, onClose }: EdgeFunctionTester
<RoleImpersonationStateContextProvider
key={`role-impersonation-state-${projectRef}`}
>
<RoleImpersonationPopover />
<RoleImpersonationPopover disallowAuthenticatedOption />
</RoleImpersonationStateContextProvider>
<Button
type="primary"