Skip to content

Commit b3c6992

Browse files
ivasilovjoshenlim
andauthored
feat: Make the protected schemas dynamic, namespace schemas are now protected (supabase#37290)
* Add hooks for async protected schemas. * Migrate the ProtectedSchemaWarning to support the new implementation. * sq * Migrate all uses of protected schemas to the new approach. * Delete extra file. * Refactor the import foreign schema dialog to forbid protected and exposed schemas. * Add the type to the protected schema. * Revert ImportForeignSchemaDialog, it'll be addressed in another PR. * Update apps/studio/hooks/useProtectedSchemas.ts Co-authored-by: Joshen Lim <joshenlimek@gmail.com> * Fix a bad commit. * Minor fixes. * Fix the FDW delete mutation to handle names with numbers. * Simplify the logic to skip a fetch. * Minor fixes. * Make the useIcebergFdwSchemasQuery work for all iceberg FDWs. * Fix the tab schemas to always show in the Table Editor. * Apply suggestion from @joshenlim Co-authored-by: Joshen Lim <joshenlimek@gmail.com> * Fix a minor typo. * Refactor ProtectedSchemaWarning to use Admonition, and standardise input field for target schema iceberg --------- Co-authored-by: Joshen Lim <joshenlimek@gmail.com>
1 parent 0a985a5 commit b3c6992

File tree

28 files changed

+335
-278
lines changed

28 files changed

+335
-278
lines changed

apps/studio/components/interfaces/Auth/Policies/Policies.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
PolicyTableRow,
1111
PolicyTableRowProps,
1212
} from 'components/interfaces/Auth/Policies/PolicyTableRow'
13-
import ProtectedSchemaWarning from 'components/interfaces/Database/ProtectedSchemaWarning'
13+
import { ProtectedSchemaWarning } from 'components/interfaces/Database/ProtectedSchemaWarning'
1414
import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
1515
import NoSearchResults from 'components/to-be-cleaned/NoSearchResults'
1616
import ProductEmptyState from 'components/to-be-cleaned/ProductEmptyState'

