mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 17:35:10 +03:00
fix: remove refs to pgsodium keys from vault & wrappers (#35168)
This commit is contained in:
1 parent
f183ba2c68
commit
bc6cca73bb
19 files changed
+15
-747
No files matched your search
@@ -204,16 +204,6 @@ const supabaseIntegrations: IntegrationDefinition[] = [
|
||||
loading: Loading,
|
||||
}
|
||||
)
|
||||
case 'keys':
|
||||
return dynamic(
|
||||
() =>
|
||||
import('../Vault/Keys/EncryptionKeysManagement').then(
|
||||
(mod) => mod.EncryptionKeysManagement
|
||||
),
|
||||
{
|
||||
loading: Loading,
|
||||
}
|
||||
)
|
||||
case 'secrets':
|
||||
return dynamic(
|
||||
() => import('../Vault/Secrets/SecretsManagement').then((mod) => mod.SecretsManagement),
|
||||
|
||||
@@ -1,98 +0,0 @@
|
||||
import { noop } from 'lodash'
|
||||
import { Plus } from 'lucide-react'
|
||||
import {
|
||||
Input,
|
||||
SelectContent_Shadcn_,
|
||||
SelectGroup_Shadcn_,
|
||||
SelectItem_Shadcn_,
|
||||
SelectTrigger_Shadcn_,
|
||||
Select_Shadcn_,
|
||||
} from 'ui'
|
||||
|
||||
import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
|
||||
import { usePgSodiumKeysQuery } from 'data/pg-sodium-keys/pg-sodium-keys-query'
|
||||
|
||||
interface EncryptionKeySelectorProps {
|
||||
nameId?: string
|
||||
label?: string
|
||||
labelOptional?: string
|
||||
selectedKeyId?: any
|
||||
error?: string
|
||||
onSelectKey: (keyId: string) => void
|
||||
onUpdateDescription?: (desc: string) => void
|
||||
}
|
||||
|
||||
const EncryptionKeySelector = ({
|
||||
nameId = 'keyName',
|
||||
label = 'Encryption key',
|
||||
labelOptional,
|
||||
selectedKeyId,
|
||||
error,
|
||||
onSelectKey = noop,
|
||||
onUpdateDescription = noop,
|
||||
}: EncryptionKeySelectorProps) => {
|
||||
const { project } = useProjectContext()
|
||||
|
||||
const { data } = usePgSodiumKeysQuery({
|
||||
projectRef: project?.ref,
|
||||
connectionString: project?.connectionString,
|
||||
})
|
||||
|
||||
const keys = data || []
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="space-y-2">
|
||||
{label && (
|
||||
<div className="flex flex-row gap-x-2 justify-between">
|
||||
<label className="block text-foreground-light text-sm break-all">{label}</label>
|
||||
{labelOptional && (
|
||||
<span className="text-foreground-lighter text-sm">{labelOptional}</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<Select_Shadcn_ value={selectedKeyId} onValueChange={onSelectKey}>
|
||||
<SelectTrigger_Shadcn_ className="w-full">
|
||||
{keys.find((key) => key.id === selectedKeyId)?.name || 'Select an encryption key'}
|
||||
</SelectTrigger_Shadcn_>
|
||||
<SelectContent_Shadcn_>
|
||||
<SelectGroup_Shadcn_>
|
||||
<SelectItem_Shadcn_ value="create-new">
|
||||
<span className="flex items-center gap-2">
|
||||
<Plus size={16} strokeWidth={1.5} />
|
||||
Create a new Encryption Key
|
||||
</span>
|
||||
</SelectItem_Shadcn_>
|
||||
{keys.length > 0 && (
|
||||
<>
|
||||
<div className="h-px bg-border my-2" />
|
||||
{keys.map((key) => (
|
||||
<SelectItem_Shadcn_ key={key.id} value={key.id}>
|
||||
<div className="space-y-1">
|
||||
<p>{key.name || 'No name provided'}</p>
|
||||
<p className="text-xs">
|
||||
ID: <span className="font-mono">{key.id}</span>
|
||||
</p>
|
||||
</div>
|
||||
</SelectItem_Shadcn_>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</SelectGroup_Shadcn_>
|
||||
</SelectContent_Shadcn_>
|
||||
</Select_Shadcn_>
|
||||
</div>
|
||||
{selectedKeyId === 'create-new' && (
|
||||
<Input
|
||||
id={nameId}
|
||||
label="Name of encryption key"
|
||||
error={error}
|
||||
onChange={(event) => onUpdateDescription(event.target.value)}
|
||||
descriptionText="Provide a name for your key for easier identification"
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default EncryptionKeySelector
|
||||
-310
@@ -1,310 +0,0 @@
|
||||
import { PermissionAction } from '@supabase/shared-types/out/constants'
|
||||
import dayjs from 'dayjs'
|
||||
import { sortBy } from 'lodash'
|
||||
import { Fragment, useEffect, useState } from 'react'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
import { useParams } from 'common'
|
||||
import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
|
||||
import { ButtonTooltip } from 'components/ui/ButtonTooltip'
|
||||
import { DocsButton } from 'components/ui/DocsButton'
|
||||
import { usePgSodiumKeyCreateMutation } from 'data/pg-sodium-keys/pg-sodium-key-create-mutation'
|
||||
import { usePgSodiumKeyDeleteMutation } from 'data/pg-sodium-keys/pg-sodium-key-delete-mutation'
|
||||
import { usePgSodiumKeysQuery } from 'data/pg-sodium-keys/pg-sodium-keys-query'
|
||||
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
|
||||
import { Key, Loader, Search, Trash, X } from 'lucide-react'
|
||||
import { Button, Form, Input, Listbox, Modal, Separator } from 'ui'
|
||||
import { Admonition } from 'ui-patterns'
|
||||
|
||||
const DEFAULT_KEY_NAME = 'No description provided'
|
||||
|
||||
export const EncryptionKeysManagement = () => {
|
||||
const { search } = useParams()
|
||||
const { project } = useProjectContext()
|
||||
|
||||
const [searchValue, setSearchValue] = useState<string>('')
|
||||
const [selectedSort, setSelectedSort] = useState<'name' | 'created'>('created')
|
||||
const [showAddKeyModal, setShowAddKeyModal] = useState(false)
|
||||
const [selectedKeyToRemove, setSelectedKeyToRemove] = useState<any>()
|
||||
const canManageKeys = useCheckPermissions(PermissionAction.TENANT_SQL_ADMIN_WRITE, 'tables')
|
||||
|
||||
useEffect(() => {
|
||||
if (search !== undefined) setSearchValue(search)
|
||||
}, [search])
|
||||
|
||||
const { data, isLoading } = usePgSodiumKeysQuery({
|
||||
projectRef: project?.ref,
|
||||
connectionString: project?.connectionString,
|
||||
})
|
||||
const { mutate: addKeyMutation, isLoading: isCreating } = usePgSodiumKeyCreateMutation({
|
||||
onSuccess: () => {
|
||||
toast.success('Successfully added new key')
|
||||
setShowAddKeyModal(false)
|
||||
},
|
||||
})
|
||||
const { mutate: deleteKeyMutation, isLoading: isDeleting } = usePgSodiumKeyDeleteMutation({
|
||||
onSuccess: () => {
|
||||
toast.success(`Successfully deleted encryption key`)
|
||||
setSelectedKeyToRemove(undefined)
|
||||
},
|
||||
})
|
||||
|
||||
const allKeys = data || []
|
||||
const keys = sortBy(
|
||||
searchValue
|
||||
? allKeys.filter(
|
||||
(key) =>
|
||||
(key?.name ?? '').toLowerCase().includes(searchValue.toLowerCase()) ||
|
||||
key.id.toLowerCase().includes(searchValue.toLowerCase())
|
||||
)
|
||||
: allKeys,
|
||||
(k) => {
|
||||
if (selectedSort === 'created') {
|
||||
return Number(new Date(k.created))
|
||||
} else {
|
||||
return k[selectedSort]
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
const addKey = async (values: any, { setSubmitting }: any) => {
|
||||
if (!project) return console.error('Project is required')
|
||||
|
||||
addKeyMutation({
|
||||
projectRef: project.ref,
|
||||
connectionString: project.connectionString,
|
||||
name: values.name,
|
||||
})
|
||||
}
|
||||
|
||||
const confirmDeleteKey = async () => {
|
||||
if (!selectedKeyToRemove) return
|
||||
if (!project) return console.error('Project is required')
|
||||
|
||||
deleteKeyMutation({
|
||||
projectRef: project.ref,
|
||||
connectionString: project.connectionString,
|
||||
id: selectedKeyToRemove.id,
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="space-y-4 p-4 md:p-10">
|
||||
<div className="flex flex-col md:flex-row md:items-center justify-between gap-2">
|
||||
<div className="flex flex-col md:flex-row md:items-center gap-2">
|
||||
<Input
|
||||
className="md:w-52 input-clear"
|
||||
size="tiny"
|
||||
placeholder="Search by name or ID"
|
||||
value={searchValue}
|
||||
onChange={(event) => setSearchValue(event.target.value)}
|
||||
icon={<Search strokeWidth={2} size={16} />}
|
||||
actions={
|
||||
searchValue.length > 0
|
||||
? [
|
||||
<Button
|
||||
key="clear"
|
||||
size="tiny"
|
||||
type="text"
|
||||
icon={<X />}
|
||||
className="px-1"
|
||||
onClick={() => setSearchValue('')}
|
||||
/>,
|
||||
]
|
||||
: []
|
||||
}
|
||||
/>
|
||||
<div className="md:w-44">
|
||||
<Listbox size="tiny" value={selectedSort} onChange={setSelectedSort}>
|
||||
<Listbox.Option
|
||||
id="created"
|
||||
className="max-w-[180px]"
|
||||
value="created"
|
||||
label="Sort by created at"
|
||||
>
|
||||
Created at
|
||||
</Listbox.Option>
|
||||
<Listbox.Option
|
||||
id="name"
|
||||
className="max-w-[180px]"
|
||||
value="name"
|
||||
label="Sort by name"
|
||||
>
|
||||
Name
|
||||
</Listbox.Option>
|
||||
</Listbox>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-x-2">
|
||||
<DocsButton href="https://supabase.com/docs/guides/database/vault" />
|
||||
<ButtonTooltip
|
||||
type="primary"
|
||||
disabled={!canManageKeys}
|
||||
onClick={() => setShowAddKeyModal(true)}
|
||||
tooltip={{
|
||||
content: {
|
||||
side: 'bottom',
|
||||
text: !canManageKeys ? 'You need additional permissions to add keys' : undefined,
|
||||
},
|
||||
}}
|
||||
>
|
||||
Add new key
|
||||
</ButtonTooltip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Table of keys */}
|
||||
<div className="border rounded">
|
||||
{isLoading ? (
|
||||
<div className="px-6 py-6 space-x-2 flex items-center justify-center">
|
||||
<Loader className="animate-spin text-foreground-light" size={16} strokeWidth={1.5} />
|
||||
<p className="text-sm text-foreground">Loading keys from the Vault</p>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{keys.map((key, idx) => {
|
||||
return (
|
||||
<Fragment key={key.key_id}>
|
||||
<div className="px-6 py-4 flex items-center space-x-4">
|
||||
<Key className="text-foreground-light" strokeWidth={2} />
|
||||
<div className="space-y-1 min-w-[70%] max-w-[70%]">
|
||||
<p
|
||||
className="text-sm truncate text-foreground"
|
||||
title={key.name || DEFAULT_KEY_NAME}
|
||||
>
|
||||
{key.name || DEFAULT_KEY_NAME}
|
||||
</p>
|
||||
<p
|
||||
title={key.id}
|
||||
className="text-xs text-foreground-light font-bold truncate"
|
||||
>
|
||||
ID: <span className="font-mono">{key.id}</span>
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center justify-end w-[30%] space-x-4">
|
||||
<p className="text-sm text-foreground-light">
|
||||
Added on {dayjs(key.created).format('MMM D, YYYY')}
|
||||
</p>
|
||||
<ButtonTooltip
|
||||
type="default"
|
||||
className="py-2"
|
||||
icon={<Trash />}
|
||||
disabled={!canManageKeys}
|
||||
onClick={() => setSelectedKeyToRemove(key)}
|
||||
tooltip={{
|
||||
content: {
|
||||
side: 'bottom',
|
||||
text: !canManageKeys
|
||||
? 'You need additional permissions to delete keys'
|
||||
: undefined,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{idx !== keys.length - 1 && <Separator />}
|
||||
</Fragment>
|
||||
)
|
||||
})}
|
||||
{keys.length === 0 && (
|
||||
<>
|
||||
{searchValue.length === 0 ? (
|
||||
<div className="px-6 py-6 space-y-1 flex flex-col items-center justify-center">
|
||||
<p className="text-sm text-foreground">No encryption keys added yet</p>
|
||||
<p className="text-sm text-foreground-light text-center">
|
||||
Encryption keys are created by the pgsodium extension and can be used to
|
||||
encrypt your columns and secrets
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="px-6 py-4 space-y-1">
|
||||
<p className="text-sm text-foreground">No results found</p>
|
||||
<p className="text-sm text-foreground-light">
|
||||
Your search for "{searchValue}" did not return any results
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Modal
|
||||
size="medium"
|
||||
alignFooter="right"
|
||||
visible={selectedKeyToRemove !== undefined}
|
||||
onCancel={() => setSelectedKeyToRemove(undefined)}
|
||||
onConfirm={confirmDeleteKey}
|
||||
loading={isDeleting}
|
||||
header="Confirm to delete key"
|
||||
>
|
||||
<Modal.Content className="space-y-4">
|
||||
<Admonition
|
||||
type="warning"
|
||||
title="Deleting a key that's in use will cause any secret or column which depends on it to be
|
||||
unusable."
|
||||
description="Do ensure that the key is not currently in use to prevent any issues."
|
||||
/>
|
||||
<p className="text-sm">
|
||||
The following key will be permanently removed and cannot be recovered.
|
||||
</p>
|
||||
<div className="space-y-2">
|
||||
<p className="text-sm text-foreground">
|
||||
{selectedKeyToRemove?.name ?? DEFAULT_KEY_NAME}
|
||||
</p>
|
||||
<p className="text-xs text-foreground-light">
|
||||
<code className="!mx-0">ID: {selectedKeyToRemove?.id}</code>
|
||||
</p>
|
||||
</div>
|
||||
</Modal.Content>
|
||||
</Modal>
|
||||
|
||||
<Modal
|
||||
hideFooter
|
||||
size="medium"
|
||||
visible={showAddKeyModal}
|
||||
onCancel={() => setShowAddKeyModal(false)}
|
||||
header="Add a new key"
|
||||
>
|
||||
<Form
|
||||
id="add-new-key-form"
|
||||
validateOnBlur={false}
|
||||
initialValues={{ name: '' }}
|
||||
validate={(values: any) => {
|
||||
const errors: any = {}
|
||||
if (values.name.length === 0) errors.name = 'Please provide a name for your key'
|
||||
return errors
|
||||
}}
|
||||
onSubmit={addKey}
|
||||
>
|
||||
{() => {
|
||||
return (
|
||||
<>
|
||||
<Modal.Content className="space-y-4">
|
||||
<p className="text-sm">Provide a name for your key for easier identification.</p>
|
||||
<Input id="name" label="Key Name" />
|
||||
</Modal.Content>
|
||||
<Modal.Separator />
|
||||
<Modal.Content className="flex items-center justify-end space-x-2">
|
||||
<Button
|
||||
type="default"
|
||||
disabled={isCreating}
|
||||
onClick={() => setShowAddKeyModal(false)}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button htmlType="submit" disabled={isCreating} loading={isCreating}>
|
||||
Add key
|
||||
</Button>
|
||||
</Modal.Content>
|
||||
</>
|
||||
)
|
||||
}}
|
||||
</Form>
|
||||
</Modal>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -4,12 +4,9 @@ import { toast } from 'sonner'
|
||||
import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
|
||||
import InformationBox from 'components/ui/InformationBox'
|
||||
import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader'
|
||||
import { usePgSodiumKeyCreateMutation } from 'data/pg-sodium-keys/pg-sodium-key-create-mutation'
|
||||
import { usePgSodiumKeysQuery } from 'data/pg-sodium-keys/pg-sodium-keys-query'
|
||||
import { useVaultSecretCreateMutation } from 'data/vault/vault-secret-create-mutation'
|
||||
import { Eye, EyeOff, HelpCircle } from 'lucide-react'
|
||||
import { Button, Form, Input, Modal } from 'ui'
|
||||
import EncryptionKeySelector from '../Keys/EncryptionKeySelector'
|
||||
|
||||
interface AddNewSecretModalProps {
|
||||
visible: boolean
|
||||
@@ -18,35 +15,20 @@ interface AddNewSecretModalProps {
|
||||
|
||||
const AddNewSecretModal = ({ visible, onClose }: AddNewSecretModalProps) => {
|
||||
const [showSecretValue, setShowSecretValue] = useState(false)
|
||||
const [selectedKeyId, setSelectedKeyId] = useState<string>()
|
||||
const { project } = useProjectContext()
|
||||
|
||||
const { mutateAsync: addKeyMutation } = usePgSodiumKeyCreateMutation()
|
||||
const { mutateAsync: addSecret } = useVaultSecretCreateMutation()
|
||||
|
||||
// const { data: keys, isLoading } = usePgSodiumKeysQuery(
|
||||
// {
|
||||
// projectRef: project?.ref,
|
||||
// connectionString: project?.connectionString,
|
||||
// },
|
||||
// {
|
||||
// enabled: visible,
|
||||
// }
|
||||
// )
|
||||
|
||||
// useEffect(() => {
|
||||
// if (visible && keys) {
|
||||
// setShowSecretValue(false)
|
||||
// setSelectedKeyId(keys[0]?.id ?? 'create-new')
|
||||
// }
|
||||
// }, [visible, keys])
|
||||
useEffect(() => {
|
||||
if (visible) {
|
||||
setShowSecretValue(false)
|
||||
}
|
||||
}, [visible])
|
||||
|
||||
const validate = (values: any) => {
|
||||
const errors: any = {}
|
||||
if (values.name.length === 0) errors.name = 'Please provide a name for your secret'
|
||||
if (values.secret.length === 0) errors.secret = 'Please enter your secret value'
|
||||
if (selectedKeyId === 'create-new' && values.keyName.length === 0)
|
||||
errors.keyName = 'Please provide a name for your new key'
|
||||
return errors
|
||||
}
|
||||
|
||||
@@ -54,18 +36,9 @@ const AddNewSecretModal = ({ visible, onClose }: AddNewSecretModalProps) => {
|
||||
if (!project) return console.error('Project is required')
|
||||
|
||||
setSubmitting(true)
|
||||
let encryptionKeyId = selectedKeyId
|
||||
|
||||
try {
|
||||
setSubmitting(true)
|
||||
if (selectedKeyId === 'create-new') {
|
||||
const addKeyRes = await addKeyMutation({
|
||||
projectRef: project?.ref!,
|
||||
connectionString: project?.connectionString,
|
||||
name: values.keyName || undefined,
|
||||
})
|
||||
encryptionKeyId = addKeyRes[0].id
|
||||
}
|
||||
|
||||
await addSecret({
|
||||
projectRef: project.ref,
|
||||
@@ -73,7 +46,6 @@ const AddNewSecretModal = ({ visible, onClose }: AddNewSecretModalProps) => {
|
||||
name: values.name,
|
||||
description: values.description,
|
||||
secret: values.secret,
|
||||
key_id: encryptionKeyId,
|
||||
})
|
||||
toast.success(`Successfully added new secret ${values.name}`)
|
||||
onClose()
|
||||
@@ -86,11 +58,6 @@ const AddNewSecretModal = ({ visible, onClose }: AddNewSecretModalProps) => {
|
||||
|
||||
return (
|
||||
<Modal hideFooter size="medium" visible={visible} onCancel={onClose} header="Add new secret">
|
||||
{/* {isLoading ? (
|
||||
<Modal.Content>
|
||||
<GenericSkeletonLoader />
|
||||
</Modal.Content>
|
||||
) : ( */}
|
||||
<Form
|
||||
id="add-new-secret-form"
|
||||
initialValues={{ name: '', description: '', secret: '' }}
|
||||
@@ -120,35 +87,6 @@ const AddNewSecretModal = ({ visible, onClose }: AddNewSecretModalProps) => {
|
||||
/>
|
||||
</Modal.Content>
|
||||
<Modal.Separator />
|
||||
<Modal.Content className="space-y-4">
|
||||
<></>
|
||||
{/* <EncryptionKeySelector
|
||||
nameId="keyName"
|
||||
label="Select a key to encrypt your secret with"
|
||||
labelOptional="Optional"
|
||||
selectedKeyId={selectedKeyId}
|
||||
onSelectKey={setSelectedKeyId}
|
||||
/> */}
|
||||
{/* <InformationBox
|
||||
icon={<HelpCircle size={18} strokeWidth={2} />}
|
||||
url="https://github.com/supabase/vault"
|
||||
urlLabel="Vault documentation"
|
||||
title="What is a key?"
|
||||
description={
|
||||
<div className="space-y-2">
|
||||
<p>
|
||||
Keys are used to encrypt data inside your database, and every secret in the
|
||||
Vault is encrypted with a key.
|
||||
</p>
|
||||
<p>
|
||||
You may create different keys for different purposes, such as one for
|
||||
encrypting user data, and another for application data.
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
/> */}
|
||||
</Modal.Content>
|
||||
<Modal.Separator />
|
||||
<Modal.Content className="flex items-center justify-end space-x-2">
|
||||
<Button type="default" disabled={isSubmitting} onClick={onClose}>
|
||||
Cancel
|
||||
@@ -161,7 +99,6 @@ const AddNewSecretModal = ({ visible, onClose }: AddNewSecretModalProps) => {
|
||||
)
|
||||
}}
|
||||
</Form>
|
||||
{/* )} */}
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ const DeleteSecretModal = ({ selectedSecret, onClose }: DeleteSecretModalProps)
|
||||
<div className="space-y-1">
|
||||
<p className="text-sm">{selectedSecret?.description}</p>
|
||||
<p className="text-sm text-foreground-light">
|
||||
ID: <span className="font-mono">{selectedSecret?.key_id}</span>
|
||||
ID: <span className="font-mono">{selectedSecret?.id}</span>
|
||||
</p>
|
||||
</div>
|
||||
</Modal.Content>
|
||||
|
||||
@@ -4,12 +4,10 @@ import { toast } from 'sonner'
|
||||
|
||||
import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
|
||||
import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader'
|
||||
import { usePgSodiumKeyCreateMutation } from 'data/pg-sodium-keys/pg-sodium-key-create-mutation'
|
||||
import { useVaultSecretDecryptedValueQuery } from 'data/vault/vault-secret-decrypted-value-query'
|
||||
import { useVaultSecretUpdateMutation } from 'data/vault/vault-secret-update-mutation'
|
||||
import type { VaultSecret } from 'types'
|
||||
import { Button, Form, Input, Modal } from 'ui'
|
||||
import EncryptionKeySelector from '../Keys/EncryptionKeySelector'
|
||||
import { EyeOff, Eye } from 'lucide-react'
|
||||
|
||||
interface EditSecretModalProps {
|
||||
@@ -18,11 +16,9 @@ interface EditSecretModalProps {
|
||||
}
|
||||
|
||||
const EditSecretModal = ({ selectedSecret, onClose }: EditSecretModalProps) => {
|
||||
const [selectedKeyId, setSelectedKeyId] = useState<string>()
|
||||
const [showSecretValue, setShowSecretValue] = useState(false)
|
||||
const { project } = useProjectContext()
|
||||
|
||||
const { mutateAsync: addKeyMutation } = usePgSodiumKeyCreateMutation()
|
||||
const { mutateAsync: updateSecret } = useVaultSecretUpdateMutation()
|
||||
|
||||
let INITIAL_VALUES = {
|
||||
@@ -34,7 +30,6 @@ const EditSecretModal = ({ selectedSecret, onClose }: EditSecretModalProps) => {
|
||||
useEffect(() => {
|
||||
if (selectedSecret !== undefined) {
|
||||
setShowSecretValue(false)
|
||||
setSelectedKeyId(selectedSecret.key_id)
|
||||
}
|
||||
}, [selectedSecret])
|
||||
|
||||
@@ -53,23 +48,6 @@ const EditSecretModal = ({ selectedSecret, onClose }: EditSecretModalProps) => {
|
||||
if (values.name !== selectedSecret?.name) payload.name = values.name
|
||||
if (values.description !== selectedSecret?.description)
|
||||
payload.description = values.description
|
||||
if (selectedKeyId !== selectedSecret?.key_id) {
|
||||
let encryptionKeyId = selectedKeyId
|
||||
if (encryptionKeyId === 'create-new') {
|
||||
const addKeyRes = await addKeyMutation({
|
||||
projectRef: project?.ref!,
|
||||
connectionString: project?.connectionString,
|
||||
name: values.keyName || undefined,
|
||||
})
|
||||
if (addKeyRes.error) {
|
||||
return toast.error(`Failed to create new key: ${addKeyRes.error.message}`)
|
||||
} else {
|
||||
encryptionKeyId = addKeyRes[0].id
|
||||
}
|
||||
}
|
||||
|
||||
payload.key_id = encryptionKeyId
|
||||
}
|
||||
payload.secret = values.secret
|
||||
|
||||
if (!isEmpty(payload) && selectedSecret) {
|
||||
@@ -161,17 +139,6 @@ const EditSecretModal = ({ selectedSecret, onClose }: EditSecretModalProps) => {
|
||||
}
|
||||
/>
|
||||
</Modal.Content>
|
||||
{/*
|
||||
<Modal.Separator />
|
||||
<Modal.Content className="space-y-4">
|
||||
<EncryptionKeySelector
|
||||
nameId="keyName"
|
||||
label="Select a key to encrypt your secret with"
|
||||
labelOptional="Optional"
|
||||
selectedKeyId={selectedKeyId}
|
||||
onSelectKey={setSelectedKeyId}
|
||||
/>
|
||||
</Modal.Content> */}
|
||||
<Modal.Separator />
|
||||
<Modal.Content className="flex items-center justify-end space-x-2">
|
||||
<Button type="default" disabled={isSubmitting} onClick={() => onClose()}>
|
||||
|
||||
@@ -61,18 +61,10 @@ const SecretRow = ({ secret, onSelectEdit, onSelectRemove }: SecretRowProps) =>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center space-x-2 group">
|
||||
<Key
|
||||
size={14}
|
||||
strokeWidth={2}
|
||||
className="text-foreground-light transition group-hover:text-brand"
|
||||
/>
|
||||
<Link
|
||||
href={`/project/${ref}/integrations/vault/keys?search=${secret.key_id}`}
|
||||
className="text-foreground-light font-mono text-xs cursor-pointer transition group-hover:text-brand"
|
||||
title={secret.key_id}
|
||||
>
|
||||
{secret.key_id}
|
||||
</Link>
|
||||
<Key size={14} strokeWidth={2} className="text-foreground-light transition" />
|
||||
<p className="text-foreground-light font-mono text-xs transition" title={secret.id}>
|
||||
{secret.id}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2 w-[40%]">
|
||||
|
||||
@@ -39,10 +39,8 @@ export const SecretsManagement = () => {
|
||||
const allSecrets = data || []
|
||||
const secrets = sortBy(
|
||||
searchValue.length > 0
|
||||
? allSecrets.filter(
|
||||
(secret) =>
|
||||
(secret?.name ?? '').toLowerCase().includes(searchValue.toLowerCase()) ||
|
||||
secret.key_id.toLowerCase().includes(searchValue.toLowerCase())
|
||||
? allSecrets.filter((secret) =>
|
||||
(secret?.name ?? '').toLowerCase().includes(searchValue.toLowerCase())
|
||||
)
|
||||
: allSecrets,
|
||||
(s) => {
|
||||
@@ -99,14 +97,6 @@ export const SecretsManagement = () => {
|
||||
>
|
||||
Name
|
||||
</Listbox.Option>
|
||||
<Listbox.Option
|
||||
id="key_id"
|
||||
className="max-w-[180px]"
|
||||
value="key_id"
|
||||
label="Sort by key ID"
|
||||
>
|
||||
Key ID
|
||||
</Listbox.Option>
|
||||
</Listbox>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
} from 'components/interfaces/Integrations/Wrappers/Wrappers.types'
|
||||
import { entityTypeKeys } from 'data/entity-types/keys'
|
||||
import { foreignTableKeys } from 'data/foreign-tables/keys'
|
||||
import { pgSodiumKeys } from 'data/pg-sodium-keys/keys'
|
||||
import { executeSql } from 'data/sql/execute-sql-query'
|
||||
import { wrapWithTransaction } from 'data/sql/utils/transaction'
|
||||
import { vaultSecretsKeys } from 'data/vault/keys'
|
||||
@@ -243,7 +242,6 @@ export const useFDWCreateMutation = ({
|
||||
queryClient.invalidateQueries(fdwKeys.list(projectRef), { refetchType: 'all' }),
|
||||
queryClient.invalidateQueries(entityTypeKeys.list(projectRef)),
|
||||
queryClient.invalidateQueries(foreignTableKeys.list(projectRef)),
|
||||
queryClient.invalidateQueries(pgSodiumKeys.list(projectRef)),
|
||||
queryClient.invalidateQueries(vaultSecretsKeys.list(projectRef)),
|
||||
])
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ import { toast } from 'sonner'
|
||||
import type { WrapperMeta } from 'components/interfaces/Integrations/Wrappers/Wrappers.types'
|
||||
import { entityTypeKeys } from 'data/entity-types/keys'
|
||||
import { foreignTableKeys } from 'data/foreign-tables/keys'
|
||||
import { pgSodiumKeys } from 'data/pg-sodium-keys/keys'
|
||||
import { executeSql } from 'data/sql/execute-sql-query'
|
||||
import { wrapWithTransaction } from 'data/sql/utils/transaction'
|
||||
import { vaultSecretsKeys } from 'data/vault/keys'
|
||||
@@ -113,7 +112,6 @@ export const useFDWDeleteMutation = ({
|
||||
queryClient.invalidateQueries(fdwKeys.list(projectRef), { refetchType: 'all' }),
|
||||
queryClient.invalidateQueries(entityTypeKeys.list(projectRef)),
|
||||
queryClient.invalidateQueries(foreignTableKeys.list(projectRef)),
|
||||
queryClient.invalidateQueries(pgSodiumKeys.list(projectRef)),
|
||||
queryClient.invalidateQueries(vaultSecretsKeys.list(projectRef)),
|
||||
])
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ import { toast } from 'sonner'
|
||||
import type { WrapperMeta } from 'components/interfaces/Integrations/Wrappers/Wrappers.types'
|
||||
import { entityTypeKeys } from 'data/entity-types/keys'
|
||||
import { foreignTableKeys } from 'data/foreign-tables/keys'
|
||||
import { pgSodiumKeys } from 'data/pg-sodium-keys/keys'
|
||||
import { executeSql } from 'data/sql/execute-sql-query'
|
||||
import { wrapWithTransaction } from 'data/sql/utils/transaction'
|
||||
import { vaultSecretsKeys } from 'data/vault/keys'
|
||||
@@ -76,7 +75,6 @@ export const useFDWUpdateMutation = ({
|
||||
queryClient.invalidateQueries(fdwKeys.list(projectRef), { refetchType: 'all' }),
|
||||
queryClient.invalidateQueries(entityTypeKeys.list(projectRef)),
|
||||
queryClient.invalidateQueries(foreignTableKeys.list(projectRef)),
|
||||
queryClient.invalidateQueries(pgSodiumKeys.list(projectRef)),
|
||||
queryClient.invalidateQueries(vaultSecretsKeys.list(projectRef)),
|
||||
])
|
||||
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
export const pgSodiumKeys = {
|
||||
list: (projectRef: string | undefined) => ['projects', projectRef, 'pg-sodium-keys'] as const,
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
import { executeSql } from 'data/sql/execute-sql-query'
|
||||
import type { ResponseError } from 'types'
|
||||
import { pgSodiumKeys } from './keys'
|
||||
|
||||
export type CreateEncryptionKeyVariables = {
|
||||
projectRef: string
|
||||
connectionString?: string
|
||||
name: string
|
||||
}
|
||||
|
||||
export async function createEncryptionKey({
|
||||
projectRef,
|
||||
connectionString,
|
||||
name,
|
||||
}: CreateEncryptionKeyVariables) {
|
||||
const sql =
|
||||
name !== undefined
|
||||
? `select * from pgsodium.create_key(name := '${name}')`
|
||||
: `select * from pgsodium.create_key()`
|
||||
|
||||
const { result } = await executeSql({
|
||||
projectRef,
|
||||
connectionString,
|
||||
sql,
|
||||
queryKey: ['projects', projectRef, 'pg-sodium-keys', 'create'],
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
type EncryptionKeyCreateData = Awaited<ReturnType<typeof createEncryptionKey>>
|
||||
|
||||
export const usePgSodiumKeyCreateMutation = ({
|
||||
onError,
|
||||
onSuccess,
|
||||
...options
|
||||
}: Omit<
|
||||
UseMutationOptions<EncryptionKeyCreateData, ResponseError, CreateEncryptionKeyVariables>,
|
||||
'mutationFn'
|
||||
> = {}) => {
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
return useMutation<EncryptionKeyCreateData, ResponseError, CreateEncryptionKeyVariables>(
|
||||
(vars) => createEncryptionKey(vars),
|
||||
{
|
||||
async onSuccess(data, variables, context) {
|
||||
const { projectRef } = variables
|
||||
await queryClient.invalidateQueries(pgSodiumKeys.list(projectRef))
|
||||
await onSuccess?.(data, variables, context)
|
||||
},
|
||||
async onError(data, variables, context) {
|
||||
if (onError === undefined) {
|
||||
toast.error(`Failed to create key: ${data.message}`)
|
||||
} else {
|
||||
onError(data, variables, context)
|
||||
}
|
||||
},
|
||||
...options,
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
import { Query } from '@supabase/pg-meta/src/query'
|
||||
import { executeSql } from 'data/sql/execute-sql-query'
|
||||
import type { ResponseError } from 'types'
|
||||
import { pgSodiumKeys } from './keys'
|
||||
|
||||
export type VaultSecretDeleteVariables = {
|
||||
projectRef: string
|
||||
connectionString?: string
|
||||
id: string
|
||||
}
|
||||
|
||||
export async function deleteVaultSecret({
|
||||
projectRef,
|
||||
connectionString,
|
||||
id,
|
||||
}: VaultSecretDeleteVariables) {
|
||||
const sql = new Query().from('key', 'pgsodium').delete().match({ id }).toSql()
|
||||
const { result } = await executeSql({
|
||||
projectRef,
|
||||
connectionString,
|
||||
sql,
|
||||
queryKey: ['projects', projectRef, 'pg-sodium-keys', 'delete', id],
|
||||
})
|
||||
return result
|
||||
}
|
||||
|
||||
type VaultSecretDeleteData = Awaited<ReturnType<typeof deleteVaultSecret>>
|
||||
|
||||
export const usePgSodiumKeyDeleteMutation = ({
|
||||
onError,
|
||||
onSuccess,
|
||||
...options
|
||||
}: Omit<
|
||||
UseMutationOptions<VaultSecretDeleteData, ResponseError, VaultSecretDeleteVariables>,
|
||||
'mutationFn'
|
||||
> = {}) => {
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
return useMutation<VaultSecretDeleteData, ResponseError, VaultSecretDeleteVariables>(
|
||||
(vars) => deleteVaultSecret(vars),
|
||||
{
|
||||
async onSuccess(data, variables, context) {
|
||||
const { projectRef } = variables
|
||||
await queryClient.invalidateQueries(pgSodiumKeys.list(projectRef))
|
||||
await onSuccess?.(data, variables, context)
|
||||
},
|
||||
async onError(data, variables, context) {
|
||||
if (onError === undefined) {
|
||||
toast.error(`Failed to delete key: ${data.message}`)
|
||||
} else {
|
||||
onError(data, variables, context)
|
||||
}
|
||||
},
|
||||
...options,
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
import { UseQueryOptions, useQuery } from '@tanstack/react-query'
|
||||
import { Query } from '@supabase/pg-meta/src/query'
|
||||
import { sortBy } from 'lodash'
|
||||
import { executeSql } from '../sql/execute-sql-query'
|
||||
import { pgSodiumKeys } from './keys'
|
||||
|
||||
export interface EncryptionKey {
|
||||
id: string
|
||||
key_id: number
|
||||
name: string
|
||||
comment: string
|
||||
created: string
|
||||
status: string
|
||||
}
|
||||
|
||||
export const getPgSodiumKeysQuery = () => {
|
||||
const sql = new Query()
|
||||
.from('key', 'pgsodium')
|
||||
.select('id,key_id,name,comment,created,status')
|
||||
.toSql()
|
||||
|
||||
return sql
|
||||
}
|
||||
|
||||
export type PgSodiumKeysVariables = {
|
||||
projectRef?: string
|
||||
connectionString?: string
|
||||
}
|
||||
|
||||
const getPgSodiumKeys = async (
|
||||
{ projectRef, connectionString }: PgSodiumKeysVariables,
|
||||
signal?: AbortSignal
|
||||
) => {
|
||||
const sql = getPgSodiumKeysQuery()
|
||||
const { result } = await executeSql(
|
||||
{
|
||||
projectRef,
|
||||
connectionString,
|
||||
sql,
|
||||
queryKey: pgSodiumKeys.list(projectRef),
|
||||
},
|
||||
signal
|
||||
)
|
||||
return result
|
||||
}
|
||||
|
||||
export type PgSodiumKeysData = EncryptionKey[]
|
||||
export type PgSodiumKeysError = unknown
|
||||
|
||||
export const usePgSodiumKeysQuery = <TData = PgSodiumKeysData>(
|
||||
{ projectRef, connectionString }: PgSodiumKeysVariables,
|
||||
{ enabled = true, ...options }: UseQueryOptions<PgSodiumKeysData, PgSodiumKeysError, TData> = {}
|
||||
) =>
|
||||
useQuery<PgSodiumKeysData, PgSodiumKeysError, TData>(
|
||||
pgSodiumKeys.list(projectRef),
|
||||
({ signal }) => getPgSodiumKeys({ projectRef, connectionString }, signal),
|
||||
{
|
||||
select(data) {
|
||||
return sortBy(data, (k) => Number(new Date(k.created))) as any
|
||||
},
|
||||
enabled: enabled && typeof projectRef !== 'undefined',
|
||||
...options,
|
||||
}
|
||||
)
|
||||
@@ -16,13 +16,12 @@ export async function createVaultSecret({
|
||||
connectionString,
|
||||
...newSecret
|
||||
}: VaultSecretCreateVariables) {
|
||||
const { name, description, secret, key_id } = newSecret
|
||||
const { name, description, secret } = newSecret
|
||||
const sql = /* SQL */ `
|
||||
select vault.create_secret(
|
||||
new_secret := ${quoteLiteral(secret)}
|
||||
${name ? `, new_name := ${quoteLiteral(name)}` : ''}
|
||||
${description ? `, new_description := ${quoteLiteral(description)}` : ''}
|
||||
${key_id ? `, new_key_id := ${quoteLiteral(key_id)}` : ''}
|
||||
)
|
||||
`
|
||||
|
||||
|
||||
@@ -18,14 +18,13 @@ export async function updateVaultSecret({
|
||||
id,
|
||||
...payload
|
||||
}: VaultSecretUpdateVariables) {
|
||||
const { name, description, secret, key_id } = payload
|
||||
const { name, description, secret } = payload
|
||||
const sql = /* SQL */ `
|
||||
select vault.update_secret(
|
||||
secret_id := ${quoteLiteral(id)}
|
||||
${secret ? `, new_secret := ${quoteLiteral(secret)}` : ''}
|
||||
${name ? `, new_name := ${quoteLiteral(name)}` : ''}
|
||||
${description ? `, new_description := ${quoteLiteral(description)}` : ''}
|
||||
${key_id ? `, new_key_id := ${quoteLiteral(key_id)}` : ''}
|
||||
)
|
||||
`
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import { vaultSecretsKeys } from './keys'
|
||||
export const getVaultSecretsSql = () => {
|
||||
const sql = new Query()
|
||||
.from('secrets', 'vault')
|
||||
.select('id,name,description,secret,key_id,created_at,updated_at')
|
||||
.select('id,name,description,secret,created_at,updated_at')
|
||||
.toSql()
|
||||
|
||||
return sql
|
||||
|
||||
@@ -31,7 +31,6 @@ export interface VaultSecret {
|
||||
description: string
|
||||
secret: string
|
||||
decryptedSecret?: string
|
||||
key_id: string
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user