PolicyTableRow.utils.ts88 lines · main
| 1 | import { ident, joinSqlFragments, safeSql, type SafeSqlFragment } from '@supabase/pg-meta' |
| 2 | import type { PGPolicy } from '@supabase/pg-meta' |
| 3 | |
| 4 | import type { TableApiAccessData } from '@/data/privileges/table-api-access-query' |
| 5 | |
| 6 | export type Policy = Omit<PGPolicy, 'definition' | 'check'> & { |
| 7 | definition: SafeSqlFragment | null |
| 8 | check: SafeSqlFragment | null |
| 9 | } |
| 10 | |
| 11 | /** |
| 12 | * Single classifier for the RLS page's per-table admonition state. Shares the |
| 13 | * "granted / custom / revoked" grant semantics used by the Data API settings |
| 14 | * page's ExposedTableSelector so the two views agree on what counts as exposed. |
| 15 | */ |
| 16 | export type TableDataApiStatus = |
| 17 | | 'schema-not-exposed' // schema isn't in the PostgREST exposed list |
| 18 | | 'no-grants' // schema exposed, no API roles have any privileges (revoked) |
| 19 | | 'custom-grants' // schema exposed, partial / non-standard grants |
| 20 | | 'publicly-readable' // fully granted + RLS disabled (dangerous) |
| 21 | | 'locked-by-rls' // fully granted + RLS enabled, no policies |
| 22 | | 'secured' // fully granted + RLS enabled, policies exist |
| 23 | | 'unknown' // privileges query is still loading or errored — caller should stay silent |
| 24 | |
| 25 | export function getTableDataApiStatus({ |
| 26 | isSchemaExposed, |
| 27 | apiAccessData, |
| 28 | isRLSEnabled, |
| 29 | policiesCount, |
| 30 | }: { |
| 31 | isSchemaExposed: boolean |
| 32 | apiAccessData: TableApiAccessData | undefined |
| 33 | isRLSEnabled: boolean |
| 34 | policiesCount: number |
| 35 | }): TableDataApiStatus { |
| 36 | if (!isSchemaExposed) return 'schema-not-exposed' |
| 37 | if (apiAccessData?.apiAccessType === 'exposed-schema-no-grants') return 'no-grants' |
| 38 | if (apiAccessData?.apiAccessType === 'access') { |
| 39 | if (apiAccessData.grantStatus === 'custom') return 'custom-grants' |
| 40 | if (!isRLSEnabled) return 'publicly-readable' |
| 41 | if (policiesCount === 0) return 'locked-by-rls' |
| 42 | return 'secured' |
| 43 | } |
| 44 | // Schema is exposed but the privileges query hasn't resolved (still loading |
| 45 | // or errored). We return 'unknown' rather than 'schema-not-exposed' so the |
| 46 | // caller doesn't falsely tell the user to reconfigure API settings. |
| 47 | return 'unknown' |
| 48 | } |
| 49 | |
| 50 | /** |
| 51 | * Returns the copy for the in-row admonition, or null when the row needs no |
| 52 | * admonition (the "everything is fine" `secured` case and the orthogonal |
| 53 | * `schema-not-exposed` case which is rendered separately with a link). |
| 54 | */ |
| 55 | export function getTableAdmonitionMessage(status: TableDataApiStatus): string | null { |
| 56 | switch (status) { |
| 57 | case 'custom-grants': |
| 58 | return 'This table has custom Data API permissions — access may be restricted for some roles or operations.' |
| 59 | case 'no-grants': |
| 60 | return 'This table cannot be accessed via the Data API. Enable access in your project’s Data API settings.' |
| 61 | case 'publicly-readable': |
| 62 | return 'This table can be accessed by anyone via the Data API as RLS is disabled.' |
| 63 | case 'locked-by-rls': |
| 64 | return 'No data will be returned via the Data API as no RLS policies exist on this table.' |
| 65 | default: |
| 66 | return null |
| 67 | } |
| 68 | } |
| 69 | |
| 70 | export const generatePolicyUpdateSQL = (policy: Policy): SafeSqlFragment => { |
| 71 | const parts: Array<SafeSqlFragment> = [] |
| 72 | |
| 73 | if (policy.definition != null) { |
| 74 | const semicolon = policy.check == null ? safeSql`;` : safeSql`` |
| 75 | parts.push(safeSql`using (${policy.definition})${semicolon}`) |
| 76 | } |
| 77 | if (policy.check != null) { |
| 78 | parts.push(safeSql`with check (${policy.check});`) |
| 79 | } |
| 80 | |
| 81 | const expression = parts.length > 0 ? joinSqlFragments(parts, '\n') : safeSql`` |
| 82 | |
| 83 | return safeSql` |
| 84 | alter policy ${ident(policy.name)} |
| 85 | on ${ident(policy.schema)}.${ident(policy.table)} |
| 86 | to ${joinSqlFragments(policy.roles.map(ident), ', ')} |
| 87 | ${expression}` |
| 88 | } |