Merge pull request #1882 from appwrite/dat-534-string-encryption

string encryption in create attribute
This commit is contained in:
Darshan
2025-05-28 18:11:50 +05:30
committed by GitHub
16 changed files with 177 additions and 102 deletions
+1 -2
View File
@@ -23,11 +23,10 @@
"dependencies": {
"@ai-sdk/svelte": "^1.1.24",
"@appwrite.io/console": "https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@e190a19",
"@appwrite.io/pink": "0.25.0",
"@appwrite.io/pink-icons": "0.25.0",
"@appwrite.io/pink-icons-svelte": "^2.0.0-RC.1",
"@appwrite.io/pink-legacy": "^1.0.3",
"@appwrite.io/pink-svelte": "https://try-module.cloud/module/@appwrite/@appwrite.io/pink-svelte@0cc439f",
"@appwrite.io/pink-svelte": "https://try-module.cloud/-/@appwrite/@appwrite.io/pink-svelte@063404f",
"@popperjs/core": "^2.11.8",
"@sentry/sveltekit": "^8.38.0",
"@stripe/stripe-js": "^3.5.0",
+9 -26
View File
@@ -14,9 +14,6 @@ importers:
'@appwrite.io/console':
specifier: https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@e190a19
version: https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@e190a19
'@appwrite.io/pink':
specifier: 0.25.0
version: 0.25.0
'@appwrite.io/pink-icons':
specifier: 0.25.0
version: 0.25.0
@@ -27,8 +24,8 @@ importers:
specifier: ^1.0.3
version: 1.0.3
'@appwrite.io/pink-svelte':
specifier: https://try-module.cloud/module/@appwrite/@appwrite.io/pink-svelte@0cc439f
version: https://try-module.cloud/module/@appwrite/%40appwrite.io%2Fpink-svelte@0cc439f(svelte@5.25.3)
specifier: https://try-module.cloud/-/@appwrite/@appwrite.io/pink-svelte@063404f
version: https://try-module.cloud/-/@appwrite/%40appwrite.io%2Fpink-svelte@063404f(svelte@5.25.3)
'@popperjs/core':
specifier: ^2.11.8
version: 2.11.8
@@ -281,15 +278,12 @@ packages:
'@appwrite.io/pink-legacy@1.0.3':
resolution: {integrity: sha512-GGde5fmPhs+s6/3aFeMPc/kKADG/gTFkYQSy6oBN8pK0y0XNCLrZZgBv+EBbdhwdtqVEWXa0X85Mv9w7jcIlwQ==}
'@appwrite.io/pink-svelte@https://try-module.cloud/module/@appwrite/%40appwrite.io%2Fpink-svelte@0cc439f':
resolution: {tarball: https://try-module.cloud/module/@appwrite/%40appwrite.io%2Fpink-svelte@0cc439f}
'@appwrite.io/pink-svelte@https://try-module.cloud/-/@appwrite/%40appwrite.io%2Fpink-svelte@063404f':
resolution: {tarball: https://try-module.cloud/-/@appwrite/%40appwrite.io%2Fpink-svelte@063404f}
version: 2.0.0-RC.2
peerDependencies:
svelte: ^4.0.0
'@appwrite.io/pink@0.25.0':
resolution: {integrity: sha512-9z4G/iAhjvVpM7tkNqzGqrokst4hvNt3Fkf2jD4W6n8j//EJbQ3mJ0I+5dye5Sry0a33wUanq5DSztqFNYq5zw==}
'@asamuzakjp/css-color@3.1.1':
resolution: {integrity: sha512-hpRD68SV2OMcZCsrbdkccTw5FXjNDLo5OuqSHyHZfwweGsDWZwDJ2+gONyNAbazZclobMirACLw0lk8WVxIqxA==}
@@ -1343,8 +1337,8 @@ packages:
'@types/prop-types@15.7.14':
resolution: {integrity: sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==}
'@types/react@18.3.22':
resolution: {integrity: sha512-vUhG0YmQZ7kL/tmKLrD3g5zXbXXreZXB3pmROW8bg3CnLnpjkRVwUlLne7Ufa2r9yJ8+/6B73RzhAek5TBKh2Q==}
'@types/react@18.3.23':
resolution: {integrity: sha512-/LDXMQh55EzZQ0uVAZmKKhfENivEvWz6E+EYzh+/MCjMhNsotd+ZHhBGIjFDTi6+fz0OhQQQLbTgdQIxxCsC0w==}
'@types/remarkable@2.0.8':
resolution: {integrity: sha512-eKXqPZfpQl1kOADjdKchHrp2gwn9qMnGXhH/AtZe0UrklzhGJkawJo/Y/D0AlWcdWoWamFNIum8+/nkAISQVGg==}
@@ -2664,9 +2658,6 @@ packages:
resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
engines: {node: '>=0.10.0'}
normalize.css@8.0.1:
resolution: {integrity: sha512-qizSNPO93t1YUuUhP22btGOo3chcvDFqFaj2TRybP0DMxkHOCTYwp3n34fel4a31ORXy4m1Xq0Gyqpb5m33qIg==}
nwsapi@2.2.20:
resolution: {integrity: sha512-/ieB+mDe4MrrKMT8z+mQL8klXydZWGR5Dowt4RAGKbJ3kIGEx3X4ljUo+6V73IXtUPWgfOlU5B9MlGxFO5T+cA==}
@@ -3644,7 +3635,7 @@ snapshots:
'@appwrite.io/pink-icons': 1.0.0
the-new-css-reset: 1.11.3
'@appwrite.io/pink-svelte@https://try-module.cloud/module/@appwrite/%40appwrite.io%2Fpink-svelte@0cc439f(svelte@5.25.3)':
'@appwrite.io/pink-svelte@https://try-module.cloud/-/@appwrite/%40appwrite.io%2Fpink-svelte@063404f(svelte@5.25.3)':
dependencies:
'@appwrite.io/pink-icons-svelte': 2.0.0-RC.1(svelte@5.25.3)
'@floating-ui/dom': 1.6.13
@@ -3659,12 +3650,6 @@ snapshots:
svelte-motion: 0.12.2(svelte@5.25.3)
svelte-sonner: 0.3.28(svelte@5.25.3)
'@appwrite.io/pink@0.25.0':
dependencies:
'@appwrite.io/pink-icons': 0.25.0
normalize.css: 8.0.1
the-new-css-reset: 1.11.3
'@asamuzakjp/css-color@3.1.1':
dependencies:
'@csstools/css-calc': 2.1.2(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)
@@ -4787,7 +4772,7 @@ snapshots:
'@types/prop-types@15.7.14': {}
'@types/react@18.3.22':
'@types/react@18.3.23':
dependencies:
'@types/prop-types': 15.7.14
csstype: 3.1.3
@@ -6254,8 +6239,6 @@ snapshots:
normalize-path@3.0.0: {}
normalize.css@8.0.1: {}
nwsapi@2.2.20: {}
object-inspect@1.13.4: {}
@@ -6735,7 +6718,7 @@ snapshots:
svelte-motion@0.12.2(svelte@5.25.3):
dependencies:
'@types/react': 18.3.22
'@types/react': 18.3.23
framesync: 6.1.2
popmotion: 11.0.5
style-value-types: 5.1.2
+1
View File
@@ -44,6 +44,7 @@ export type Column = PinkColumn & {
format?: string;
exclude?: boolean;
elements?: string[] | { value: string | number; label: string }[];
encrypt?: boolean;
};
export function isValueOfStringEnum<T extends Record<string, string>>(
+1
View File
@@ -338,6 +338,7 @@ export type Plan = {
backupPolicies: number;
emailBranding: boolean;
supportsCredits: boolean;
databasesAllowEncrypt: boolean;
buildSize: number; // in MB
deploymentSize: number; // in MB
};
@@ -13,10 +13,11 @@
Link,
Popover,
Table,
Tooltip,
Typography
} from '@appwrite.io/pink-svelte';
import Create from '../createAttribute.svelte';
import { isRelationship } from '../document-[document]/attributes/store';
import { isRelationship, isString } from '../document-[document]/attributes/store';
import FailedModal from '../failedModal.svelte';
import CreateIndex from '../indexes/createIndex.svelte';
import { attributes, type Attributes, isCsvImportInProgress } from '../store';
@@ -33,7 +34,8 @@
IconPlus,
IconSwitchHorizontal,
IconTrash,
IconViewList
IconViewList,
IconLockClosed
} from '@appwrite.io/pink-icons-svelte';
import type { ComponentProps } from 'svelte';
import { Click, trackEvent } from '$lib/actions/analytics';
@@ -112,25 +114,38 @@
{:else}
<Icon icon={option.icon} size="s" />
{/if}
<span class="text u-trim-1" data-private>{attribute.key}</span>
{#if attribute.status !== 'available'}
<Badge
size="s"
variant="secondary"
content={attribute.status}
type={getAttributeStatusBadge(attribute.status)} />
{#if attribute.error}
<Link.Button
variant="muted"
on:click={(e) => {
e.preventDefault();
error = attribute.error;
showFailed = true;
}}>Details</Link.Button>
<Layout.Stack direction="row" alignItems="center" gap="s">
<Layout.Stack inline direction="row" alignItems="center" gap="xxs">
<span class="text u-trim-1" data-private>{attribute.key}</span>
{#if isString(attribute) && !attribute.encrypt}
<Tooltip>
<Icon
size="s"
icon={IconLockClosed}
color="--fgcolor-neutral-tertiary" />
<div slot="tooltip">Encrypted</div>
</Tooltip>
{/if}
</Layout.Stack>
{#if attribute.status !== 'available'}
<Badge
size="s"
variant="secondary"
content={attribute.status}
type={getAttributeStatusBadge(attribute.status)} />
{#if attribute.error}
<Link.Button
variant="muted"
on:click={(e) => {
e.preventDefault();
error = attribute.error;
showFailed = true;
}}>Details</Link.Button>
{/if}
{:else if attribute.required}
<Badge size="xs" variant="secondary" content="required" />
{/if}
{:else if attribute.required}
<Badge size="s" variant="secondary" content="required" />
{/if}
</Layout.Stack>
</Layout.Stack>
</Table.Cell>
<Table.Cell column="type" {root}>
@@ -100,5 +100,4 @@
label="Array"
bind:checked={data.array}
disabled={data.required || editing}
description="Indicate whether this attribute should act as an array, with the default value set as an empty
array." />
description="Indicate whether this attribute is an array. Defaults to an empty array." />
@@ -92,5 +92,4 @@
label="Array"
bind:checked={data.array}
disabled={data.required || editing}
description="Indicate whether this attribute should act as an array, with the default value set as an empty
array." />
description="Indicate whether this attribute is an array. Defaults to an empty array." />
@@ -93,5 +93,4 @@
label="Array"
bind:checked={data.array}
disabled={data.required || editing}
description="Indicate whether this attribute should act as an array, with the default value set as an empty
array." />
description="Indicate whether this attribute is an array. Defaults to an empty array." />
@@ -124,5 +124,4 @@
label="Array"
bind:checked={data.array}
disabled={data.required || editing}
description="Indicate whether this attribute should act as an array, with the default value set as an empty
array." />
description="Indicate whether this attribute is an array. Defaults to an empty array." />
@@ -122,5 +122,4 @@
label="Array"
bind:checked={data.array}
disabled={data.required || editing}
description="Indicate whether this attribute should act as an array, with the default value set as an empty
array." />
description="Indicate whether this attribute is an array. Defaults to an empty array." />
@@ -120,5 +120,4 @@
label="Array"
bind:checked={data.array}
disabled={data.required || editing}
description="Indicate whether this attribute should act as an array, with the default value set as an empty
array." />
description="Indicate whether this attribute is an array. Defaults to an empty array." />
@@ -92,5 +92,4 @@
label="Array"
bind:checked={data.array}
disabled={data.required || editing}
description="Indicate whether this attribute should act as an array, with the default value set as an empty
array." />
description="Indicate whether this attribute is an array. Defaults to an empty array." />
@@ -18,7 +18,8 @@
data.size,
data.required,
data.default,
data.array
data.array,
data.encrypt
);
}
export async function updateString(
@@ -42,16 +43,21 @@
</script>
<script lang="ts">
import { Selector } from '@appwrite.io/pink-svelte';
import { isCloud } from '$lib/system';
import { upgradeURL } from '$lib/stores/billing';
import { currentPlan } from '$lib/stores/organization';
import { createConservative } from '$lib/helpers/stores';
import { ActionMenu, Selector } from '@appwrite.io/pink-svelte';
import { InputNumber, InputText, InputTextarea } from '$lib/elements/forms';
import { Popover, Layout, Tag, Typography, Link } from '@appwrite.io/pink-svelte';
export let data: Partial<Models.AttributeString> = {
required: false,
size: 0,
default: null,
array: false
array: false,
encrypt: false
};
export let editing = false;
let savedDefault = data.default;
@@ -73,48 +79,107 @@
array: false,
...data
});
$: listen(data);
$: handleDefaultState($required || $array);
// check plan on cloud, always allow on self-hosted!
$: supportsStringEncryption = isCloud ? $currentPlan?.databasesAllowEncrypt : true;
</script>
<InputNumber
id="size"
label="Size"
required
placeholder="Enter size"
bind:value={data.size}
required={true} />
{#if data.size >= 50}
<InputTextarea
id="default"
label="Default"
placeholder="Enter string"
disabled={data.required || data.array}
nullable={!data.required && !data.array}
maxlength={data.size}
bind:value={data.default} />
{:else}
<InputText
id="default"
label="Default"
placeholder="Enter string"
disabled={data.required || data.array}
nullable={!data.required && !data.array}
maxlength={data.size}
bind:value={data.default} />
{/if}
min={supportsStringEncryption && data.encrypt ? 150 : undefined}
helper={supportsStringEncryption && data.encrypt
? 'Encrypted string attributes require a minimum size of 150.'
: undefined} />
<svelte:component
this={data.size >= 50 ? InputTextarea : InputText}
id="default"
label="Default"
placeholder="Enter string"
maxlength={data.size}
bind:value={data.default}
disabled={data.required || data.array}
nullable={!data.required && !data.array} />
<Selector.Checkbox
size="s"
id="required"
label="Required"
bind:checked={data.required}
disabled={data.array}
description="Indicate whether this attribute is required" />
description="Indicate whether this attribute is required." />
<Selector.Checkbox
size="s"
id="array"
label="Array"
bind:checked={data.array}
disabled={data.required || editing}
description="Indicate whether this attribute should act as an array, with the default value set as an empty
array." />
description="Indicate whether this attribute is an array. Defaults to an empty array." />
<Layout.Stack gap="xs" direction="column">
<div class="popover-holder" class:disabled-checkbox={!supportsStringEncryption || editing}>
<Layout.Stack inline gap="s" alignItems="flex-start" direction="row">
<Popover let:toggle placement="bottom-start">
<Selector.Checkbox
size="s"
id="encrypt"
bind:checked={data.encrypt}
disabled={!supportsStringEncryption || editing} />
<Layout.Stack gap="xxs" direction="column">
<button
type="button"
class="u-cursor-pointer"
on:click={(e) => {
if (!supportsStringEncryption) {
toggle(e);
} else {
data.encrypt = !data.encrypt;
}
}}>
<Layout.Stack inline gap="xxs" direction="row" alignItems="center">
<Typography.Text variant="m-500">Encrypted</Typography.Text>
{#if !supportsStringEncryption}
<Tag variant="default" size="xs" on:click={toggle}>Pro</Tag>
{/if}
</Layout.Stack>
</button>
<Typography.Text color="--fgcolor-neutral-tertiary">
Indicate whether this attribute is encrypted. Encrypted attributes 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={$upgradeURL}>Upgrade</Link.Anchor>
to enable encrypted attributes.
</Typography.Text>
</ActionMenu.Root>
</Popover>
</Layout.Stack>
</div>
</Layout.Stack>
<style lang="scss">
.popover-holder {
& :global([role='tooltip']) {
margin-top: 4px;
left: 8rem !important;
}
// no cursor when disabled
&.disabled-checkbox :global(button) {
cursor: unset !important;
}
}
</style>
@@ -93,5 +93,4 @@
label="Array"
bind:checked={data.array}
disabled={data.required || editing}
description="Indicate whether this attribute should act as an array, with the default value set as an empty
array." />
description="Indicate whether this attribute is an array. Defaults to an empty array." />
@@ -15,3 +15,8 @@ export function isRelationship(attribute: Attributes): attribute is Models.Attri
if (!attribute) return false;
return attribute?.type === 'relationship';
}
export function isString(attribute: Attributes): attribute is Models.AttributeString {
if (!attribute) return false;
return attribute?.type === 'string';
}
@@ -12,7 +12,11 @@
import type { Models } from '@appwrite.io/console';
import { afterUpdate, onMount } from 'svelte';
import type { PageData } from './$types';
import { isRelationship, isRelationshipToMany } from './document-[document]/attributes/store';
import {
isRelationship,
isRelationshipToMany,
isString
} from './document-[document]/attributes/store';
import RelationshipsModal from './relationshipsModal.svelte';
import { attributes, collection, columns } from './store';
import type { ColumnType } from '$lib/helpers/types';
@@ -22,10 +26,10 @@
Button,
Link,
Badge,
FloatingActionBar
FloatingActionBar,
InteractiveText
} from '@appwrite.io/pink-svelte';
import DualTimeView from '$lib/components/dualTimeView.svelte';
export let data: PageData;
const databaseId = page.params.database;
@@ -237,14 +241,24 @@
{/if}
{:else}
{@const formatted = formatColumn(document[id])}
<Tooltip disabled={!formatted.truncated} placement="bottom">
<span>
{formatted.value}
</span>
<span style:white-space="pre-wrap" slot="tooltip">
{formatted.whole}
</span>
</Tooltip>
{#if isString(attr) && !attr.encrypt}
<button on:click={(e) => e.preventDefault()}>
<InteractiveText
copy={false}
variant="secret"
isVisible={false}
text={formatted.value} />
</button>
{:else}
<Tooltip disabled={!formatted.truncated} placement="bottom">
<span>
{formatted.value}
</span>
<span style:white-space="pre-wrap" slot="tooltip">
{formatted.whole}
</span>
</Tooltip>
{/if}
{/if}
</Table.Cell>
{/each}