fix: column encryption support for other attributes

This commit is contained in:
Harsh Mahajan
2026-02-25 17:59:23 +05:30
parent 301caeb4e1
commit 0e6a0a171e
8 changed files with 167 additions and 16 deletions
@@ -15,7 +15,7 @@
Tooltip,
Typography
} from '@appwrite.io/pink-svelte';
import { isRelationship, isSpatialType, isString } from '../rows/store';
import { isRelationship, isSpatialType, isTextType } from '../rows/store';
import {
columns,
type Columns,
@@ -443,7 +443,7 @@
{/if}
</Typography.Text>
{#if isString(column) && column.encrypt}
{#if isTextType(column) && 'encrypt' in column && column.encrypt}
<Tooltip portal>
<Icon
size="s"
@@ -0,0 +1,107 @@
<script lang="ts">
import { page } from '$app/state';
import { isCloud } from '$lib/system';
import { getChangePlanUrl } from '$lib/stores/billing';
import { currentPlan } from '$lib/stores/organization';
import {
ActionMenu,
Popover,
Layout,
Selector,
Tag,
Tooltip,
Typography,
Link
} from '@appwrite.io/pink-svelte';
let {
encrypt = $bindable(false),
editing = false,
disabled = false,
id = 'encrypt'
}: {
encrypt?: boolean;
editing?: boolean;
disabled?: boolean;
id?: string;
} = $props();
const organizationId = page.data?.organization?.$id ?? page.data?.project?.$id;
const supportsEncryption = isCloud ? $currentPlan?.databasesAllowEncrypt : true;
</script>
<Tooltip disabled={!(editing || disabled)} maxWidth="275px" placement="bottom-start">
<div
class="popover-holder"
class:cursor-not-allowed={editing || disabled}
class:disabled-checkbox={!supportsEncryption || editing || disabled}>
<Layout.Stack inline gap="s" alignItems="flex-start" direction="row">
<Popover let:toggle placement="bottom-start">
<Selector.Checkbox
size="s"
{id}
bind:checked={encrypt}
disabled={!supportsEncryption || editing || disabled} />
<Layout.Stack gap="xxs" direction="column">
<button
type="button"
disabled={editing || disabled}
class:cursor-pointer={!editing}
class:cursor-not-allowed={editing || disabled}
on:click={(e) => {
if (!supportsEncryption) {
toggle(e);
} else {
encrypt = !encrypt;
}
}}>
<Layout.Stack inline gap="xxs" direction="row" alignItems="center">
<Typography.Text variant="m-500">Encrypted</Typography.Text>
{#if !supportsEncryption}
<Tag variant="default" size="xs" on:click={toggle}>Pro</Tag>
{/if}
</Layout.Stack>
</button>
<Typography.Text color="--fgcolor-neutral-tertiary">
Protect column against data leaks for best privacy compliance. Encrypted
columns cannot be queried.
</Typography.Text>
</Layout.Stack>
<ActionMenu.Root width="180px" slot="tooltip">
<Typography.Text variant="m-500">
Available on Pro plan. <Link.Anchor href={getChangePlanUrl(organizationId)}
>Upgrade</Link.Anchor>
to enable encrypted columns.
</Typography.Text>
</ActionMenu.Root>
</Popover>
</Layout.Stack>
</div>
<svelte:fragment slot="tooltip">
Encryption can only be set when creating the column.
</svelte:fragment>
</Tooltip>
<style lang="scss">
.popover-holder {
& :global([role='tooltip']) {
margin-top: 4px;
left: 8rem !important;
}
&.disabled-checkbox :global(button) {
cursor: unset;
}
}
.cursor-pointer {
cursor: pointer !important;
}
.cursor-not-allowed {
cursor: not-allowed;
}
</style>
@@ -17,7 +17,8 @@
key,
required: data.required,
xdefault: data.default,
array: data.array
array: data.array,
encrypt: data.encrypt
});
}
export async function updateLongtext(
@@ -42,17 +43,21 @@
<script lang="ts">
import { createConservative } from '$lib/helpers/stores';
import RequiredArrayCheckboxes from './requiredArrayCheckboxes.svelte';
import EncryptCheckbox from './encryptCheckbox.svelte';
import { InputTextarea } from '$lib/elements/forms';
import { Layout, Typography } from '@appwrite.io/pink-svelte';
export let data: Partial<Models.ColumnLongtext> = {
required: false,
array: false
array: false,
encrypt: false
};
export let editing = false;
export let disabled = false;
if (data && (data.encrypt === undefined || data.encrypt === null)) data.encrypt = false;
let savedDefault = data.default;
function handleDefaultState(hideDefault: boolean) {
@@ -97,3 +102,7 @@
{disabled}
bind:array={data.array}
bind:required={data.required} />
<Layout.Stack gap="xs" direction="column">
<EncryptCheckbox id="encrypt-longtext" bind:encrypt={data.encrypt} {editing} {disabled} />
</Layout.Stack>
@@ -17,7 +17,8 @@
key,
required: data.required,
xdefault: data.default,
array: data.array
array: data.array,
encrypt: data.encrypt
});
}
export async function updateMediumtext(
@@ -42,17 +43,21 @@
<script lang="ts">
import { createConservative } from '$lib/helpers/stores';
import RequiredArrayCheckboxes from './requiredArrayCheckboxes.svelte';
import EncryptCheckbox from './encryptCheckbox.svelte';
import { InputTextarea } from '$lib/elements/forms';
import { Layout, Typography } from '@appwrite.io/pink-svelte';
export let data: Partial<Models.ColumnMediumtext> = {
required: false,
array: false
array: false,
encrypt: false
};
export let editing = false;
export let disabled = false;
if (data && (data.encrypt === undefined || data.encrypt === null)) data.encrypt = false;
let savedDefault = data.default;
function handleDefaultState(hideDefault: boolean) {
@@ -97,3 +102,7 @@
{disabled}
bind:array={data.array}
bind:required={data.required} />
<Layout.Stack gap="xs" direction="column">
<EncryptCheckbox id="encrypt-mediumtext" bind:encrypt={data.encrypt} {editing} {disabled} />
</Layout.Stack>
@@ -15,7 +15,8 @@
key,
required: data.required,
xdefault: data.default,
array: data.array
array: data.array,
encrypt: data.encrypt
});
}
export async function updateText(
@@ -38,17 +39,21 @@
<script lang="ts">
import { createConservative } from '$lib/helpers/stores';
import RequiredArrayCheckboxes from './requiredArrayCheckboxes.svelte';
import EncryptCheckbox from './encryptCheckbox.svelte';
import { InputTextarea } from '$lib/elements/forms';
import { Layout, Typography } from '@appwrite.io/pink-svelte';
export let data: Partial<Models.ColumnText> = {
required: false,
array: false
array: false,
encrypt: false
};
export let editing = false;
export let disabled = false;
if (data && (data.encrypt === undefined || data.encrypt === null)) data.encrypt = false;
let savedDefault = data.default;
function handleDefaultState(hideDefault: boolean) {
@@ -93,3 +98,7 @@
{disabled}
bind:array={data.array}
bind:required={data.required} />
<Layout.Stack gap="xs" direction="column">
<EncryptCheckbox id="encrypt-text" bind:encrypt={data.encrypt} {editing} {disabled} />
</Layout.Stack>
@@ -16,7 +16,8 @@
size: data.size,
required: data.required,
xdefault: data.default,
array: data.array
array: data.array,
encrypt: data.encrypt
});
}
export async function updateVarchar(
@@ -45,16 +46,20 @@
import { ProgressBar } from '$lib/components';
import { Layout, Typography, Tooltip, Icon } from '@appwrite.io/pink-svelte';
import { IconInfo } from '@appwrite.io/pink-icons-svelte';
import EncryptCheckbox from './encryptCheckbox.svelte';
export let data: Partial<Models.ColumnVarchar> = {
required: false,
size: 255,
array: false
array: false,
encrypt: false
};
export let editing = false;
export let disabled = false;
if (data && (data.encrypt === undefined || data.encrypt === null)) data.encrypt = false;
// Local size for reactivity
let size = data.size ?? 255;
$: data.size = size;
@@ -118,6 +123,10 @@
$: listen(data);
$: handleDefaultState($required || $array);
$: if (data.encrypt && size < 150) {
size = 150;
}
</script>
<InputNumber
@@ -127,8 +136,11 @@
{disabled}
placeholder="Enter size"
bind:value={size}
min={1}
max={16383} />
min={data.encrypt ? 150 : 1}
max={16383}
helper={data.encrypt
? 'Encrypted varchar columns require a minimum size of 150.'
: undefined} />
{#if !editing}
<Layout.Stack gap="xs">
@@ -173,3 +185,7 @@
{disabled}
bind:array={data.array}
bind:required={data.required} />
<Layout.Stack gap="xs" direction="column">
<EncryptCheckbox id="encrypt-varchar" bind:encrypt={data.encrypt} {editing} {disabled} />
</Layout.Stack>
@@ -47,6 +47,7 @@
required: column?.required ?? false,
array: column?.array ?? false,
default: column?.default ?? null,
encrypt: (column as { encrypt?: boolean })?.encrypt ?? false,
...column
} as Partial<Columns>);
@@ -60,7 +61,8 @@
data = {
required: false,
array: false,
default: null
default: null,
encrypt: false
};
/* default to text */
@@ -16,8 +16,7 @@
buildWildcardColumnsQuery,
isRelationship,
isRelationshipToMany,
isSpatialType,
isString
isSpatialType, isTextType
} from './rows/store';
import {
columns,
@@ -1072,7 +1071,7 @@
{@const isEmptyArray = formatted === 'Empty'}
{@const isDatetimeAttribute = rowColumn.type === 'datetime'}
{@const isEncryptedAttribute =
isString(rowColumn) && rowColumn.encrypt}
isTextType(rowColumn) && 'encrypt' in rowColumn && rowColumn.encrypt}
{#if isDatetimeAttribute}
<DualTimeView time={value}>
<span slot="title">Timestamp</span>