Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 18 additions & 9 deletions src/lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,12 +155,15 @@ export const defaultRoles: string[] = ['owner'];

// these are kept for backwards compatibility with keys and events that already exists.
// for the new ones, we use the new terminology.
export const scopes: {
export type ScopeDefinition = {
scope: string;
description: string;
category: string;
icon: string;
}[] = [
deprecated?: boolean;
};

export const scopes: ScopeDefinition[] = [
{
scope: 'sessions.write',
description: "Access to create, update and delete your project's sessions",
Expand Down Expand Up @@ -207,13 +210,15 @@ export const scopes: {
scope: 'collections.read',
description: "Access to read your project's database collections",
category: 'Database',
icon: 'database'
icon: 'database',
deprecated: true
},
{
scope: 'collections.write',
description: "Access to create, update, and delete your project's database collections",
category: 'Database',
icon: 'database'
icon: 'database',
deprecated: true
},
{
scope: 'tables.read',
Expand All @@ -231,14 +236,16 @@ export const scopes: {
scope: 'attributes.read',
description: "Access to read your project's database collection's attributes",
category: 'Database',
icon: 'database'
icon: 'database',
deprecated: true
},
{
scope: 'attributes.write',
description:
"Access to create, update, and delete your project's database collection's attributes",
category: 'Database',
icon: 'database'
icon: 'database',
deprecated: true
},
{
scope: 'columns.read',
Expand Down Expand Up @@ -268,13 +275,15 @@ export const scopes: {
scope: 'documents.read',
description: "Access to read your project's database documents",
category: 'Database',
icon: 'database'
icon: 'database',
deprecated: true
},
{
scope: 'documents.write',
description: "Access to create, update, and delete your project's database documents",
category: 'Database',
icon: 'database'
icon: 'database',
deprecated: true
},
{
scope: 'rows.read',
Expand Down Expand Up @@ -466,7 +475,7 @@ export const scopes: {
}
];

export const cloudOnlyBackupScopes = [
export const cloudOnlyBackupScopes: ScopeDefinition[] = [
{
scope: 'policies.read',
description: 'Access to read your database backup policies',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
import { symmetricDifference } from '$lib/helpers/array';
import Scopes from '../api-keys/scopes.svelte';
import { InteractiveText, Layout, Typography } from '@appwrite.io/pink-svelte';
import { getEffectiveScopes } from '../api-keys/scopes.svelte';

export let key: Models.DevKey | Models.Key;
export let keyType: 'api' | 'dev' = 'api';
Expand Down Expand Up @@ -163,8 +162,6 @@
{#if isApiKey}
<Form onSubmit={updateScopes}>
{@const apiKey = asApiKey(key)}
{@const apiKeyCorrectScopes = getEffectiveScopes(apiKey.scopes)}
{@const currentEffective = scopes ? getEffectiveScopes(scopes) : null}
<CardGrid>
<svelte:fragment slot="title">Scopes</svelte:fragment>
You can choose which permission scope to grant your application. It is a best practice
Expand All @@ -178,8 +175,7 @@
<svelte:fragment slot="actions">
<Button
submit
disabled={scopes &&
!symmetricDifference(currentEffective, apiKeyCorrectScopes).length}
disabled={scopes && !symmetricDifference(scopes, apiKey.scopes).length}
>Update</Button>
</svelte:fragment>
</CardGrid>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
import { Badge, Layout, Table } from '@appwrite.io/pink-svelte';
import DeleteBatch from './deleteBatch.svelte';
import { capitalize } from '$lib/helpers/string';
import { getEffectiveScopes } from '../api-keys/scopes.svelte';

let {
keyType = 'api',
Expand All @@ -31,7 +30,7 @@

function getApiKeyScopeCount(key: Models.Key | Models.DevKey) {
const apiKey = key as Models.Key;
return getEffectiveScopes(apiKey.scopes).length;
return apiKey.scopes.length;
}

function getExpiryDetails(key: Models.Key | Models.DevKey): {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,29 +31,21 @@
import { Button } from '$lib/elements/forms';
import { symmetricDifference } from '$lib/helpers/array';
import { scopes as allScopes, cloudOnlyBackupScopes } from '$lib/constants';
import { Accordion, Divider, Layout, Selector } from '@appwrite.io/pink-svelte';
import { Accordion, Badge, Divider, Layout, Selector } from '@appwrite.io/pink-svelte';
import type { Scopes } from '@appwrite.io/console';

export let scopes: Scopes[];

const baseFilteredScopes = allScopes.filter((scope) => {
const val = scope.scope;
if (!val) return false;

const legacyPrefixes = ['collections.', 'attributes.', 'documents.'];
return !legacyPrefixes.some((prefix) => val.startsWith(prefix));
});

// insert cloud-only scopes right after databases.write
const databasesWriteIndex = baseFilteredScopes.findIndex((s) => s.scope === 'databases.write');
const databasesWriteIndex = allScopes.findIndex((s) => s.scope === 'databases.write');
const filteredScopes =
isCloud && databasesWriteIndex !== -1
? [
...baseFilteredScopes.slice(0, databasesWriteIndex + 1),
...allScopes.slice(0, databasesWriteIndex + 1),
...cloudOnlyBackupScopes,
...baseFilteredScopes.slice(databasesWriteIndex + 1)
...allScopes.slice(databasesWriteIndex + 1)
]
: baseFilteredScopes;
: allScopes;

// include all scopes
const scopeCatalog = new Set([
Expand Down Expand Up @@ -90,9 +82,8 @@

onMount(() => {
scopes.forEach((scope) => {
const newerScope = toNewerScope(scope);
if (newerScope in activeScopes) {
activeScopes[newerScope] = true;
if (scope in activeScopes) {
activeScopes[scope] = true;
}
});

Expand All @@ -111,36 +102,11 @@
}
}

function toNewerScope(scope: string): string {
for (const pair of compatPairs) {
if (scope.startsWith(pair.legacy)) {
return scope.replace(pair.legacy, pair.newer);
}
}
return scope;
}

function getAllScopeVariants(scope: string): string[] {
const variants = new Set([scope]);

for (const pair of compatPairs) {
if (scope.startsWith(pair.newer)) {
variants.add(scope.replace(pair.newer, pair.legacy));
} else if (scope.startsWith(pair.legacy)) {
variants.add(scope.replace(pair.legacy, pair.newer));
}
}

return Array.from(variants);
}

function categoryState(category: string, s: string[]): boolean | 'indeterminate' {
const scopesByCategory = filteredScopes.filter((n) => n.category === category);

const activeInCategory = scopesByCategory.filter((scopeItem) => {
const newerScope = scopeItem.scope;
return s.some((scope) => toNewerScope(scope) === newerScope);
});
const activeInCategory = scopesByCategory.filter((scopeItem) =>
s.includes(scopeItem.scope as Scopes)
);

if (activeInCategory.length === 0) {
return false;
Expand All @@ -154,27 +120,16 @@
function onCategoryChange(event: CustomEvent<boolean | 'indeterminate'>, category: Category) {
if (event.detail === 'indeterminate') return;
filteredScopes.forEach((s) => {
if (s.category === category) {
if (s.category === category && !s.deprecated) {
activeScopes[s.scope] = event.detail;
}
});
}

function generateSyncedScopes(activeScopesObj: Record<string, boolean>): Scopes[] {
const result = new Set<string>();

Object.entries(activeScopesObj).forEach(([scope, isActive]) => {
if (isActive) {
const variants = getAllScopeVariants(scope);
variants.forEach((variant) => {
if (scopeCatalog.has(variant)) {
result.add(variant);
}
});
}
});

return Array.from(result) as Scopes[];
return Object.entries(activeScopesObj)
.filter(([scope, isActive]) => isActive && scopeCatalog.has(scope))
.map(([scope]) => scope as Scopes);
}

$: {
Expand Down Expand Up @@ -203,9 +158,7 @@
{@const checked = categoryState(category, scopes)}
{@const isLastItem = index === categories.length - 1}
{@const scopesLength = filteredScopes.filter(
(n) =>
n.category === category &&
scopes.some((scope) => toNewerScope(scope) === n.scope)
(n) => n.category === category && scopes.includes(n.scope as Scopes)
).length}
<Accordion
selectable
Expand All @@ -216,12 +169,17 @@
on:change={(event) => onCategoryChange(event, category)}>
<Layout.Stack>
{#each filteredScopes.filter((s) => s.category === category) as scope}
<Selector.Checkbox
size="s"
id={scope.scope}
label={scope.scope}
description={scope.description}
bind:checked={activeScopes[scope.scope]} />
<Layout.Stack direction="row" alignItems="center" gap="s">
<Selector.Checkbox
size="s"
id={scope.scope}
label={`${scope.scope}${scope.deprecated ? ' (Deprecated)' : ''}`}
description={scope.description}
bind:checked={activeScopes[scope.scope]} />
{#if scope.deprecated}
<Badge size="xs" variant="secondary" content="Deprecated" />
Comment thread
ChiragAgg5k marked this conversation as resolved.
{/if}
</Layout.Stack>
{/each}
Comment thread
ChiragAgg5k marked this conversation as resolved.
</Layout.Stack>
</Accordion>
Expand Down
Loading