RLSTesterResults.tsx168 lines · main
1import {
2 Badge,
3 cn,
4 Tabs_Shadcn_,
5 TabsContent_Shadcn_,
6 TabsList_Shadcn_,
7 TabsTrigger_Shadcn_,
8} from 'ui'
9import { Admonition } from 'ui-patterns'
10
11import { Results } from '../../SQLEditor/UtilityPanel/Results'
12import { RLSTableCard } from './RLSTableCard'
13import { ParseQueryResults } from './RLSTester.types'
14import { deriveRLSTestState } from './RLSTesterResults.utils'
15import { useTestQueryRLS } from './useTestQueryRLS'
16import type { Policy } from '@/components/interfaces/Auth/Policies/PolicyTableRow/PolicyTableRow.utils'
17
18interface RLSTesterResultsProps {
19 results: Object[]
20 autoLimit: boolean
21 parseQueryResults: ParseQueryResults
22 handleSelectEditPolicy: (policy: Policy) => void
23}
24
25export const RLSTesterResults = ({
26 results,
27 autoLimit,
28 parseQueryResults,
29 handleSelectEditPolicy,
30}: RLSTesterResultsProps) => {
31 const { limit } = useTestQueryRLS()
32
33 const {
34 isServiceRole,
35 tableWithRLSEnabledButNoPolicies,
36 tableWithRLSEnabledWithPolicyFalse,
37 noAccessToData,
38 } = deriveRLSTestState(parseQueryResults)
39
40 return (
41 <div className="p-5 pt-4">
42 <div className="flex items-center gap-x-2 mb-2">
43 <p className="text-sm">Summary</p>
44 {noAccessToData ? (
45 <Badge variant="destructive">No access</Badge>
46 ) : (
47 <Badge variant="success">{results.length > 0 ? 'Can access' : 'Has access'}</Badge>
48 )}
49 </div>
50
51 <Tabs_Shadcn_ defaultValue="policies">
52 <TabsList_Shadcn_ className="gap-x-3">
53 <TabsTrigger_Shadcn_ value="policies" className="px-2">
54 Policies applied
55 </TabsTrigger_Shadcn_>
56 <TabsTrigger_Shadcn_ value="data" className="px-2">
57 Data preview
58 </TabsTrigger_Shadcn_>
59 </TabsList_Shadcn_>
60
61 {!!parseQueryResults && (
62 <div className="border rounded-sm flex items-center justify-between px-3 py-1.5 mt-3">
63 <div className="flex items-center gap-x-2">
64 <p className="text-xs text-foreground-light">Ran as</p>
65 {!parseQueryResults.role ? (
66 <code className="text-code-inline">postgres</code>
67 ) : parseQueryResults.user ? (
68 <p className="text-sm truncate max-w-52">{parseQueryResults.user.email}</p>
69 ) : parseQueryResults.role === 'anon' ? (
70 <p className="text-xs">an Anonymous user</p>
71 ) : null}
72 </div>
73
74 {parseQueryResults.role === 'anon' && (
75 <p className="text-foreground-light text-xs">Not logged in user</p>
76 )}
77 {!!parseQueryResults.user && (
78 <code className="text-code-inline">ID: {parseQueryResults.user.id}</code>
79 )}
80 </div>
81 )}
82
83 <TabsContent_Shadcn_ value="policies" className="mt-0">
84 {!isServiceRole &&
85 (!!tableWithRLSEnabledButNoPolicies ? (
86 <Admonition showIcon={false} type="default" className="rounded-sm mt-2">
87 <p className="mb-0.5!">This user has no access to any rows from this query</p>
88 <p className="text-foreground-light">
89 The table{' '}
90 <code className="text-code-inline">
91 {tableWithRLSEnabledButNoPolicies.schema}.
92 {tableWithRLSEnabledButNoPolicies.table}
93 </code>{' '}
94 has RLS enabled but no policies set up for the{' '}
95 <code className="text-code-inline break-keep!">{parseQueryResults.role}</code>{' '}
96 role.
97 </p>
98 </Admonition>
99 ) : tableWithRLSEnabledWithPolicyFalse ? (
100 <Admonition showIcon={false} type="default" className="rounded-sm mt-2">
101 <p className="mb-0.5!">This user has no access to any rows from this query</p>
102 <p className="text-foreground-light">
103 The table{' '}
104 <code className="text-code-inline">
105 {tableWithRLSEnabledWithPolicyFalse.schema}.
106 {tableWithRLSEnabledWithPolicyFalse.table}
107 </code>{' '}
108 has a policy that evaluates to
109 <code className="text-code-inline break-keep!">false</code> for the{' '}
110 <code className="text-code-inline break-keep!">{parseQueryResults.role}</code>{' '}
111 role.
112 </p>
113 </Admonition>
114 ) : null)}
115
116 {isServiceRole && (
117 <Admonition showIcon={false} type="default" className="rounded-sm mt-2">
118 <p className="mb-0.5!">
119 The <code className="text-code-inline">postgres</code> role has access to all rows
120 for this query
121 </p>
122 <p className="text-foreground-light">
123 The <code className="text-code-inline">postgres</code> role has admin privileges and
124 bypasses all RLS policies.
125 </p>
126 </Admonition>
127 )}
128
129 <div className="flex flex-col gap-y-2 mt-4">
130 <p className="text-sm">Table access</p>
131 {!isServiceRole && (
132 <div className="flex flex-col gap-y-2">
133 {parseQueryResults?.tables.map((x) => {
134 const { schema, table, tablePolicies, isRLSEnabled } = x
135 return (
136 <RLSTableCard
137 key={`${schema}.${table}`}
138 table={{ schema, name: table, isRLSEnabled }}
139 role={parseQueryResults.role}
140 policies={tablePolicies}
141 handleSelectEditPolicy={handleSelectEditPolicy}
142 />
143 )
144 })}
145 </div>
146 )}
147 </div>
148 </TabsContent_Shadcn_>
149 <TabsContent_Shadcn_ value="data" className="mt-2">
150 <div
151 className={cn(
152 'grow flex flex-col border overflow-hidden',
153 results.length === 0 ? 'rounded-sm h-32' : 'rounded-t h-56'
154 )}
155 >
156 <Results rows={results} />
157 </div>
158 {results.length > 0 && (
159 <p className="border border-t-0 rounded-b font-mono text-xs text-foreground-light p-2">
160 {results.length} row{results.length > 1 ? 's' : ''}
161 {autoLimit && results.length >= limit && ` (Limited to only ${limit} rows)`}
162 </p>
163 )}
164 </TabsContent_Shadcn_>
165 </Tabs_Shadcn_>
166 </div>
167 )
168}