mirror of
https://github.com/supabase/supabase.git
synced 2026-10-10 20:05:06 +03:00
## I have read the [CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md) file. YES ## What kind of change does this PR introduce? Refactor / security improvement ## What is the current behavior? SQL fragments across Studio are built from plain `string` values with no type-level distinction between developer-authored SQL, DB-sourced identifiers, and user-typed or externally-influenced content. ## What is the new behavior? Extends the safe SQL model to additional Studio interfaces, using `SafeSqlFragment`, `safeSql`, `ident()`, `literal()`, `untrustedSql()`, and `acceptUntrustedSql()` from `@supabase/pg-meta/src/pg-format`: - **Policy editor**: template constants typed as `SafeSqlFragment` via `safeSql` tagged literals; Monaco editor `onInputChange` emits `untrustedSql()`; `acceptUntrustedSql()` called only at the Save gesture; roles selector emits a composed `SafeSqlFragment` via `ident()` + `joinSqlFragments()` - **Auth hooks**: grant/revoke SQL statements use `ident()` for schema and function names - **Docs description editor**: `COMMENT ON` queries use `ident()` and `literal()` for table/column/function names and values - **Cron jobs**: `cron.schedule()` call and HTTP request builder use `literal()` for all user-provided values - **GraphQL linter CTA**: `REVOKE` statement uses `ident()` for schema, table, and role - **Storage public bucket warning**: `DROP POLICY` uses `ident()` for policy name - **View security autofix modal**: `ALTER VIEW` uses `ident()` for schema and view name - **API settings**: `CREATE SCHEMA` mutation uses `safeSql` tagged literal - **Database event trigger delete**: `DROP EVENT TRIGGER` uses `ident()` for trigger name - **Database queues query**: queue list query uses `safeSql` tagged literal - **Role impersonation**: function invocation SQL uses `ident()` and `literal()` ## Manual testing checklist - Authentication > Policies - Authentication > Hooks - Integrations > Queues - Database > Event Triggers - Integrations > Cron Jobs - Table Editor > View entity security autofix - API Settings > expose schema - Linter > GraphQL exposure CTA - Docs > table/column description editor - Role impersonation (user impersonation panel) ## Additional context <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Refactor** * Replaced ad-hoc SQL string building with a safer, fragment-based SQL construction across auth, policies, integrations, storage, and DB operations to improve SQL safety while preserving behavior. * **Bug Fixes / UX** * Policy editor and code editor now propagate role and input changes more reliably, improving editor responsiveness and policy handling without UI changes. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
94 lines
3.0 KiB
TypeScript
94 lines
3.0 KiB
TypeScript
import { useMemo, useState } from 'react'
|
|
import { GenericSkeletonLoader } from 'ui-patterns/ShimmeringLoader'
|
|
|
|
import { Policies } from '@/components/interfaces/Auth/Policies/Policies'
|
|
import { PoliciesDataProvider } from '@/components/interfaces/Auth/Policies/PoliciesDataContext'
|
|
import { PolicyEditorPanel } from '@/components/interfaces/Auth/Policies/PolicyEditorPanel'
|
|
import type { Policy } from '@/components/interfaces/Auth/Policies/PolicyTableRow/PolicyTableRow.utils'
|
|
import AlertError from '@/components/ui/AlertError'
|
|
import { useDatabasePoliciesQuery } from '@/data/database-policies/database-policies-query'
|
|
import { useTablesQuery } from '@/data/tables/tables-query'
|
|
import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
|
|
|
|
export const RealtimePolicies = () => {
|
|
const { data: project } = useSelectedProjectQuery()
|
|
|
|
const [showPolicyEditor, setShowPolicyEditor] = useState(false)
|
|
const [selectedPolicyToEdit, setSelectedPolicyToEdit] = useState<Policy>()
|
|
|
|
const {
|
|
data: tables,
|
|
isPending: isLoading,
|
|
isSuccess,
|
|
isError,
|
|
error,
|
|
} = useTablesQuery({
|
|
projectRef: project?.ref,
|
|
connectionString: project?.connectionString,
|
|
schema: 'realtime',
|
|
})
|
|
|
|
const filteredTables = useMemo(
|
|
() => (tables ?? []).filter((table) => table.name === 'messages'),
|
|
[tables]
|
|
)
|
|
const visibleTableIds = useMemo(
|
|
() => new Set(filteredTables.map((table) => table.id)),
|
|
[filteredTables]
|
|
)
|
|
const {
|
|
data: policies,
|
|
isPending: isLoadingPolicies,
|
|
isError: isPoliciesError,
|
|
error: policiesError,
|
|
} = useDatabasePoliciesQuery({
|
|
projectRef: project?.ref,
|
|
connectionString: project?.connectionString,
|
|
})
|
|
// realtime is never in PostgREST's db_schema — skip the config query to avoid a false warning
|
|
const exposedSchemas = useMemo(() => ['realtime'], [])
|
|
|
|
return (
|
|
<>
|
|
{isLoading && <GenericSkeletonLoader />}
|
|
|
|
{isError && <AlertError error={error} subject="Failed to retrieve tables" />}
|
|
|
|
{isSuccess && (
|
|
<PoliciesDataProvider
|
|
policies={policies ?? []}
|
|
isPoliciesLoading={isLoadingPolicies}
|
|
isPoliciesError={isPoliciesError}
|
|
policiesError={policiesError ?? undefined}
|
|
exposedSchemas={exposedSchemas}
|
|
>
|
|
<Policies
|
|
schema="realtime"
|
|
tables={filteredTables}
|
|
hasTables
|
|
isLocked={false}
|
|
visibleTableIds={visibleTableIds}
|
|
onSelectCreatePolicy={(_tableName) => {
|
|
setSelectedPolicyToEdit(undefined)
|
|
setShowPolicyEditor(true)
|
|
}}
|
|
onSelectEditPolicy={(policy) => {
|
|
setSelectedPolicyToEdit(policy)
|
|
setShowPolicyEditor(true)
|
|
}}
|
|
/>
|
|
</PoliciesDataProvider>
|
|
)}
|
|
|
|
<PolicyEditorPanel
|
|
visible={showPolicyEditor}
|
|
searchString="messages"
|
|
schema="realtime"
|
|
selectedPolicy={selectedPolicyToEdit}
|
|
onSelectCancel={() => setShowPolicyEditor(false)}
|
|
authContext="realtime"
|
|
/>
|
|
</>
|
|
)
|
|
}
|