import { ident, safeSql } from '@supabase/pg-meta/src/pg-format' import { useQueryClient } from '@tanstack/react-query' import { EyeOff, Lock } from 'lucide-react' import { useState } from 'react' import { toast } from 'sonner' import { Badge, Button } from 'ui' import { Admonition } from 'ui-patterns' import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal' import { InlineLink } from '@/components/ui/InlineLink' import { lintKeys } from '@/data/lint/keys' import { Lint } from '@/data/lint/lint-query' import { useExecuteSqlMutation } from '@/data/sql/execute-sql-mutation' import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject' const GRAPHQL_EXPOSURE_LINT_NAMES = [ 'pg_graphql_anon_table_exposed', 'pg_graphql_authenticated_table_exposed', ] as const export type GraphqlExposureLintName = (typeof GRAPHQL_EXPOSURE_LINT_NAMES)[number] export const asGraphqlExposureLint = ( name: string | undefined | null ): GraphqlExposureLintName | null => !!name && (GRAPHQL_EXPOSURE_LINT_NAMES as readonly string[]).includes(name) ? (name as GraphqlExposureLintName) : null interface GraphqlExposureLintCTAProps { lintName: GraphqlExposureLintName projectRef: string metadata: Lint['metadata'] onAfterAction?: () => void } const ROLE_BY_LINT: Record = { pg_graphql_anon_table_exposed: 'anon', pg_graphql_authenticated_table_exposed: 'authenticated', } const AUDIENCE: Record = { pg_graphql_anon_table_exposed: { lower: 'anonymous users', upper: 'Anonymous users' }, pg_graphql_authenticated_table_exposed: { lower: 'signed-in users', upper: 'Signed-in users' }, } const TRIGGER_LABEL: Record = { pg_graphql_anon_table_exposed: 'Remove access for anonymous users', pg_graphql_authenticated_table_exposed: 'Remove access for signed-in users', } export const GraphqlExposureLintCTA = ({ lintName, projectRef, metadata, onAfterAction, }: GraphqlExposureLintCTAProps) => { const { data: project } = useSelectedProjectQuery() const queryClient = useQueryClient() const [showConfirmRevoke, setShowConfirmRevoke] = useState(false) const schema = metadata?.schema const name = metadata?.name const objectType = metadata?.type ?? 'object' const role = ROLE_BY_LINT[lintName] const audience = AUDIENCE[lintName] const canAct = !!schema && !!name const revokeSql = schema && name ? safeSql`revoke all on ${ident(schema)}.${ident(name)} from ${ident(role)};` : undefined const { mutate: executeSql, isPending: isRevoking } = useExecuteSqlMutation({ onSuccess: async () => { toast.success( `Revoked access to ${schema}.${name} from ${role}. ${audience.upper} can no longer query this ${objectType} via GraphQL or Data API.` ) setShowConfirmRevoke(false) await queryClient.invalidateQueries({ queryKey: lintKeys.lint(projectRef) }) onAfterAction?.() }, onError: (error) => { toast.error(`Failed to revoke access: ${error.message}`) }, }) const handleRevoke = () => { if (!revokeSql) return executeSql({ projectRef, connectionString: project?.connectionString, sql: revokeSql, }) } return ( <> setShowConfirmRevoke(false)} onConfirm={handleRevoke} >

This change affects both schema visibility and data access for {audience.lower}.

Alternatively, you can{' '} disable GraphQL {' '} to remove schema visibility.

Data API access removed

Breaking change

{audience.upper} will no longer be able to read or write to this {objectType} via Briven APIs (GraphQL or Data API), even if RLS policies allow it.

Schema hidden from GraphQL

This {objectType} will no longer appear in the GraphQL schema. {audience.upper}{' '} won't be able to discover its name, columns, or relationships.

The following statement will be executed:

          {revokeSql}
        
) } export const GraphqlExposureCallout = ({ projectRef }: { projectRef: string }) => { return ( These warnings are triggered by GraphQL exposing your table schemas. If you're not using GraphQL, disable it from the{' '} Database extensions page .

} /> ) }