diff --git a/bun.lock b/bun.lock
index 59441522cf..35dd0aa42f 100644
--- a/bun.lock
+++ b/bun.lock
@@ -6,7 +6,7 @@
"name": "@appwrite/console",
"dependencies": {
"@ai-sdk/svelte": "^1.1.24",
- "@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@9786d91",
+ "@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@5c7f8e5",
"@appwrite.io/pink-icons": "0.25.0",
"@appwrite.io/pink-icons-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@bfe7ce3",
"@appwrite.io/pink-legacy": "^1.0.3",
@@ -125,7 +125,7 @@
"@analytics/type-utils": ["@analytics/type-utils@0.6.4", "", {}, "sha512-Ou1gQxFakOWLcPnbFVsrPb8g1wLLUZYYJXDPjHkG07+5mustGs5yqACx42UAu4A6NszNN6Z5gGxhyH45zPWRxw=="],
- "@appwrite.io/console": ["@appwrite.io/console@https://pkg.vc/-/@appwrite/@appwrite.io/console@9786d91", { "dependencies": { "json-bigint": "1.0.0" } }],
+ "@appwrite.io/console": ["@appwrite.io/console@https://pkg.vc/-/@appwrite/@appwrite.io/console@5c7f8e5", { "dependencies": { "json-bigint": "1.0.0" } }],
"@appwrite.io/pink-icons": ["@appwrite.io/pink-icons@0.25.0", "", {}, "sha512-0O3i2oEuh5mWvjO80i+X6rbzrWLJ1m5wmv2/M3a1p2PyBJsFxN8xQMTEmTn3Wl/D26SsM7SpzbdW6gmfgoVU9Q=="],
diff --git a/package.json b/package.json
index 849d235d4a..68435fd1a0 100644
--- a/package.json
+++ b/package.json
@@ -20,7 +20,7 @@
},
"dependencies": {
"@ai-sdk/svelte": "^1.1.24",
- "@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@9786d91",
+ "@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@5c7f8e5",
"@appwrite.io/pink-icons": "0.25.0",
"@appwrite.io/pink-icons-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@bfe7ce3",
"@appwrite.io/pink-legacy": "^1.0.3",
diff --git a/src/lib/actions/analytics.ts b/src/lib/actions/analytics.ts
index a03b956e4c..63d5cf42d4 100644
--- a/src/lib/actions/analytics.ts
+++ b/src/lib/actions/analytics.ts
@@ -255,6 +255,7 @@ export enum Submit {
ProjectUpdateLabels = 'submit_project_update_labels',
ProjectService = 'submit_project_service',
ProjectUpdateSMTP = 'submit_project_update_smtp',
+ ProjectUpdateOAuth2Server = 'submit_project_update_oauth2_server',
ProjectResume = 'submit_project_resume',
MemberCreate = 'submit_member_create',
MemberDelete = 'submit_member_delete',
diff --git a/src/lib/flags.ts b/src/lib/flags.ts
index 9f8c57a574..3a0cf0d85e 100644
--- a/src/lib/flags.ts
+++ b/src/lib/flags.ts
@@ -20,5 +20,6 @@ function isFlagEnabled(name: string) {
export const flags = {
multiDb: isFlagEnabled('multi-db'),
- granularProjectAccess: isFlagEnabled('granular-project-access')
+ granularProjectAccess: isFlagEnabled('granular-project-access'),
+ oauth2Server: isFlagEnabled('oauth2-server')
};
diff --git a/src/routes/(console)/project-[region]-[project]/settings/+page.svelte b/src/routes/(console)/project-[region]-[project]/settings/+page.svelte
index 3961307590..77ae89946d 100644
--- a/src/routes/(console)/project-[region]-[project]/settings/+page.svelte
+++ b/src/routes/(console)/project-[region]-[project]/settings/+page.svelte
@@ -18,8 +18,12 @@
import UpdateVariables from '../updateVariables.svelte';
import { page } from '$app/state';
import UpdateLabels from './updateLabels.svelte';
+ import UpdateOAuth2Server from './updateOAuth2Server.svelte';
import type { PageData } from './$types';
import { Alert } from '@appwrite.io/pink-svelte';
+ import { flags } from '$lib/flags';
+ import { user } from '$lib/stores/user';
+ import { organization } from '$lib/stores/organization';
let { data }: { data: PageData } = $props();
@@ -95,6 +99,9 @@
+ {#if flags.oauth2Server({ account: $user, organization: $organization })}
+
+ {/if}
+ import { invalidate } from '$app/navigation';
+ import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
+ import { CardGrid } from '$lib/components';
+ import { Dependencies } from '$lib/constants';
+ import { Button, Form, InputNumber, InputTags, InputText } from '$lib/elements/forms';
+ import InputSelect from '$lib/elements/forms/inputSelect.svelte';
+ import { addNotification } from '$lib/stores/notifications';
+ import { canWriteProjects } from '$lib/stores/roles';
+ import { sdk } from '$lib/stores/sdk';
+ import { Divider, Icon, Layout, Selector, Tooltip, Typography } from '@appwrite.io/pink-svelte';
+ import { IconInfo } from '@appwrite.io/pink-icons-svelte';
+ import deepEqual from 'deep-equal';
+ import { project } from '../store';
+
+ type TimeUnit = 'seconds' | 'minutes' | 'hours' | 'days';
+
+ const multipliers: Record = {
+ seconds: 1,
+ minutes: 60,
+ hours: 3600,
+ days: 86400
+ };
+
+ const unitOptions: { value: TimeUnit; label: string }[] = [
+ { value: 'seconds', label: 'Seconds' },
+ { value: 'minutes', label: 'Minutes' },
+ { value: 'hours', label: 'Hours' },
+ { value: 'days', label: 'Days' }
+ ];
+
+ function fromSeconds(
+ s: number | null,
+ defaultUnit: TimeUnit = 'hours'
+ ): { value: number | null; unit: TimeUnit } {
+ if (s === null) return { value: null, unit: defaultUnit };
+ if (s % 86400 === 0) return { value: s / 86400, unit: 'days' };
+ if (s % 3600 === 0) return { value: s / 3600, unit: 'hours' };
+ if (s % 60 === 0) return { value: s / 60, unit: 'minutes' };
+ return { value: s, unit: 'seconds' };
+ }
+
+ function toSeconds(value: number | null, unit: TimeUnit): number | null {
+ return value !== null ? value * multipliers[unit] : null;
+ }
+
+ let enabled = $state(false);
+ let authorizationUrl = $state('');
+ let scopes = $state([]);
+
+ let accessTokenValue = $state(null);
+ let accessTokenUnit = $state('hours');
+ let refreshTokenValue = $state(null);
+ let refreshTokenUnit = $state('days');
+ let publicAccessTokenValue = $state(null);
+ let publicAccessTokenUnit = $state('hours');
+ let publicRefreshTokenValue = $state(null);
+ let publicRefreshTokenUnit = $state('days');
+ let confidentialPkce = $state(false);
+
+ const accessTokenDuration = $derived(toSeconds(accessTokenValue, accessTokenUnit));
+ const refreshTokenDuration = $derived(toSeconds(refreshTokenValue, refreshTokenUnit));
+ const publicAccessTokenDuration = $derived(
+ toSeconds(publicAccessTokenValue, publicAccessTokenUnit)
+ );
+ const publicRefreshTokenDuration = $derived(
+ toSeconds(publicRefreshTokenValue, publicRefreshTokenUnit)
+ );
+
+ const isButtonDisabled = $derived(
+ !$canWriteProjects ||
+ deepEqual(
+ {
+ enabled,
+ authorizationUrl,
+ scopes,
+ accessTokenDuration,
+ refreshTokenDuration,
+ publicAccessTokenDuration,
+ publicRefreshTokenDuration,
+ confidentialPkce
+ },
+ {
+ enabled: $project.oAuth2ServerEnabled ?? false,
+ authorizationUrl: $project.oAuth2ServerAuthorizationUrl ?? '',
+ scopes: $project.oAuth2ServerScopes ?? [],
+ accessTokenDuration: $project.oAuth2ServerAccessTokenDuration ?? null,
+ refreshTokenDuration: $project.oAuth2ServerRefreshTokenDuration ?? null,
+ publicAccessTokenDuration:
+ $project.oAuth2ServerPublicAccessTokenDuration ?? null,
+ publicRefreshTokenDuration:
+ $project.oAuth2ServerPublicRefreshTokenDuration ?? null,
+ confidentialPkce: $project.oAuth2ServerConfidentialPkce ?? false
+ }
+ )
+ );
+
+ async function update() {
+ try {
+ await sdk.forProject($project.region, $project.$id).project.updateOAuth2Server({
+ enabled,
+ authorizationUrl,
+ scopes,
+ accessTokenDuration: accessTokenDuration ?? undefined,
+ refreshTokenDuration: refreshTokenDuration ?? undefined,
+ publicAccessTokenDuration: publicAccessTokenDuration ?? undefined,
+ publicRefreshTokenDuration: publicRefreshTokenDuration ?? undefined,
+ confidentialPkce
+ });
+
+ await invalidate(Dependencies.PROJECT);
+
+ addNotification({
+ type: 'success',
+ message: 'OAuth2 server settings have been updated.'
+ });
+ trackEvent(Submit.ProjectUpdateOAuth2Server);
+ } catch (error) {
+ addNotification({ type: 'error', message: error.message });
+ trackError(error, Submit.ProjectUpdateOAuth2Server);
+ }
+ }
+
+ $effect(() => {
+ enabled = $project.oAuth2ServerEnabled ?? false;
+ authorizationUrl = $project.oAuth2ServerAuthorizationUrl ?? '';
+ scopes = $project.oAuth2ServerScopes ?? [];
+
+ const at = fromSeconds($project.oAuth2ServerAccessTokenDuration ?? null, 'hours');
+ accessTokenValue = at.value;
+ accessTokenUnit = at.unit;
+
+ const rt = fromSeconds($project.oAuth2ServerRefreshTokenDuration ?? null, 'days');
+ refreshTokenValue = rt.value;
+ refreshTokenUnit = rt.unit;
+
+ const pat = fromSeconds($project.oAuth2ServerPublicAccessTokenDuration ?? null, 'hours');
+ publicAccessTokenValue = pat.value;
+ publicAccessTokenUnit = pat.unit;
+
+ const prt = fromSeconds($project.oAuth2ServerPublicRefreshTokenDuration ?? null, 'days');
+ publicRefreshTokenValue = prt.value;
+ publicRefreshTokenUnit = prt.unit;
+
+ confidentialPkce = $project.oAuth2ServerConfidentialPkce ?? false;
+ });
+
+
+
+
+