mirror of
https://github.com/supabase/supabase.git
synced 2026-10-13 21:29:08 +03:00
Fixes the RLS policies page showing self-contradictory or wrong
admonitions for tables with partial grants. Classifies each table using
the same `granted / custom / revoked` semantics used by the Data API
settings page so the two views agree on what counts as "exposed".
**Changed:**
- `PolicyTableRow` now uses `useTableApiAccessQuery` (shared cache with
the Table Editor sidebar) instead of a bespoke
`tables-roles-access-query`
- Boolean soup collapsed into a single `TableDataApiStatus`
discriminated union (`schema-not-exposed | no-grants | custom-grants |
publicly-readable | locked-by-rls | secured`) via a pure helper
- Admonition copy for `no-grants` and `locked-by-rls` updated; a table
with no policies but full grants now reads "No data will be returned via
the Data API as no RLS policies exist on this table." instead of the
earlier self-contradictory "can be accessed but no data will be
returned"
- `table-api-access-query.ts` now exposes a `grantStatus: 'granted' |
'custom'` on `access` entries — `granted` = all 3 API roles × all 4 CRUD
privileges (matches `getTableGrantsCTEs` in pg-meta)
**Added:**
- New `custom-grants` admonition: "This table has custom Data API
permissions — access may be restricted for some roles or operations."
- Unit tests for `getTableDataApiStatus`, `getTableAdmonitionMessage`,
and `isFullyGranted`
**Removed:**
- `data/tables/tables-roles-access-query.ts` and the `rolesAccess` key —
no more callers
## To test
On a project with the `public` schema exposed, for each scenario check
the admonition shown on `/project/{ref}/auth/policies`:
1. Table with full standard grants, RLS on, no policies → "No data will
be returned via the Data API as no RLS policies exist on this table."
2. Table with full standard grants, RLS off → yellow warning "can be
accessed by anyone"
3. Table with partial grants (e.g. only `GRANT SELECT ON t TO anon`) →
new "custom Data API permissions" admonition regardless of RLS state
4. Table with no anon/authenticated/service_role grants → "cannot be
accessed via the Data API"
5. Schema not in the exposed list → "schema not exposed" admonition with
link
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Tests**
* Added unit tests covering table Data API/RLS status classification and
API grant validation.
* **Refactor**
* Introduced a unified per-table API/RLS status model and reusable
utilities to derive display status and admonitions.
* Simplified UI logic to drive access indicators and warnings from the
new status.
* **Chores**
* Removed legacy role-based access query and its related keying logic.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: Alaister Young <10985857+alaister@users.noreply.github.com>
82 lines
3.2 KiB
TypeScript
82 lines
3.2 KiB
TypeScript
import { PostgresPolicy } from '@supabase/postgres-meta'
|
||
|
||
import type { TableApiAccessData } from '@/data/privileges/table-api-access-query'
|
||
|
||
/**
|
||
* Single classifier for the RLS page's per-table admonition state. Shares the
|
||
* "granted / custom / revoked" grant semantics used by the Data API settings
|
||
* page's ExposedTableSelector so the two views agree on what counts as exposed.
|
||
*/
|
||
export type TableDataApiStatus =
|
||
| 'schema-not-exposed' // schema isn't in the PostgREST exposed list
|
||
| 'no-grants' // schema exposed, no API roles have any privileges (revoked)
|
||
| 'custom-grants' // schema exposed, partial / non-standard grants
|
||
| 'publicly-readable' // fully granted + RLS disabled (dangerous)
|
||
| 'locked-by-rls' // fully granted + RLS enabled, no policies
|
||
| 'secured' // fully granted + RLS enabled, policies exist
|
||
| 'unknown' // privileges query is still loading or errored — caller should stay silent
|
||
|
||
export function getTableDataApiStatus({
|
||
isSchemaExposed,
|
||
apiAccessData,
|
||
isRLSEnabled,
|
||
policiesCount,
|
||
}: {
|
||
isSchemaExposed: boolean
|
||
apiAccessData: TableApiAccessData | undefined
|
||
isRLSEnabled: boolean
|
||
policiesCount: number
|
||
}): TableDataApiStatus {
|
||
if (!isSchemaExposed) return 'schema-not-exposed'
|
||
if (apiAccessData?.apiAccessType === 'exposed-schema-no-grants') return 'no-grants'
|
||
if (apiAccessData?.apiAccessType === 'access') {
|
||
if (apiAccessData.grantStatus === 'custom') return 'custom-grants'
|
||
if (!isRLSEnabled) return 'publicly-readable'
|
||
if (policiesCount === 0) return 'locked-by-rls'
|
||
return 'secured'
|
||
}
|
||
// Schema is exposed but the privileges query hasn't resolved (still loading
|
||
// or errored). We return 'unknown' rather than 'schema-not-exposed' so the
|
||
// caller doesn't falsely tell the user to reconfigure API settings.
|
||
return 'unknown'
|
||
}
|
||
|
||
/**
|
||
* Returns the copy for the in-row admonition, or null when the row needs no
|
||
* admonition (the "everything is fine" `secured` case and the orthogonal
|
||
* `schema-not-exposed` case which is rendered separately with a link).
|
||
*/
|
||
export function getTableAdmonitionMessage(status: TableDataApiStatus): string | null {
|
||
switch (status) {
|
||
case 'custom-grants':
|
||
return 'This table has custom Data API permissions — access may be restricted for some roles or operations.'
|
||
case 'no-grants':
|
||
return 'This table cannot be accessed via the Data API. Enable access in your project’s Data API settings.'
|
||
case 'publicly-readable':
|
||
return 'This table can be accessed by anyone via the Data API as RLS is disabled.'
|
||
case 'locked-by-rls':
|
||
return 'No data will be returned via the Data API as no RLS policies exist on this table.'
|
||
default:
|
||
return null
|
||
}
|
||
}
|
||
|
||
export const generatePolicyUpdateSQL = (policy: PostgresPolicy) => {
|
||
let expression = ''
|
||
if (policy.definition !== null && policy.definition !== undefined) {
|
||
expression += `using (${policy.definition})${
|
||
policy.check === null || policy.check === undefined ? ';' : ''
|
||
}\n`
|
||
}
|
||
if (policy.check !== null && policy.check !== undefined) {
|
||
expression += `with check (${policy.check});\n`
|
||
}
|
||
|
||
return `
|
||
alter policy "${policy.name}"
|
||
on "${policy.schema}"."${policy.table}"
|
||
to ${policy.roles.join(', ')}
|
||
${expression}
|
||
`.trim()
|
||
}
|