RLSTesterResults.tsx 6.4 KB

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