apps/studio/components/interfaces/Database/EnumeratedTypes/EnumeratedTypes.tsx

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import {
1313
useEnumeratedTypesQuery,
1414
} from 'data/enumerated-types/enumerated-types-query'
1515
import { useQuerySchemaState } from 'hooks/misc/useSchemaQueryState'
16-
import { PROTECTED_SCHEMAS } from 'lib/constants/schemas'
16+
import { useIsProtectedSchema } from 'hooks/useProtectedSchemas'
1717
import {
1818
Button,
1919
DropdownMenu,
@@ -22,7 +22,7 @@ import {
2222
DropdownMenuTrigger,
2323
Input,
2424
} from 'ui'
25-
import ProtectedSchemaWarning from '../ProtectedSchemaWarning'
25+
import { ProtectedSchemaWarning } from '../ProtectedSchemaWarning'
2626
import CreateEnumeratedTypeSidePanel from './CreateEnumeratedTypeSidePanel'
2727
import DeleteEnumeratedTypeModal from './DeleteEnumeratedTypeModal'
2828
import EditEnumeratedTypeSidePanel from './EditEnumeratedTypeSidePanel'
@@ -52,11 +52,7 @@ const EnumeratedTypes = () => {
5252
)
5353
: enumeratedTypes.filter((x) => x.schema === selectedSchema)
5454

55-
const protectedSchemas = (schemas ?? []).filter((schema) =>
56-
PROTECTED_SCHEMAS.includes(schema?.name ?? '')
57-
)
58-
const schema = schemas?.find((schema) => schema.name === selectedSchema)
59-
const isLocked = protectedSchemas.some((s) => s.id === schema?.id)
55+
const { isSchemaLocked } = useIsProtectedSchema({ schema: selectedSchema })
6056

6157
return (
6258
<div className="space-y-4">
@@ -81,7 +77,7 @@ const EnumeratedTypes = () => {
8177

8278
<div className="flex items-center gap-2">
8379
<DocsButton href="https://www.postgresql.org/docs/current/datatype-enum.html" />
84-
{!isLocked && (
80+
{!isSchemaLocked && (
8581
<Button
8682
className="ml-auto flex-1"
8783
type="primary"
@@ -93,7 +89,9 @@ const EnumeratedTypes = () => {
9389
</div>
9490
</div>
9591

96-
{isLocked && <ProtectedSchemaWarning schema={selectedSchema} entity="enumerated types" />}
92+
{isSchemaLocked && (
93+
<ProtectedSchemaWarning schema={selectedSchema} entity="enumerated types" />
94+
)}
9795

9896
{isLoading && <GenericSkeletonLoader />}
9997

@@ -140,7 +138,7 @@ const EnumeratedTypes = () => {
140138
<Table.td>{type.name}</Table.td>
141139
<Table.td>{type.enums.join(', ')}</Table.td>
142140
<Table.td>
143-
{!isLocked && (
141+
{!isSchemaLocked && (
144142
<div className="flex justify-end items-center space-x-2">
145143
<DropdownMenu>
146144
<DropdownMenuTrigger asChild>

apps/studio/components/interfaces/Database/Functions/CreateFunction/index.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { useDatabaseExtensionsQuery } from 'data/database-extensions/database-ex
1313
import { useDatabaseFunctionCreateMutation } from 'data/database-functions/database-functions-create-mutation'
1414
import { DatabaseFunction } from 'data/database-functions/database-functions-query'
1515
import { useDatabaseFunctionUpdateMutation } from 'data/database-functions/database-functions-update-mutation'
16-
import { PROTECTED_SCHEMAS } from 'lib/constants/schemas'
16+
import { useProtectedSchemas } from 'hooks/useProtectedSchemas'
1717
import type { FormSchema } from 'types'
1818
import {
1919
Button,
@@ -149,6 +149,8 @@ const CreateFunction = ({ func, visible, setVisible }: CreateFunctionProps) => {
149149
}
150150
}, [visible, func])
151151

152+
const { data: protectedSchemas } = useProtectedSchemas()
153+
152154
return (
153155
<Sheet open={visible} onOpenChange={() => isClosingSidePanel()}>
154156
<SheetContent
@@ -205,7 +207,7 @@ const CreateFunction = ({ func, visible, setVisible }: CreateFunctionProps) => {
205207
<SchemaSelector
206208
portal={false}
207209
selectedSchemaName={field.value}
208-
excludedSchemas={PROTECTED_SCHEMAS}
210+
excludedSchemas={protectedSchemas?.map((s) => s.name)}
209211
size="small"
210212
onSelectSchema={(name) => field.onChange(name)}
211213
/>

apps/studio/components/interfaces/Database/Functions/FunctionsList/FunctionsList.tsx

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { PostgresFunction } from '@supabase/postgres-meta'
22
import { PermissionAction } from '@supabase/shared-types/out/constants'
3-
import { noop, partition } from 'lodash'
3+
import { noop } from 'lodash'
44
import { Search } from 'lucide-react'
55
import { useRouter } from 'next/router'
66

@@ -16,10 +16,10 @@ import { useDatabaseFunctionsQuery } from 'data/database-functions/database-func
1616
import { useSchemasQuery } from 'data/database/schemas-query'
1717
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
1818
import { useQuerySchemaState } from 'hooks/misc/useSchemaQueryState'
19-
import { PROTECTED_SCHEMAS } from 'lib/constants/schemas'
19+
import { useIsProtectedSchema } from 'hooks/useProtectedSchemas'
2020
import { useAiAssistantStateSnapshot } from 'state/ai-assistant-state'
2121
import { AiIconAnimation, Input } from 'ui'
22-
import ProtectedSchemaWarning from '../../ProtectedSchemaWarning'
22+
import { ProtectedSchemaWarning } from '../../ProtectedSchemaWarning'
2323
import FunctionList from './FunctionList'
2424

2525
interface FunctionsListProps {
@@ -60,11 +60,8 @@ const FunctionsList = ({
6060
projectRef: project?.ref,
6161
connectionString: project?.connectionString,
6262
})
63-
const [protectedSchemas] = partition(schemas ?? [], (schema) =>
64-
PROTECTED_SCHEMAS.includes(schema?.name ?? '')
65-
)
66-
const foundSchema = schemas?.find((schema) => schema.name === selectedSchema)
67-
const isLocked = protectedSchemas.some((s) => s.id === foundSchema?.id)
63+
64+
const { isSchemaLocked } = useIsProtectedSchema({ schema: selectedSchema })
6865

6966
const {
7067
data: functions,
@@ -126,7 +123,7 @@ const FunctionsList = ({
126123
</div>
127124

128125
<div className="flex items-center gap-x-2">
129-
{!isLocked && (
126+
{!isSchemaLocked && (
130127
<>
131128
<ButtonTooltip
132129
disabled={!canCreateFunctions}
@@ -169,7 +166,7 @@ const FunctionsList = ({
169166
</div>
170167
</div>
171168

172-
{isLocked && <ProtectedSchemaWarning schema={selectedSchema} entity="functions" />}
169+
{isSchemaLocked && <ProtectedSchemaWarning schema={selectedSchema} entity="functions" />}
173170

174171
<Table
175172
className="table-fixed overflow-x-auto"
@@ -192,7 +189,7 @@ const FunctionsList = ({
192189
<FunctionList
193190
schema={selectedSchema}
194191
filterString={filterString}
195-
isLocked={isLocked}
192+
isLocked={isSchemaLocked}
196193
editFunction={editFunction}
197194
deleteFunction={deleteFunction}
198195
/>

apps/studio/components/interfaces/Database/Indexes/Indexes.tsx

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { partition, sortBy } from 'lodash'
1+
import { sortBy } from 'lodash'
22
import { AlertCircle, Search, Trash } from 'lucide-react'
33
import { useEffect, useState } from 'react'
44
import { toast } from 'sonner'
@@ -14,10 +14,10 @@ import { useDatabaseIndexDeleteMutation } from 'data/database-indexes/index-dele
1414
import { DatabaseIndex, useIndexesQuery } from 'data/database-indexes/indexes-query'
1515
import { useSchemasQuery } from 'data/database/schemas-query'
1616
import { useQuerySchemaState } from 'hooks/misc/useSchemaQueryState'
17-
import { PROTECTED_SCHEMAS } from 'lib/constants/schemas'
17+
import { useIsProtectedSchema } from 'hooks/useProtectedSchemas'
1818
import { Button, Input, SidePanel } from 'ui'
1919
import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal'
20-
import ProtectedSchemaWarning from '../ProtectedSchemaWarning'
20+
import { ProtectedSchemaWarning } from '../ProtectedSchemaWarning'
2121
import CreateIndexSidePanel from './CreateIndexSidePanel'
2222

2323
const Indexes = () => {
@@ -58,11 +58,7 @@ const Indexes = () => {
5858
},
5959
})
6060

61-
const [protectedSchemas] = partition(schemas ?? [], (schema) =>
62-
PROTECTED_SCHEMAS.includes(schema?.name ?? '')
63-
)
64-
const schema = schemas?.find((schema) => schema.name === selectedSchema)
65-
const isLocked = protectedSchemas.some((s) => s.id === schema?.id)
61+
const { isSchemaLocked } = useIsProtectedSchema({ schema: selectedSchema })
6662

6763
const sortedIndexes = sortBy(allIndexes ?? [], (index) => index.name.toLocaleLowerCase())
6864
const indexes =
@@ -122,7 +118,7 @@ const Indexes = () => {
122118
icon={<Search size={14} />}
123119
/>
124120

125-
{!isLocked && (
121+
{!isSchemaLocked && (
126122
<Button
127123
className="ml-auto flex-grow lg:flex-grow-0"
128124
type="primary"
@@ -134,7 +130,7 @@ const Indexes = () => {
134130
)}
135131
</div>
136132

137-
{isLocked && <ProtectedSchemaWarning schema={selectedSchema} entity="indexes" />}
133+
{isSchemaLocked && <ProtectedSchemaWarning schema={selectedSchema} entity="indexes" />}
138134

139135
{isLoadingIndexes && <GenericSkeletonLoader />}
140136

@@ -190,7 +186,7 @@ const Indexes = () => {
190186
<Button type="default" onClick={() => setSelectedIndex(index)}>
191187
View definition
192188
</Button>
193-
{!isLocked && (
189+
{!isSchemaLocked && (
194190
<Button
195191
type="text"
196192
className="px-1"

apps/studio/components/interfaces/Database/ProtectedSchemaWarning.tsx

Lines changed: 75 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,35 @@
11
import { useState } from 'react'
2-
import { AlertDescription_Shadcn_, AlertTitle_Shadcn_, Alert_Shadcn_, Button, Modal } from 'ui'
32

4-
import { PROTECTED_SCHEMAS } from 'lib/constants/schemas'
5-
import { AlertCircle } from 'lucide-react'
3+
import {
4+
Button,
5+
Dialog,
6+
DialogContent,
7+
DialogFooter,
8+
DialogHeader,
9+
DialogSection,
10+
DialogSectionSeparator,
11+
DialogTitle,
12+
DialogTrigger,
13+
cn,
14+
} from 'ui'
615

7-
export const ProtectedSchemaModal = ({
8-
visible,
9-
onClose,
10-
}: {
11-
visible: boolean
12-
onClose: () => void
13-
}) => {
16+
import { INTERNAL_SCHEMAS, useIsProtectedSchema } from 'hooks/useProtectedSchemas'
17+
import { Admonition } from 'ui-patterns'
18+
19+
export const ProtectedSchemaDialog = ({ onClose }: { onClose: () => void }) => {
1420
return (
15-
<Modal
16-
size="medium"
17-
visible={visible}
18-
header="Schemas managed by Supabase"
19-
customFooter={
20-
<div className="flex items-center justify-end space-x-2">
21-
<Button type="default" onClick={() => onClose()}>
22-
Understood
23-
</Button>
24-
</div>
25-
}
26-
onCancel={() => onClose()}
27-
>
28-
<Modal.Content className="space-y-2">
21+
<>
22+
<DialogHeader>
23+
<DialogTitle>Schemas managed by Supabase</DialogTitle>
24+
</DialogHeader>
25+
<DialogSectionSeparator />
26+
<DialogSection className="space-y-2 prose">
2927
<p className="text-sm">
3028
The following schemas are managed by Supabase and are currently protected from write
3129
access through the dashboard.
3230
</p>
3331
<div className="flex flex-wrap gap-1">
34-
{PROTECTED_SCHEMAS.map((schema) => (
32+
{INTERNAL_SCHEMAS.map((schema) => (
3533
<code key={schema} className="text-xs">
3634
{schema}
3735
</code>
@@ -45,32 +43,67 @@ export const ProtectedSchemaModal = ({
4543
You can, however, still interact with those schemas through the SQL Editor although we
4644
advise you only do so if you know what you are doing.
4745
</p>
48-
</Modal.Content>
49-
</Modal>
46+
</DialogSection>
47+
<DialogFooter>
48+
<div className="flex items-center justify-end space-x-2">
49+
<Button type="default" onClick={onClose}>
50+
Understood
51+
</Button>
52+
</div>
53+
</DialogFooter>
54+
</>
5055
)
5156
}
5257

53-
const ProtectedSchemaWarning = ({ schema, entity }: { schema: string; entity: string }) => {
58+
export const ProtectedSchemaWarning = ({
59+
size = 'md',
60+
schema,
61+
entity,
62+
}: {
63+
size?: 'sm' | 'md'
64+
schema: string
65+
entity: string
66+
}) => {
5467
const [showModal, setShowModal] = useState(false)
68+
const { isSchemaLocked, reason } = useIsProtectedSchema({ schema })
69+
70+
if (!isSchemaLocked) return null
5571

5672
return (
57-
<>
58-
<Alert_Shadcn_>
59-
<AlertCircle strokeWidth={2} />
60-
<AlertTitle_Shadcn_>Currently viewing {entity} from a protected schema</AlertTitle_Shadcn_>
61-
<AlertDescription_Shadcn_>
73+
<Admonition
74+
showIcon={false}
75+
type="note"
76+
title={
77+
size === 'sm' ? `Viewing protected schema` : `Viewing ${entity} from a protected schema`
78+
}
79+
className={cn(
80+
'[&>div>p]:prose [&>div>p]:max-w-full [&>div>p]:!leading-normal',
81+
size === 'sm' ? '[&>div>p]:text-xs' : '[&>div>p]:text-sm'
82+
)}
83+
>
84+
{reason === 'fdw' ? (
85+
<p>
86+
The <code className="text-xs">{schema}</code> schema is used by Supabase to connect to
87+
analytics buckets and is read-only through the dashboard.
88+
</p>
89+
) : (
90+
<>
6291
<p className="mb-2">
6392
The <code className="text-xs">{schema}</code> schema is managed by Supabase and is
6493
read-only through the dashboard.
6594
</p>
66-
<Button type="default" size="tiny" onClick={() => setShowModal(true)}>
67-
Learn more
68-
</Button>
69-
</AlertDescription_Shadcn_>
70-
</Alert_Shadcn_>
71-
<ProtectedSchemaModal visible={showModal} onClose={() => setShowModal(false)} />
72-
</>
95+
<Dialog open={showModal} onOpenChange={setShowModal}>
96+
<DialogTrigger asChild>
97+
<Button type="default" size="tiny" onClick={() => setShowModal(true)}>
98+
Learn more
99+
</Button>
100+
</DialogTrigger>
101+
<DialogContent>
102+
<ProtectedSchemaDialog onClose={() => setShowModal(false)} />
103+
</DialogContent>
104+
</Dialog>
105+
</>
106+
)}
107+
</Admonition>
73108
)
74109
}
75-
76-
export default ProtectedSchemaWarning

0 commit comments

Comments
 (0)