mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 17:35:10 +03:00
[SEC-385] feat(studio): Add default privileges for new entities toggle (#43583)
Adds a new toggle in: <img width="1161" height="356" alt="Screenshot 2026-03-10 at 17 17 06" src="https://github.com/user-attachments/assets/b09ac1aa-a8f5-4fb4-8771-f113b140eac8" /> Other changes: - form submissions with no table/function changes were failing because an empty string got passed to executeSql. Added an early return when there's nothing to execute To test: - Ensure the form is still in working order - Create some tables and functions with the toggle on add off and make sure your selected default applies
This commit is contained in:
1 parent
d6d419a12e
commit
9652e8c4d3
9 files changed
+307
-23
No files matched your search
@@ -1,6 +1,6 @@
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import { PermissionAction } from '@supabase/shared-types/out/constants'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import { useQuery, useQueryClient } from '@tanstack/react-query'
|
||||
import { useParams } from 'common'
|
||||
import { Lock } from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
Input_Shadcn_,
|
||||
PrePostTab,
|
||||
Skeleton,
|
||||
Switch,
|
||||
useWatch_Shadcn_,
|
||||
} from 'ui'
|
||||
import { GenericSkeletonLoader, PageSection, PageSectionContent } from 'ui-patterns'
|
||||
@@ -42,7 +43,9 @@ import { useProjectPostgrestConfigQuery } from '@/data/config/project-postgrest-
|
||||
import { useProjectPostgrestConfigUpdateMutation } from '@/data/config/project-postgrest-config-update-mutation'
|
||||
import { useDatabaseExtensionsQuery } from '@/data/database-extensions/database-extensions-query'
|
||||
import { useSchemasQuery } from '@/data/database/schemas-query'
|
||||
import { defaultPrivilegesQueryOptions } from '@/data/privileges/default-privileges-query'
|
||||
import { privilegeKeys } from '@/data/privileges/keys'
|
||||
import { useUpdateDefaultPrivilegesMutation } from '@/data/privileges/update-default-privileges-mutation'
|
||||
import { useUpdateExposedEntitiesMutation } from '@/data/privileges/update-exposed-entities-mutation'
|
||||
import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions'
|
||||
import { useDataApiGrantTogglesEnabled } from '@/hooks/misc/useDataApiGrantTogglesEnabled'
|
||||
@@ -64,6 +67,9 @@ const formSchema = z.object({
|
||||
.optional()
|
||||
.nullable(),
|
||||
|
||||
// Default privileges toggle
|
||||
defaultPrivilegesGranted: z.boolean(),
|
||||
|
||||
// Fields for expose toggles
|
||||
tableIdsToAdd: z.array(z.number()),
|
||||
tableIdsToRemove: z.array(z.number()),
|
||||
@@ -98,12 +104,23 @@ export const PostgrestConfig = () => {
|
||||
connectionString: project?.connectionString,
|
||||
})
|
||||
|
||||
const {
|
||||
data: defaultPrivilegesGranted,
|
||||
isPending: isLoadingDefaultPrivileges,
|
||||
isSuccess: isSuccessDefaultPrivileges,
|
||||
} = useQuery(
|
||||
defaultPrivilegesQueryOptions({
|
||||
projectRef: project?.ref,
|
||||
connectionString: project?.connectionString,
|
||||
})
|
||||
)
|
||||
|
||||
const configDbSchemas = useMemo(
|
||||
() => (config?.db_schema ? config.db_schema.split(',').map((x) => x.trim()) : []),
|
||||
[config?.db_schema]
|
||||
)
|
||||
|
||||
const isLoading = isLoadingConfig || isLoadingSchemas
|
||||
const isLoading = isLoadingConfig || isLoadingSchemas || isLoadingDefaultPrivileges
|
||||
|
||||
const schemas = useMemo(
|
||||
() =>
|
||||
@@ -120,9 +137,13 @@ export const PostgrestConfig = () => {
|
||||
[allSchemas]
|
||||
)
|
||||
|
||||
const { mutateAsync: updatePostgrestConfig } = useProjectPostgrestConfigUpdateMutation()
|
||||
|
||||
const { mutateAsync: updateExposedEntities } = useUpdateExposedEntitiesMutation()
|
||||
const { mutateAsync: updatePostgrestConfig } = useProjectPostgrestConfigUpdateMutation({
|
||||
onError: noop,
|
||||
})
|
||||
const { mutateAsync: updateExposedEntities } = useUpdateExposedEntitiesMutation({ onError: noop })
|
||||
const { mutateAsync: updateDefaultPrivileges } = useUpdateDefaultPrivilegesMutation({
|
||||
onError: noop,
|
||||
})
|
||||
|
||||
const [isUpdating, setIsUpdating] = useState(false)
|
||||
|
||||
@@ -144,12 +165,13 @@ export const PostgrestConfig = () => {
|
||||
.map((x) => x.trim())
|
||||
.filter(Boolean),
|
||||
dbPool: config?.db_pool,
|
||||
defaultPrivilegesGranted: defaultPrivilegesGranted ?? true,
|
||||
tableIdsToAdd: [] as number[],
|
||||
tableIdsToRemove: [] as number[],
|
||||
functionNamesToAdd: [] as string[],
|
||||
functionNamesToRemove: [] as string[],
|
||||
}
|
||||
}, [config, configDbSchemas])
|
||||
}, [config, configDbSchemas, defaultPrivilegesGranted])
|
||||
|
||||
const form = useForm<z.infer<typeof formSchema>>({
|
||||
resolver: zodResolver(formSchema),
|
||||
@@ -178,6 +200,14 @@ export const PostgrestConfig = () => {
|
||||
functionNamesToAdd: values.functionNamesToAdd,
|
||||
functionNamesToRemove: values.functionNamesToRemove,
|
||||
})
|
||||
|
||||
if (values.defaultPrivilegesGranted !== defaultPrivilegesGranted) {
|
||||
await updateDefaultPrivileges({
|
||||
projectRef,
|
||||
connectionString: project?.connectionString,
|
||||
granted: values.defaultPrivilegesGranted,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
await updatePostgrestConfig(
|
||||
@@ -196,13 +226,16 @@ export const PostgrestConfig = () => {
|
||||
queryKey: privilegeKeys.exposedTablesInfinite(projectRef),
|
||||
}),
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: privilegeKeys.exposedTableCounts(projectRef, watchedDbSchema),
|
||||
queryKey: privilegeKeys.exposedTableCounts(projectRef),
|
||||
}),
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: privilegeKeys.exposedFunctionsInfinite(projectRef),
|
||||
}),
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: privilegeKeys.exposedFunctionCounts(projectRef, watchedDbSchema),
|
||||
queryKey: privilegeKeys.exposedFunctionCounts(projectRef),
|
||||
}),
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: privilegeKeys.defaultPrivileges(projectRef),
|
||||
}),
|
||||
])
|
||||
|
||||
@@ -215,6 +248,7 @@ export const PostgrestConfig = () => {
|
||||
maxRows: values.maxRows,
|
||||
dbExtraSearchPath: values.dbExtraSearchPath,
|
||||
dbPool: values.dbPool,
|
||||
defaultPrivilegesGranted: values.defaultPrivilegesGranted,
|
||||
tableIdsToAdd: [],
|
||||
tableIdsToRemove: [],
|
||||
functionNamesToAdd: [],
|
||||
@@ -228,7 +262,7 @@ export const PostgrestConfig = () => {
|
||||
}
|
||||
|
||||
const resetFormRef = useLatest(resetForm)
|
||||
const isReady = isSuccessConfig && isSuccessSchemas
|
||||
const isReady = isSuccessConfig && isSuccessSchemas && isSuccessDefaultPrivileges
|
||||
useEffect(() => {
|
||||
if (isReady) {
|
||||
resetFormRef.current()
|
||||
@@ -250,6 +284,7 @@ export const PostgrestConfig = () => {
|
||||
control: form.control,
|
||||
name: 'functionNamesToRemove',
|
||||
})
|
||||
|
||||
return (
|
||||
<PageSection id="postgrest-config" className="first:pt-0">
|
||||
<PageSectionContent>
|
||||
@@ -376,6 +411,40 @@ export const PostgrestConfig = () => {
|
||||
/>
|
||||
</FormItemLayout>
|
||||
|
||||
{watchedDbSchema.includes('public') && (
|
||||
<FormField_Shadcn_
|
||||
control={form.control}
|
||||
name="defaultPrivilegesGranted"
|
||||
render={({ field }) => (
|
||||
<FormItem_Shadcn_>
|
||||
<FormItemLayout
|
||||
layout="flex-row-reverse"
|
||||
label="Default privileges for new entities"
|
||||
description={
|
||||
<>
|
||||
When enabled, new tables and functions in the{' '}
|
||||
<code>public</code> schema are automatically accessible via the
|
||||
Data API. We recommend disabling this and manually granting
|
||||
access to each new entity.
|
||||
</>
|
||||
}
|
||||
>
|
||||
<FormControl_Shadcn_>
|
||||
<div>
|
||||
<Switch
|
||||
size="large"
|
||||
disabled={!canUpdatePostgrestConfig}
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</div>
|
||||
</FormControl_Shadcn_>
|
||||
</FormItemLayout>
|
||||
</FormItem_Shadcn_>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{watchedDbSchema.length === 0 && (
|
||||
<Admonition
|
||||
type="warning"
|
||||
|
||||
+31
-2
@@ -1,3 +1,4 @@
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { useLoadBalancersQuery } from 'data/read-replicas/load-balancers-query'
|
||||
import { useReadReplicasQuery } from 'data/read-replicas/replicas-query'
|
||||
import { useIsSchemaExposed } from 'hooks/misc/useIsSchemaExposed'
|
||||
@@ -20,6 +21,7 @@ import { Admonition } from 'ui-patterns'
|
||||
import { Input } from 'ui-patterns/DataInputs/Input'
|
||||
|
||||
import { useProjectApiUrl } from '@/data/config/project-endpoint-query'
|
||||
import { defaultPrivilegesQueryOptions } from '@/data/privileges/default-privileges-query'
|
||||
import { useTableApiAccessQuery } from '@/data/privileges/table-api-access-query'
|
||||
import { useStaticEffectEvent } from '@/hooks/useStaticEffectEvent'
|
||||
import {
|
||||
@@ -110,6 +112,18 @@ const useTableApiAccessHandler = (
|
||||
: isDuplicate
|
||||
? params.templateTableName
|
||||
: params.tableName
|
||||
const isNewTableQuery = isNewTable && enabled
|
||||
const defaultPrivilegesQuery = useQuery(
|
||||
defaultPrivilegesQueryOptions(
|
||||
{
|
||||
projectRef: project?.ref,
|
||||
connectionString: project?.connectionString ?? undefined,
|
||||
schema: currentTableSchema,
|
||||
},
|
||||
{ enabled: isNewTableQuery }
|
||||
)
|
||||
)
|
||||
|
||||
const canResolvePrivilegeParams = Boolean(
|
||||
shouldReadExistingGrants &&
|
||||
project?.ref &&
|
||||
@@ -131,20 +145,34 @@ const useTableApiAccessHandler = (
|
||||
? apiAccessStatus.data?.[permissionsTemplateTable]
|
||||
: undefined
|
||||
|
||||
const defaultPrivilegesEnabled = defaultPrivilegesQuery.data ?? true
|
||||
const defaultPrivilegesForNewTable = defaultPrivilegesEnabled
|
||||
? DEFAULT_DATA_API_PRIVILEGES
|
||||
: EMPTY_DATA_API_PRIVILEGES
|
||||
|
||||
const [privileges, setPrivileges] = useState<DeepReadonly<ApiPrivilegesByRole>>(
|
||||
DEFAULT_DATA_API_PRIVILEGES
|
||||
defaultPrivilegesForNewTable
|
||||
)
|
||||
|
||||
const hasLoadedInitialData = useRef(false)
|
||||
|
||||
const resetState = useStaticEffectEvent(() => {
|
||||
hasLoadedInitialData.current = !shouldReadExistingGrants
|
||||
setPrivileges(DEFAULT_DATA_API_PRIVILEGES)
|
||||
setPrivileges(defaultPrivilegesForNewTable)
|
||||
})
|
||||
useEffect(() => {
|
||||
resetState()
|
||||
}, [params.type, selectedSchema, permissionsTemplateSchema, permissionsTemplateTable, resetState])
|
||||
|
||||
const syncDefaultPrivileges = useStaticEffectEvent(() => {
|
||||
if (!isNewTable) return
|
||||
if (!defaultPrivilegesQuery.isSuccess) return
|
||||
setPrivileges(defaultPrivilegesForNewTable)
|
||||
})
|
||||
useEffect(() => {
|
||||
syncDefaultPrivileges()
|
||||
}, [defaultPrivilegesQuery.status, syncDefaultPrivileges])
|
||||
|
||||
const syncApiPrivileges = useStaticEffectEvent(() => {
|
||||
if (hasLoadedInitialData.current) return
|
||||
if (!apiAccessStatus.isSuccess) return
|
||||
@@ -168,6 +196,7 @@ const useTableApiAccessHandler = (
|
||||
const isPending =
|
||||
!enabled ||
|
||||
schemaExposure.status === 'pending' ||
|
||||
(isNewTable && defaultPrivilegesQuery.isPending) ||
|
||||
(shouldReadExistingGrants && apiAccessStatus.isPending)
|
||||
if (isPending) {
|
||||
return { isError: false, isPending: true, isSuccess: false, data: undefined }
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
import { queryOptions } from '@tanstack/react-query'
|
||||
import { executeSql } from 'data/sql/execute-sql-query'
|
||||
import type { ResponseError } from 'types'
|
||||
|
||||
import { privilegeKeys } from './keys'
|
||||
import { getDefaultPrivilegesStateSql } from './privileges.sql'
|
||||
|
||||
export type DefaultPrivilegesVariables = {
|
||||
projectRef?: string
|
||||
connectionString?: string | null
|
||||
schema?: string
|
||||
}
|
||||
|
||||
export async function getDefaultPrivilegesState(
|
||||
{ projectRef, connectionString, schema }: DefaultPrivilegesVariables,
|
||||
signal?: AbortSignal
|
||||
): Promise<boolean> {
|
||||
if (!projectRef) throw new Error('projectRef is required')
|
||||
|
||||
const sql = getDefaultPrivilegesStateSql({ schema })
|
||||
|
||||
const { result } = await executeSql(
|
||||
{
|
||||
projectRef,
|
||||
connectionString,
|
||||
sql,
|
||||
queryKey: ['default-privileges-state'],
|
||||
},
|
||||
signal
|
||||
)
|
||||
|
||||
const grantCount = (result[0] as { grant_count: number }).grant_count
|
||||
|
||||
return grantCount === 3
|
||||
}
|
||||
|
||||
export type DefaultPrivilegesData = Awaited<ReturnType<typeof getDefaultPrivilegesState>>
|
||||
export type DefaultPrivilegesError = ResponseError
|
||||
|
||||
export const defaultPrivilegesQueryOptions = (
|
||||
{ projectRef, connectionString, schema }: DefaultPrivilegesVariables,
|
||||
{ enabled = true }: { enabled?: boolean } = {}
|
||||
) => {
|
||||
return queryOptions({
|
||||
// eslint-disable-next-line @tanstack/query/exhaustive-deps -- connection string doesn't change the result of the query
|
||||
queryKey: privilegeKeys.defaultPrivileges(projectRef, schema),
|
||||
queryFn: ({ signal }) =>
|
||||
getDefaultPrivilegesState(
|
||||
{
|
||||
projectRef,
|
||||
connectionString,
|
||||
schema,
|
||||
},
|
||||
signal
|
||||
),
|
||||
enabled: enabled && typeof projectRef !== 'undefined',
|
||||
})
|
||||
}
|
||||
@@ -11,8 +11,14 @@ export const privilegeKeys = {
|
||||
'exposed-tables-infinite',
|
||||
...(search ? ([{ search }] as const) : []),
|
||||
] as const,
|
||||
exposedTableCounts: (projectRef: string | undefined, selectedSchemas: string[]) =>
|
||||
['projects', projectRef, 'privileges', 'exposed-table-counts', ...selectedSchemas] as const,
|
||||
exposedTableCounts: (projectRef: string | undefined, selectedSchemas?: string[]) =>
|
||||
[
|
||||
'projects',
|
||||
projectRef,
|
||||
'privileges',
|
||||
'exposed-table-counts',
|
||||
...(selectedSchemas ? ([selectedSchemas] as const) : []),
|
||||
] as const,
|
||||
exposedFunctionsInfinite: (projectRef: string | undefined, search?: string) =>
|
||||
[
|
||||
'projects',
|
||||
@@ -21,6 +27,20 @@ export const privilegeKeys = {
|
||||
'exposed-functions-infinite',
|
||||
...(search ? ([{ search }] as const) : []),
|
||||
] as const,
|
||||
exposedFunctionCounts: (projectRef: string | undefined, selectedSchemas: string[]) =>
|
||||
['projects', projectRef, 'privileges', 'exposed-function-counts', ...selectedSchemas] as const,
|
||||
exposedFunctionCounts: (projectRef: string | undefined, selectedSchemas?: string[]) =>
|
||||
[
|
||||
'projects',
|
||||
projectRef,
|
||||
'privileges',
|
||||
'exposed-function-counts',
|
||||
...(selectedSchemas ? ([selectedSchemas] as const) : []),
|
||||
] as const,
|
||||
defaultPrivileges: (projectRef: string | undefined, schema?: string) =>
|
||||
[
|
||||
'projects',
|
||||
projectRef,
|
||||
'privileges',
|
||||
'default-privileges',
|
||||
...(schema ? [schema] : []),
|
||||
] as const,
|
||||
}
|
||||
@@ -221,6 +221,58 @@ export function getExposedFunctionCountsSql({ selectedSchemas }: { selectedSchem
|
||||
`
|
||||
}
|
||||
|
||||
export function getDefaultPrivilegesStateSql({ schema = 'public' }: { schema?: string } = {}) {
|
||||
return /* SQL */ `
|
||||
select
|
||||
count(*)::int as grant_count
|
||||
from pg_default_acl d
|
||||
join pg_namespace n on n.oid = d.defaclnamespace
|
||||
join pg_roles r on r.oid = d.defaclrole
|
||||
where n.nspname = '${schema}'
|
||||
and r.rolname = 'postgres'
|
||||
and d.defaclobjtype in ('r', 'f', 'S')
|
||||
and exists (
|
||||
select 1
|
||||
from aclexplode(d.defaclacl) acl
|
||||
join pg_roles gr on gr.oid = acl.grantee
|
||||
where gr.rolname in ('anon', 'authenticated', 'service_role')
|
||||
)
|
||||
`
|
||||
}
|
||||
|
||||
export function buildDefaultPrivilegesSql(action: 'grant' | 'revoke') {
|
||||
const roles = ['anon', 'authenticated', 'service_role']
|
||||
const statements: string[] = []
|
||||
|
||||
for (const role of roles) {
|
||||
if (action === 'grant') {
|
||||
statements.push(
|
||||
`alter default privileges for role postgres in schema public grant select, insert, update, delete on tables to ${role}`,
|
||||
`alter default privileges for role postgres in schema public grant execute on functions to ${role}`,
|
||||
`alter default privileges for role postgres in schema public grant usage, select on sequences to ${role}`
|
||||
)
|
||||
} else {
|
||||
statements.push(
|
||||
`alter default privileges for role postgres in schema public revoke select, insert, update, delete on tables from ${role}`,
|
||||
`alter default privileges for role postgres in schema public revoke execute on functions from ${role}`,
|
||||
`alter default privileges for role postgres in schema public revoke usage, select on sequences from ${role}`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (action === 'revoke') {
|
||||
statements.push(
|
||||
`alter default privileges for role postgres in schema public revoke execute on functions from public`
|
||||
)
|
||||
} else {
|
||||
statements.push(
|
||||
`alter default privileges for role postgres in schema public grant execute on functions to public`
|
||||
)
|
||||
}
|
||||
|
||||
return statements.join(';\n') + ';'
|
||||
}
|
||||
|
||||
export const buildTablePrivilegesSql = (oids: number[], action: 'grant' | 'revoke') => {
|
||||
if (oids.length === 0) return ''
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { useMemo } from 'react'
|
||||
|
||||
import type { ConnectionVars } from 'data/common.types'
|
||||
import { useIsSchemaExposed } from 'hooks/misc/useIsSchemaExposed'
|
||||
import { isApiAccessRole, isApiPrivilegeType, type ApiPrivilegesByRole } from 'lib/data-api-types'
|
||||
import type { Prettify } from 'lib/type-helpers'
|
||||
import { useMemo } from 'react'
|
||||
import type { UseCustomQueryOptions } from 'types'
|
||||
|
||||
import {
|
||||
useTablePrivilegesQuery,
|
||||
type TablePrivilegesData,
|
||||
@@ -23,6 +23,7 @@ const getApiPrivilegesByRole = (
|
||||
const privilegesByRole: ApiPrivilegesByRole = {
|
||||
anon: [],
|
||||
authenticated: [],
|
||||
service_role: [],
|
||||
}
|
||||
|
||||
privileges.forEach((privilege) => {
|
||||
@@ -175,11 +176,17 @@ export const useTableApiAccessQuery = (
|
||||
return
|
||||
}
|
||||
|
||||
const tablePrivileges = tablePrivilegesByName[tableName] ?? { anon: [], authenticated: [] }
|
||||
const hasAnonOrAuthenticatedPrivileges =
|
||||
tablePrivileges.anon.length > 0 || tablePrivileges.authenticated.length > 0
|
||||
const tablePrivileges = tablePrivilegesByName[tableName] ?? {
|
||||
anon: [],
|
||||
authenticated: [],
|
||||
service_role: [],
|
||||
}
|
||||
const hasApiPrivileges =
|
||||
tablePrivileges.anon.length > 0 ||
|
||||
tablePrivileges.authenticated.length > 0 ||
|
||||
tablePrivileges.service_role.length > 0
|
||||
|
||||
resultData[tableName] = hasAnonOrAuthenticatedPrivileges
|
||||
resultData[tableName] = hasApiPrivileges
|
||||
? {
|
||||
apiAccessType: 'access',
|
||||
privileges: tablePrivileges,
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import { useMutation } from '@tanstack/react-query'
|
||||
import { executeSql } from 'data/sql/execute-sql-query'
|
||||
import { toast } from 'sonner'
|
||||
import type { UseCustomMutationOptions } from 'types'
|
||||
|
||||
import type { ConnectionVars } from '../common.types'
|
||||
import { buildDefaultPrivilegesSql } from './privileges.sql'
|
||||
|
||||
export type UpdateDefaultPrivilegesVariables = ConnectionVars & {
|
||||
granted: boolean
|
||||
}
|
||||
|
||||
export async function updateDefaultPrivileges({
|
||||
projectRef,
|
||||
connectionString,
|
||||
granted,
|
||||
}: UpdateDefaultPrivilegesVariables): Promise<void> {
|
||||
if (!projectRef) throw new Error('projectRef is required')
|
||||
|
||||
const sql = buildDefaultPrivilegesSql(granted ? 'grant' : 'revoke')
|
||||
|
||||
await executeSql({
|
||||
projectRef,
|
||||
connectionString,
|
||||
sql,
|
||||
queryKey: ['update-default-privileges'],
|
||||
})
|
||||
}
|
||||
|
||||
type UpdateDefaultPrivilegesData = Awaited<ReturnType<typeof updateDefaultPrivileges>>
|
||||
|
||||
export const useUpdateDefaultPrivilegesMutation = ({
|
||||
onError,
|
||||
...options
|
||||
}: Omit<
|
||||
UseCustomMutationOptions<UpdateDefaultPrivilegesData, Error, UpdateDefaultPrivilegesVariables>,
|
||||
'mutationFn'
|
||||
> = {}) => {
|
||||
return useMutation<UpdateDefaultPrivilegesData, Error, UpdateDefaultPrivilegesVariables>({
|
||||
mutationFn: (vars: UpdateDefaultPrivilegesVariables) => updateDefaultPrivileges(vars),
|
||||
onError(error: Error) {
|
||||
toast.error(`Failed to update default privileges: ${error.message}`)
|
||||
},
|
||||
...(onError ? { onError } : {}),
|
||||
...options,
|
||||
})
|
||||
}
|
||||
@@ -54,7 +54,6 @@ export async function updateExposedEntities({
|
||||
type UpdateExposedEntitiesData = Awaited<ReturnType<typeof updateExposedEntities>>
|
||||
|
||||
export const useUpdateExposedEntitiesMutation = ({
|
||||
onSuccess,
|
||||
onError,
|
||||
...options
|
||||
}: Omit<
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import type { TablePrivilegesGrant } from 'data/privileges/table-privileges-grant-mutation'
|
||||
|
||||
import type { DeepReadonly } from './type-helpers'
|
||||
|
||||
export const API_ACCESS_ROLES = ['anon', 'authenticated'] as const
|
||||
export const API_ACCESS_ROLES = ['anon', 'authenticated', 'service_role'] as const
|
||||
export type ApiAccessRole = (typeof API_ACCESS_ROLES)[number]
|
||||
|
||||
export const isApiAccessRole = (value: string): value is ApiAccessRole => {
|
||||
@@ -28,11 +29,13 @@ export type ApiPrivilegesByRole = Record<ApiAccessRole, ApiPrivilegeType[]>
|
||||
export const DEFAULT_DATA_API_PRIVILEGES: DeepReadonly<ApiPrivilegesByRole> = {
|
||||
anon: [...API_PRIVILEGE_TYPES],
|
||||
authenticated: [...API_PRIVILEGE_TYPES],
|
||||
service_role: [...API_PRIVILEGE_TYPES],
|
||||
}
|
||||
|
||||
export const EMPTY_DATA_API_PRIVILEGES: DeepReadonly<ApiPrivilegesByRole> = {
|
||||
anon: [],
|
||||
authenticated: [],
|
||||
service_role: [],
|
||||
}
|
||||
|
||||
export const checkDataApiPrivilegesNonEmpty = (
|
||||
|
||||
Reference in new issue
Block a user