From 5488f4734e49fd163a316f496ba1eb14846c1c31 Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Wed, 29 Apr 2026 19:03:06 +0530 Subject: [PATCH 01/19] feat: update console OAuth2 provider support --- bun.lock | 4 +- package.json | 2 +- src/lib/stores/oauth-providers.ts | 20 ++ .../auth/(providers)/appleOAuth.svelte | 3 +- .../auth/(providers)/auth0OAuth.svelte | 3 +- .../auth/(providers)/authentikOAuth.svelte | 3 +- .../auth/(providers)/fusionauthOAuth.svelte | 97 ++++++ .../auth/(providers)/gitlabOAuth.svelte | 3 +- .../auth/(providers)/googleOAuth.svelte | 3 +- .../auth/(providers)/keycloakOAuth.svelte | 108 +++++++ .../auth/(providers)/mainOAuth.svelte | 3 +- .../auth/(providers)/microsoftOAuth.svelte | 3 +- .../auth/(providers)/oidcOAuth.svelte | 3 +- .../auth/(providers)/oktaOAuth.svelte | 3 +- .../auth/settings/+page.svelte | 36 ++- .../auth/updateOAuth.ts | 304 +++++++++++++++++- static/icons/dark/color/fusionauth.svg | 3 + static/icons/dark/color/keycloak.svg | 3 + static/icons/dark/color/kick.svg | 3 + static/icons/dark/color/oidc.svg | 6 +- static/icons/light/color/fusionauth.svg | 3 + static/icons/light/color/keycloak.svg | 3 + static/icons/light/color/kick.svg | 3 + static/icons/light/color/oidc.svg | 6 +- 24 files changed, 599 insertions(+), 29 deletions(-) create mode 100644 src/routes/(console)/project-[region]-[project]/auth/(providers)/fusionauthOAuth.svelte create mode 100644 src/routes/(console)/project-[region]-[project]/auth/(providers)/keycloakOAuth.svelte create mode 100644 static/icons/dark/color/fusionauth.svg create mode 100644 static/icons/dark/color/keycloak.svg create mode 100644 static/icons/dark/color/kick.svg create mode 100644 static/icons/light/color/fusionauth.svg create mode 100644 static/icons/light/color/keycloak.svg create mode 100644 static/icons/light/color/kick.svg diff --git a/bun.lock b/bun.lock index 10ce13d787..dacb63cc5b 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@88c189e", + "@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@d10b661", "@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", @@ -124,7 +124,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@88c189e", { "dependencies": { "json-bigint": "1.0.0" } }], + "@appwrite.io/console": ["@appwrite.io/console@https://pkg.vc/-/@appwrite/@appwrite.io/console@d10b661", { "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 a006833186..7efc7474e9 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@88c189e", + "@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@d10b661", "@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/stores/oauth-providers.ts b/src/lib/stores/oauth-providers.ts index 11191dbaf9..cffd6fd411 100644 --- a/src/lib/stores/oauth-providers.ts +++ b/src/lib/stores/oauth-providers.ts @@ -2,8 +2,10 @@ import type { Component } from 'svelte'; import Apple from '$routes/(console)/project-[region]-[project]/auth/(providers)/appleOAuth.svelte'; import Auth0 from '$routes/(console)/project-[region]-[project]/auth/(providers)/auth0OAuth.svelte'; import Authentik from '$routes/(console)/project-[region]-[project]/auth/(providers)/authentikOAuth.svelte'; +import FusionAuth from '$routes/(console)/project-[region]-[project]/auth/(providers)/fusionauthOAuth.svelte'; import GitLab from '$routes/(console)/project-[region]-[project]/auth/(providers)/gitlabOAuth.svelte'; import Google from '$routes/(console)/project-[region]-[project]/auth/(providers)/googleOAuth.svelte'; +import Keycloak from '$routes/(console)/project-[region]-[project]/auth/(providers)/keycloakOAuth.svelte'; import Main from '$routes/(console)/project-[region]-[project]/auth/(providers)/mainOAuth.svelte'; import Microsoft from '$routes/(console)/project-[region]-[project]/auth/(providers)/microsoftOAuth.svelte'; import Oidc from '$routes/(console)/project-[region]-[project]/auth/(providers)/oidcOAuth.svelte'; @@ -108,6 +110,12 @@ export const oAuthProviders: Record = { docs: 'https://www.figma.com/developers/api#access-tokens', component: Main }, + fusionauth: { + name: 'FusionAuth', + icon: 'fusionauth', + docs: 'https://fusionauth.io/docs/apis/identity-providers/oauth2', + component: FusionAuth + }, github: { name: 'GitHub', icon: 'github', @@ -140,6 +148,18 @@ export const oAuthProviders: Record = { component: Google, internal: true }, + keycloak: { + name: 'Keycloak', + icon: 'keycloak', + docs: 'https://www.keycloak.org/securing-apps/oidc-layers', + component: Keycloak + }, + kick: { + name: 'Kick', + icon: 'kick', + docs: 'https://docs.kick.com/getting-started/using-the-api', + component: Main + }, linkedin: { name: 'LinkedIn', icon: 'linkedin', diff --git a/src/routes/(console)/project-[region]-[project]/auth/(providers)/appleOAuth.svelte b/src/routes/(console)/project-[region]-[project]/auth/(providers)/appleOAuth.svelte index 0554e9598f..34aa8c847b 100644 --- a/src/routes/(console)/project-[region]-[project]/auth/(providers)/appleOAuth.svelte +++ b/src/routes/(console)/project-[region]-[project]/auth/(providers)/appleOAuth.svelte @@ -10,6 +10,7 @@ import { Link, Alert } from '@appwrite.io/pink-svelte'; const projectId = page.params.project; + const region = page.params.region; export let provider: Models.AuthProvider; export let show = false; @@ -30,7 +31,7 @@ }); const update = async () => { - const result = await updateOAuth({ projectId, provider, secret, appId, enabled }); + const result = await updateOAuth({ region, projectId, provider, secret, appId, enabled }); if (result.status === 'error') { error = result.message; diff --git a/src/routes/(console)/project-[region]-[project]/auth/(providers)/auth0OAuth.svelte b/src/routes/(console)/project-[region]-[project]/auth/(providers)/auth0OAuth.svelte index 9b114e7402..fb65dadd42 100644 --- a/src/routes/(console)/project-[region]-[project]/auth/(providers)/auth0OAuth.svelte +++ b/src/routes/(console)/project-[region]-[project]/auth/(providers)/auth0OAuth.svelte @@ -10,6 +10,7 @@ import { Link, Alert } from '@appwrite.io/pink-svelte'; const projectId = page.params.project; + const region = page.params.region; export let provider: Models.AuthProvider; export let show = false; @@ -28,7 +29,7 @@ }); const update = async () => { - const result = await updateOAuth({ projectId, provider, secret, appId, enabled }); + const result = await updateOAuth({ region, projectId, provider, secret, appId, enabled }); if (result.status === 'error') { error = result.message; diff --git a/src/routes/(console)/project-[region]-[project]/auth/(providers)/authentikOAuth.svelte b/src/routes/(console)/project-[region]-[project]/auth/(providers)/authentikOAuth.svelte index 19a3b40c66..0b65d7c3b1 100644 --- a/src/routes/(console)/project-[region]-[project]/auth/(providers)/authentikOAuth.svelte +++ b/src/routes/(console)/project-[region]-[project]/auth/(providers)/authentikOAuth.svelte @@ -10,6 +10,7 @@ import { Link, Alert } from '@appwrite.io/pink-svelte'; const projectId = page.params.project; + const region = page.params.region; export let provider: Models.AuthProvider; export let show = false; @@ -29,7 +30,7 @@ }); const update = async () => { - const result = await updateOAuth({ projectId, provider, secret, appId, enabled }); + const result = await updateOAuth({ region, projectId, provider, secret, appId, enabled }); if (result.status === 'error') { error = result.message; diff --git a/src/routes/(console)/project-[region]-[project]/auth/(providers)/fusionauthOAuth.svelte b/src/routes/(console)/project-[region]-[project]/auth/(providers)/fusionauthOAuth.svelte new file mode 100644 index 0000000000..f74a0f5930 --- /dev/null +++ b/src/routes/(console)/project-[region]-[project]/auth/(providers)/fusionauthOAuth.svelte @@ -0,0 +1,97 @@ + + + +

+ To use {provider.name} authentication in your application, first fill in this form. For more info + you can + visit the docs. +

+ + + + + + To complete set up, add this OAuth2 redirect URI to your {provider.name} app configuration. + + + + + + +
diff --git a/src/routes/(console)/project-[region]-[project]/auth/(providers)/gitlabOAuth.svelte b/src/routes/(console)/project-[region]-[project]/auth/(providers)/gitlabOAuth.svelte index fc46679bd5..025f2419e5 100644 --- a/src/routes/(console)/project-[region]-[project]/auth/(providers)/gitlabOAuth.svelte +++ b/src/routes/(console)/project-[region]-[project]/auth/(providers)/gitlabOAuth.svelte @@ -10,6 +10,7 @@ import { getApiEndpoint } from '$lib/stores/sdk'; const projectId = page.params.project; + const region = page.params.region; export let provider: Models.AuthProvider; export let show = false; @@ -29,7 +30,7 @@ }); const update = async () => { - const result = await updateOAuth({ projectId, provider, secret, appId, enabled }); + const result = await updateOAuth({ region, projectId, provider, secret, appId, enabled }); if (result.status === 'error') { error = result.message; diff --git a/src/routes/(console)/project-[region]-[project]/auth/(providers)/googleOAuth.svelte b/src/routes/(console)/project-[region]-[project]/auth/(providers)/googleOAuth.svelte index d5457b28be..05e5560f2f 100644 --- a/src/routes/(console)/project-[region]-[project]/auth/(providers)/googleOAuth.svelte +++ b/src/routes/(console)/project-[region]-[project]/auth/(providers)/googleOAuth.svelte @@ -10,6 +10,7 @@ import { Alert } from '@appwrite.io/pink-svelte'; const projectId = page.params.project; + const region = page.params.region; export let provider: Models.AuthProvider; export let show = false; @@ -29,7 +30,7 @@ let error: string; const update = async () => { - const result = await updateOAuth({ projectId, provider, secret, appId, enabled }); + const result = await updateOAuth({ region, projectId, provider, secret, appId, enabled }); if (result.status === 'error') { error = result.message; diff --git a/src/routes/(console)/project-[region]-[project]/auth/(providers)/keycloakOAuth.svelte b/src/routes/(console)/project-[region]-[project]/auth/(providers)/keycloakOAuth.svelte new file mode 100644 index 0000000000..2178387368 --- /dev/null +++ b/src/routes/(console)/project-[region]-[project]/auth/(providers)/keycloakOAuth.svelte @@ -0,0 +1,108 @@ + + + +

+ To use {provider.name} authentication in your application, first fill in this form. For more info + you can + visit the docs. +

+ + + + + + + To complete set up, add this OAuth2 redirect URI to your {provider.name} app configuration. + + + + + + +
diff --git a/src/routes/(console)/project-[region]-[project]/auth/(providers)/mainOAuth.svelte b/src/routes/(console)/project-[region]-[project]/auth/(providers)/mainOAuth.svelte index 365782e6b4..44109091b1 100644 --- a/src/routes/(console)/project-[region]-[project]/auth/(providers)/mainOAuth.svelte +++ b/src/routes/(console)/project-[region]-[project]/auth/(providers)/mainOAuth.svelte @@ -10,6 +10,7 @@ import { getApiEndpoint } from '$lib/stores/sdk'; const projectId = page.params.project; + const region = page.params.region; export let provider: Models.AuthProvider; export let show = false; @@ -29,7 +30,7 @@ let error: string; const update = async () => { - const result = await updateOAuth({ projectId, provider, secret, appId, enabled }); + const result = await updateOAuth({ region, projectId, provider, secret, appId, enabled }); if (result.status === 'error') { error = result.message; diff --git a/src/routes/(console)/project-[region]-[project]/auth/(providers)/microsoftOAuth.svelte b/src/routes/(console)/project-[region]-[project]/auth/(providers)/microsoftOAuth.svelte index e6679ab2e4..d192a3b974 100644 --- a/src/routes/(console)/project-[region]-[project]/auth/(providers)/microsoftOAuth.svelte +++ b/src/routes/(console)/project-[region]-[project]/auth/(providers)/microsoftOAuth.svelte @@ -10,6 +10,7 @@ import { Alert } from '@appwrite.io/pink-svelte'; const projectId = page.params.project; + const region = page.params.region; export let provider: Models.AuthProvider; export let show = false; @@ -29,7 +30,7 @@ }); const update = async () => { - const result = await updateOAuth({ projectId, provider, secret, appId, enabled }); + const result = await updateOAuth({ region, projectId, provider, secret, appId, enabled }); if (result.status === 'error') { error = result.message; diff --git a/src/routes/(console)/project-[region]-[project]/auth/(providers)/oidcOAuth.svelte b/src/routes/(console)/project-[region]-[project]/auth/(providers)/oidcOAuth.svelte index 1360c4b726..377ddefa17 100644 --- a/src/routes/(console)/project-[region]-[project]/auth/(providers)/oidcOAuth.svelte +++ b/src/routes/(console)/project-[region]-[project]/auth/(providers)/oidcOAuth.svelte @@ -10,6 +10,7 @@ import { Alert } from '@appwrite.io/pink-svelte'; const projectId = page.params.project; + const region = page.params.region; export let provider: Models.AuthProvider; export let show = false; @@ -45,7 +46,7 @@ }); const update = async () => { - const result = await updateOAuth({ projectId, provider, secret, appId, enabled }); + const result = await updateOAuth({ region, projectId, provider, secret, appId, enabled }); if (result.status === 'error') { error = result.message; diff --git a/src/routes/(console)/project-[region]-[project]/auth/(providers)/oktaOAuth.svelte b/src/routes/(console)/project-[region]-[project]/auth/(providers)/oktaOAuth.svelte index 57f5c9e2ce..069211370e 100644 --- a/src/routes/(console)/project-[region]-[project]/auth/(providers)/oktaOAuth.svelte +++ b/src/routes/(console)/project-[region]-[project]/auth/(providers)/oktaOAuth.svelte @@ -10,6 +10,7 @@ import { Alert } from '@appwrite.io/pink-svelte'; const projectId = page.params.project; + const region = page.params.region; export let provider: Models.AuthProvider; export let show = false; @@ -32,7 +33,7 @@ }); const update = async () => { - const result = await updateOAuth({ projectId, provider, secret, appId, enabled }); + const result = await updateOAuth({ region, projectId, provider, secret, appId, enabled }); if (result.status === 'error') { error = result.message; diff --git a/src/routes/(console)/project-[region]-[project]/auth/settings/+page.svelte b/src/routes/(console)/project-[region]-[project]/auth/settings/+page.svelte index c086f5b55c..46618c45d1 100644 --- a/src/routes/(console)/project-[region]-[project]/auth/settings/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/auth/settings/+page.svelte @@ -152,7 +152,11 @@ Enable the authentication methods you wish to use. - + - - + +
{#each $authMethods.list as box} - +
{/if} - +
{/each} - +
@@ -271,3 +275,23 @@ + + diff --git a/src/routes/(console)/project-[region]-[project]/auth/updateOAuth.ts b/src/routes/(console)/project-[region]-[project]/auth/updateOAuth.ts index 6249ef31ed..68ef60d5b6 100644 --- a/src/routes/(console)/project-[region]-[project]/auth/updateOAuth.ts +++ b/src/routes/(console)/project-[region]-[project]/auth/updateOAuth.ts @@ -7,6 +7,7 @@ import { sdk } from '$lib/stores/sdk'; import { OAuthProvider, type Models } from '@appwrite.io/console'; type Args = { + region: string; projectId: string; provider: Models.AuthProvider; appId: string; @@ -19,7 +20,302 @@ type Return = { message?: string; }; +function parseSecret(secret: string) { + if (!secret) return {}; + + try { + return JSON.parse(secret); + } catch { + return {}; + } +} + +async function updateProjectOAuth({ + region, + projectId, + provider, + appId, + secret, + enabled +}: Args) { + const projectSdk = sdk.forProject(region, projectId).project; + const parsedSecret = parseSecret(secret); + + switch (provider.key) { + case OAuthProvider.Amazon: + return projectSdk.updateOAuth2Amazon({ + clientId: appId || undefined, + clientSecret: secret || undefined, + enabled + }); + case OAuthProvider.Apple: + return projectSdk.updateOAuth2Apple({ + serviceId: appId || undefined, + keyId: parsedSecret.keyID || undefined, + teamId: parsedSecret.teamID || undefined, + p8File: parsedSecret.p8 || undefined, + enabled + }); + case OAuthProvider.Auth0: + return projectSdk.updateOAuth2Auth0({ + clientId: appId || undefined, + clientSecret: parsedSecret.clientSecret || undefined, + endpoint: parsedSecret.auth0Domain || undefined, + enabled + }); + case OAuthProvider.Authentik: + return projectSdk.updateOAuth2Authentik({ + endpoint: parsedSecret.authentikDomain || '', + clientId: appId || undefined, + clientSecret: parsedSecret.clientSecret || undefined, + enabled + }); + case OAuthProvider.Autodesk: + return projectSdk.updateOAuth2Autodesk({ + clientId: appId || undefined, + clientSecret: secret || undefined, + enabled + }); + case OAuthProvider.Bitbucket: + return projectSdk.updateOAuth2Bitbucket({ + key: appId || undefined, + secret: secret || undefined, + enabled + }); + case OAuthProvider.Bitly: + return projectSdk.updateOAuth2Bitly({ + clientId: appId || undefined, + clientSecret: secret || undefined, + enabled + }); + case OAuthProvider.Box: + return projectSdk.updateOAuth2Box({ + clientId: appId || undefined, + clientSecret: secret || undefined, + enabled + }); + case OAuthProvider.Dailymotion: + return projectSdk.updateOAuth2Dailymotion({ + apiKey: appId || undefined, + apiSecret: secret || undefined, + enabled + }); + case OAuthProvider.Discord: + return projectSdk.updateOAuth2Discord({ + clientId: appId || undefined, + clientSecret: secret || undefined, + enabled + }); + case OAuthProvider.Disqus: + return projectSdk.updateOAuth2Disqus({ + publicKey: appId || undefined, + secretKey: secret || undefined, + enabled + }); + case OAuthProvider.Dropbox: + return projectSdk.updateOAuth2Dropbox({ + appKey: appId || undefined, + appSecret: secret || undefined, + enabled + }); + case OAuthProvider.Etsy: + return projectSdk.updateOAuth2Etsy({ + keyString: appId || undefined, + sharedSecret: secret || undefined, + enabled + }); + case OAuthProvider.Facebook: + return projectSdk.updateOAuth2Facebook({ + appId: appId || undefined, + appSecret: secret || undefined, + enabled + }); + case OAuthProvider.Figma: + return projectSdk.updateOAuth2Figma({ + clientId: appId || undefined, + clientSecret: secret || undefined, + enabled + }); + case OAuthProvider.Fusionauth: + return projectSdk.updateOAuth2FusionAuth({ + endpoint: parsedSecret.endpoint || '', + clientId: appId || undefined, + clientSecret: parsedSecret.clientSecret || undefined, + enabled + }); + case OAuthProvider.Github: + return projectSdk.updateOAuth2GitHub({ + clientId: appId || undefined, + clientSecret: secret || undefined, + enabled + }); + case OAuthProvider.Gitlab: + return projectSdk.updateOAuth2Gitlab({ + applicationId: appId || undefined, + secret: parsedSecret.clientSecret || undefined, + endpoint: parsedSecret.endpoint || undefined, + enabled + }); + case OAuthProvider.Google: + return projectSdk.updateOAuth2Google({ + clientId: appId || undefined, + clientSecret: secret || undefined, + enabled + }); + case OAuthProvider.Keycloak: + return projectSdk.updateOAuth2Keycloak({ + endpoint: parsedSecret.endpoint || '', + realmName: parsedSecret.realmName || '', + clientId: appId || undefined, + clientSecret: parsedSecret.clientSecret || undefined, + enabled + }); + case OAuthProvider.Kick: + return projectSdk.updateOAuth2Kick({ + clientId: appId || undefined, + clientSecret: secret || undefined, + enabled + }); + case OAuthProvider.Linkedin: + return projectSdk.updateOAuth2Linkedin({ + clientId: appId || undefined, + primaryClientSecret: secret || undefined, + enabled + }); + case OAuthProvider.Microsoft: + return projectSdk.updateOAuth2Microsoft({ + tenant: parsedSecret.tenantID || 'common', + applicationId: appId || undefined, + applicationSecret: parsedSecret.clientSecret || undefined, + enabled + }); + case OAuthProvider.Notion: + return projectSdk.updateOAuth2Notion({ + oauthClientId: appId || undefined, + oauthClientSecret: secret || undefined, + enabled + }); + case OAuthProvider.Oidc: + return projectSdk.updateOAuth2Oidc({ + clientId: appId || undefined, + clientSecret: parsedSecret.clientSecret || undefined, + wellKnownURL: parsedSecret.wellKnownEndpoint || undefined, + authorizationURL: parsedSecret.authorizationEndpoint || undefined, + tokenUrl: parsedSecret.tokenEndpoint || undefined, + userInfoUrl: parsedSecret.userinfoEndpoint || undefined, + enabled + }); + case OAuthProvider.Okta: + return projectSdk.updateOAuth2Okta({ + clientId: appId || undefined, + clientSecret: parsedSecret.clientSecret || undefined, + domain: parsedSecret.oktaDomain || undefined, + authorizationServerId: parsedSecret.authorizationServerId || undefined, + enabled + }); + case OAuthProvider.Paypal: + return projectSdk.updateOAuth2Paypal({ + clientId: appId || undefined, + secretKey: secret || undefined, + enabled + }); + case OAuthProvider.PaypalSandbox: + return projectSdk.updateOAuth2PaypalSandbox({ + clientId: appId || undefined, + secretKey: secret || undefined, + enabled + }); + case OAuthProvider.Podio: + return projectSdk.updateOAuth2Podio({ + clientId: appId || undefined, + clientSecret: secret || undefined, + enabled + }); + case OAuthProvider.Salesforce: + return projectSdk.updateOAuth2Salesforce({ + customerKey: appId || undefined, + customerSecret: secret || undefined, + enabled + }); + case OAuthProvider.Slack: + return projectSdk.updateOAuth2Slack({ + clientId: appId || undefined, + clientSecret: secret || undefined, + enabled + }); + case OAuthProvider.Spotify: + return projectSdk.updateOAuth2Spotify({ + clientId: appId || undefined, + clientSecret: secret || undefined, + enabled + }); + case OAuthProvider.Stripe: + return projectSdk.updateOAuth2Stripe({ + clientId: appId || undefined, + apiSecretKey: secret || undefined, + enabled + }); + case OAuthProvider.Tradeshift: + return projectSdk.updateOAuth2Tradeshift({ + oauth2ClientId: appId || undefined, + oauth2ClientSecret: secret || undefined, + enabled + }); + case OAuthProvider.TradeshiftBox: + return projectSdk.updateOAuth2TradeshiftSandbox({ + oauth2ClientId: appId || undefined, + oauth2ClientSecret: secret || undefined, + enabled + }); + case OAuthProvider.Twitch: + return projectSdk.updateOAuth2Twitch({ + clientId: appId || undefined, + clientSecret: secret || undefined, + enabled + }); + case OAuthProvider.Wordpress: + return projectSdk.updateOAuth2WordPress({ + clientId: appId || undefined, + clientSecret: secret || undefined, + enabled + }); + case OAuthProvider.X: + return projectSdk.updateOAuth2X({ + customerKey: appId || undefined, + secretKey: secret || undefined, + enabled + }); + case OAuthProvider.Yahoo: + return projectSdk.updateOAuth2Yahoo({ + clientId: appId || undefined, + clientSecret: secret || undefined, + enabled + }); + case OAuthProvider.Yandex: + return projectSdk.updateOAuth2Yandex({ + clientId: appId || undefined, + clientSecret: secret || undefined, + enabled + }); + case OAuthProvider.Zoho: + return projectSdk.updateOAuth2Zoho({ + clientId: appId || undefined, + clientSecret: secret || undefined, + enabled + }); + case OAuthProvider.Zoom: + return projectSdk.updateOAuth2Zoom({ + clientId: appId || undefined, + clientSecret: secret || undefined, + enabled + }); + default: + throw new Error(`Unsupported OAuth2 provider: ${provider.key}`); + } +} + export async function updateOAuth({ + region, projectId, provider, appId, @@ -31,13 +327,7 @@ export async function updateOAuth({ throw new Error(`Invalid OAuth2 provider: ${provider.key}`); } - await sdk.forConsole.projects.updateOAuth2({ - projectId, - provider: provider.key, - appId: appId || undefined, - secret: secret || undefined, - enabled - }); + await updateProjectOAuth({ region, projectId, provider, appId, secret, enabled }); await invalidate(Dependencies.PROJECT); addNotification({ diff --git a/static/icons/dark/color/fusionauth.svg b/static/icons/dark/color/fusionauth.svg new file mode 100644 index 0000000000..00303a4433 --- /dev/null +++ b/static/icons/dark/color/fusionauth.svg @@ -0,0 +1,3 @@ + + + diff --git a/static/icons/dark/color/keycloak.svg b/static/icons/dark/color/keycloak.svg new file mode 100644 index 0000000000..62219d3040 --- /dev/null +++ b/static/icons/dark/color/keycloak.svg @@ -0,0 +1,3 @@ + + + diff --git a/static/icons/dark/color/kick.svg b/static/icons/dark/color/kick.svg new file mode 100644 index 0000000000..7bd7132304 --- /dev/null +++ b/static/icons/dark/color/kick.svg @@ -0,0 +1,3 @@ + + + diff --git a/static/icons/dark/color/oidc.svg b/static/icons/dark/color/oidc.svg index 3e45a505de..69ba83b7ce 100644 --- a/static/icons/dark/color/oidc.svg +++ b/static/icons/dark/color/oidc.svg @@ -1,3 +1,5 @@ - - + + + + diff --git a/static/icons/light/color/fusionauth.svg b/static/icons/light/color/fusionauth.svg new file mode 100644 index 0000000000..00303a4433 --- /dev/null +++ b/static/icons/light/color/fusionauth.svg @@ -0,0 +1,3 @@ + + + diff --git a/static/icons/light/color/keycloak.svg b/static/icons/light/color/keycloak.svg new file mode 100644 index 0000000000..264768ec4c --- /dev/null +++ b/static/icons/light/color/keycloak.svg @@ -0,0 +1,3 @@ + + + diff --git a/static/icons/light/color/kick.svg b/static/icons/light/color/kick.svg new file mode 100644 index 0000000000..7bd7132304 --- /dev/null +++ b/static/icons/light/color/kick.svg @@ -0,0 +1,3 @@ + + + diff --git a/static/icons/light/color/oidc.svg b/static/icons/light/color/oidc.svg index 3e45a505de..69ba83b7ce 100644 --- a/static/icons/light/color/oidc.svg +++ b/static/icons/light/color/oidc.svg @@ -1,3 +1,5 @@ - - + + + + From 919109d4f4cf1f2baa31071f172f14cd165c01cb Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Wed, 29 Apr 2026 19:22:08 +0530 Subject: [PATCH 02/19] fix: align console with latest project sdk methods --- src/lib/components/organizationUsageLimits.svelte | 14 ++++++++++---- .../sonners/{error.svelte => errorSonner.svelte} | 0 .../(components)/sonners/index.ts | 2 +- .../overview/(components)/create.svelte | 2 +- .../settings/deleteProject.svelte | 4 +--- .../settings/migrations/+page.svelte | 2 +- .../settings/migrations/exportModal.svelte | 2 +- 7 files changed, 15 insertions(+), 11 deletions(-) rename src/routes/(console)/project-[region]-[project]/databases/database-[database]/collection-[collection]/(components)/sonners/{error.svelte => errorSonner.svelte} (100%) diff --git a/src/lib/components/organizationUsageLimits.svelte b/src/lib/components/organizationUsageLimits.svelte index 39ae9a1ea3..ae8b08b7fc 100644 --- a/src/lib/components/organizationUsageLimits.svelte +++ b/src/lib/components/organizationUsageLimits.svelte @@ -90,10 +90,16 @@ } if (selectedProjectsToDelete?.length) { - const projectsDeletionPromises = selectedProjectsToDelete.map((projectId) => ({ - projectId, - promise: sdk.forConsole.projects.delete({ projectId }) - })); + const projectsDeletionPromises = selectedProjectsToDelete.map((projectId) => { + const projectToDelete = projects.find((project) => project.$id === projectId); + + return { + projectId, + promise: sdk + .forProject(projectToDelete.region, projectId) + .project.delete() + }; + }); try { const results = await Promise.allSettled( diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/collection-[collection]/(components)/sonners/error.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/collection-[collection]/(components)/sonners/errorSonner.svelte similarity index 100% rename from src/routes/(console)/project-[region]-[project]/databases/database-[database]/collection-[collection]/(components)/sonners/error.svelte rename to src/routes/(console)/project-[region]-[project]/databases/database-[database]/collection-[collection]/(components)/sonners/errorSonner.svelte diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/collection-[collection]/(components)/sonners/index.ts b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/collection-[collection]/(components)/sonners/index.ts index cc1daf60ad..f5afd0bc57 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/collection-[collection]/(components)/sonners/index.ts +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/collection-[collection]/(components)/sonners/index.ts @@ -1,3 +1,3 @@ export { default as Save } from './save.svelte'; -export { default as Error } from './error.svelte'; +export { default as Error } from './errorSonner.svelte'; export { default as Suggestions } from './suggestions.svelte'; diff --git a/src/routes/(console)/project-[region]-[project]/overview/(components)/create.svelte b/src/routes/(console)/project-[region]-[project]/overview/(components)/create.svelte index ed56fe2d08..59b8565c89 100644 --- a/src/routes/(console)/project-[region]-[project]/overview/(components)/create.svelte +++ b/src/routes/(console)/project-[region]-[project]/overview/(components)/create.svelte @@ -30,7 +30,7 @@ try { const { $id, secret } = await sdk .forProject(page.params.region, page.params.project) - .project.createKey({ + .project.createStandardKey({ keyId: ID.unique(), name, scopes, diff --git a/src/routes/(console)/project-[region]-[project]/settings/deleteProject.svelte b/src/routes/(console)/project-[region]-[project]/settings/deleteProject.svelte index a39f59bf68..ba12f9b94a 100644 --- a/src/routes/(console)/project-[region]-[project]/settings/deleteProject.svelte +++ b/src/routes/(console)/project-[region]-[project]/settings/deleteProject.svelte @@ -32,9 +32,7 @@ const handleDelete = async () => { try { // send the project to correct region pool for deletion! - await sdk.forConsoleIn($project.region).projects.delete({ - projectId: $project.$id - }); + await sdk.forProject($project.region, $project.$id).project.delete(); await finishAndRedirect(); } catch (e) { error = e.message; diff --git a/src/routes/(console)/project-[region]-[project]/settings/migrations/+page.svelte b/src/routes/(console)/project-[region]-[project]/settings/migrations/+page.svelte index 7bed364e62..0a19e93361 100644 --- a/src/routes/(console)/project-[region]-[project]/settings/migrations/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/settings/migrations/+page.svelte @@ -88,7 +88,7 @@ // Create API key const { secret } = await sdk .forProject(page.params.region, page.params.project) - .project.createKey({ + .project.createStandardKey({ keyId: ID.unique(), name: `[AUTO-GENERATED] Migration ${new Date().toISOString()}`, scopes: [ diff --git a/src/routes/(console)/project-[region]-[project]/settings/migrations/exportModal.svelte b/src/routes/(console)/project-[region]-[project]/settings/migrations/exportModal.svelte index d2e7177180..aa45020b30 100644 --- a/src/routes/(console)/project-[region]-[project]/settings/migrations/exportModal.svelte +++ b/src/routes/(console)/project-[region]-[project]/settings/migrations/exportModal.svelte @@ -90,7 +90,7 @@ // Create API key const { secret } = await sdk .forProject(page.params.region, page.params.project) - .project.createKey({ + .project.createStandardKey({ keyId: ID.unique(), name: `[AUTO-GENERATED] Migration ${new Date().toISOString()}`, scopes: [ From 14154cb5278512ffa2f97c6bf103d0975e443a5a Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Wed, 29 Apr 2026 19:24:45 +0530 Subject: [PATCH 03/19] format --- src/lib/components/organizationUsageLimits.svelte | 4 +--- .../auth/(providers)/fusionauthOAuth.svelte | 3 ++- .../auth/settings/+page.svelte | 1 - .../project-[region]-[project]/auth/updateOAuth.ts | 9 +-------- 4 files changed, 4 insertions(+), 13 deletions(-) diff --git a/src/lib/components/organizationUsageLimits.svelte b/src/lib/components/organizationUsageLimits.svelte index ae8b08b7fc..8373b17b36 100644 --- a/src/lib/components/organizationUsageLimits.svelte +++ b/src/lib/components/organizationUsageLimits.svelte @@ -95,9 +95,7 @@ return { projectId, - promise: sdk - .forProject(projectToDelete.region, projectId) - .project.delete() + promise: sdk.forProject(projectToDelete.region, projectId).project.delete() }; }); diff --git a/src/routes/(console)/project-[region]-[project]/auth/(providers)/fusionauthOAuth.svelte b/src/routes/(console)/project-[region]-[project]/auth/(providers)/fusionauthOAuth.svelte index f74a0f5930..d50993b06f 100644 --- a/src/routes/(console)/project-[region]-[project]/auth/(providers)/fusionauthOAuth.svelte +++ b/src/routes/(console)/project-[region]-[project]/auth/(providers)/fusionauthOAuth.svelte @@ -43,7 +43,8 @@ } }; - $: secret = clientSecret && endpoint ? JSON.stringify({ clientSecret, endpoint }) : provider.secret; + $: secret = + clientSecret && endpoint ? JSON.stringify({ clientSecret, endpoint }) : provider.secret; diff --git a/src/routes/(console)/project-[region]-[project]/auth/settings/+page.svelte b/src/routes/(console)/project-[region]-[project]/auth/settings/+page.svelte index 46618c45d1..1b04701fcd 100644 --- a/src/routes/(console)/project-[region]-[project]/auth/settings/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/auth/settings/+page.svelte @@ -288,7 +288,6 @@ min-width: 0; } - @media (max-width: 768px) { .auth-methods-grid { grid-template-columns: 1fr; diff --git a/src/routes/(console)/project-[region]-[project]/auth/updateOAuth.ts b/src/routes/(console)/project-[region]-[project]/auth/updateOAuth.ts index 68ef60d5b6..8a9e29d036 100644 --- a/src/routes/(console)/project-[region]-[project]/auth/updateOAuth.ts +++ b/src/routes/(console)/project-[region]-[project]/auth/updateOAuth.ts @@ -30,14 +30,7 @@ function parseSecret(secret: string) { } } -async function updateProjectOAuth({ - region, - projectId, - provider, - appId, - secret, - enabled -}: Args) { +async function updateProjectOAuth({ region, projectId, provider, appId, secret, enabled }: Args) { const projectSdk = sdk.forProject(region, projectId).project; const parsedSecret = parseSecret(secret); From 75c34eb4850d4904b97d02f8f854e0a5a5c986d3 Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Wed, 29 Apr 2026 19:46:24 +0530 Subject: [PATCH 04/19] fix: remove unsupported yammer oauth provider --- src/lib/stores/oauth-providers.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/lib/stores/oauth-providers.ts b/src/lib/stores/oauth-providers.ts index cffd6fd411..7e9249924a 100644 --- a/src/lib/stores/oauth-providers.ts +++ b/src/lib/stores/oauth-providers.ts @@ -268,12 +268,6 @@ export const oAuthProviders: Record = { docs: 'https://developer.yahoo.com/oauth2/guide/flows_authcode/', component: Main }, - yammer: { - name: 'Yammer', - icon: 'yammer', - docs: 'https://developer.yammer.com/docs/oauth-2', - component: Main - }, yandex: { name: 'Yandex', icon: 'yandex', From 8a78402ea17d864c80470be9635af84707337cf6 Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Wed, 29 Apr 2026 20:05:04 +0530 Subject: [PATCH 05/19] remove yammer and change names --- bun.lock | 4 +-- package.json | 2 +- .../overview/(components)/create.svelte | 2 +- .../settings/migrations/+page.svelte | 4 +-- .../settings/migrations/exportModal.svelte | 4 +-- static/icons/dark/color/yammer.svg | 26 ------------------- static/icons/dark/grayscale/yammer.svg | 7 ----- static/icons/light/color/yammer.svg | 26 ------------------- static/icons/light/grayscale/yammer.svg | 7 ----- 9 files changed, 8 insertions(+), 74 deletions(-) delete mode 100644 static/icons/dark/color/yammer.svg delete mode 100644 static/icons/dark/grayscale/yammer.svg delete mode 100644 static/icons/light/color/yammer.svg delete mode 100644 static/icons/light/grayscale/yammer.svg diff --git a/bun.lock b/bun.lock index dacb63cc5b..3e2c925ae5 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@d10b661", + "@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@33968aa", "@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", @@ -124,7 +124,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@d10b661", { "dependencies": { "json-bigint": "1.0.0" } }], + "@appwrite.io/console": ["@appwrite.io/console@https://pkg.vc/-/@appwrite/@appwrite.io/console@33968aa", { "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 7efc7474e9..a18b34eaa6 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@d10b661", + "@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@33968aa", "@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/routes/(console)/project-[region]-[project]/overview/(components)/create.svelte b/src/routes/(console)/project-[region]-[project]/overview/(components)/create.svelte index 59b8565c89..ed56fe2d08 100644 --- a/src/routes/(console)/project-[region]-[project]/overview/(components)/create.svelte +++ b/src/routes/(console)/project-[region]-[project]/overview/(components)/create.svelte @@ -30,7 +30,7 @@ try { const { $id, secret } = await sdk .forProject(page.params.region, page.params.project) - .project.createStandardKey({ + .project.createKey({ keyId: ID.unique(), name, scopes, diff --git a/src/routes/(console)/project-[region]-[project]/settings/migrations/+page.svelte b/src/routes/(console)/project-[region]-[project]/settings/migrations/+page.svelte index 0a19e93361..fc54b8ba64 100644 --- a/src/routes/(console)/project-[region]-[project]/settings/migrations/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/settings/migrations/+page.svelte @@ -88,7 +88,7 @@ // Create API key const { secret } = await sdk .forProject(page.params.region, page.params.project) - .project.createStandardKey({ + .project.createKey({ keyId: ID.unique(), name: `[AUTO-GENERATED] Migration ${new Date().toISOString()}`, scopes: [ @@ -105,7 +105,7 @@ Scopes.FilesRead, Scopes.BucketsRead, Scopes.FunctionsRead, - Scopes.ExecutionRead, + Scopes.ExecutionsRead, Scopes.LocaleRead, Scopes.AvatarsRead, Scopes.HealthRead diff --git a/src/routes/(console)/project-[region]-[project]/settings/migrations/exportModal.svelte b/src/routes/(console)/project-[region]-[project]/settings/migrations/exportModal.svelte index aa45020b30..e707021275 100644 --- a/src/routes/(console)/project-[region]-[project]/settings/migrations/exportModal.svelte +++ b/src/routes/(console)/project-[region]-[project]/settings/migrations/exportModal.svelte @@ -90,7 +90,7 @@ // Create API key const { secret } = await sdk .forProject(page.params.region, page.params.project) - .project.createStandardKey({ + .project.createKey({ keyId: ID.unique(), name: `[AUTO-GENERATED] Migration ${new Date().toISOString()}`, scopes: [ @@ -107,7 +107,7 @@ Scopes.FilesRead, Scopes.BucketsRead, Scopes.FunctionsRead, - Scopes.ExecutionRead, + Scopes.ExecutionsRead, Scopes.SitesRead, Scopes.ProvidersRead, Scopes.TopicsRead, diff --git a/static/icons/dark/color/yammer.svg b/static/icons/dark/color/yammer.svg deleted file mode 100644 index 2d1083bd9a..0000000000 --- a/static/icons/dark/color/yammer.svg +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/static/icons/dark/grayscale/yammer.svg b/static/icons/dark/grayscale/yammer.svg deleted file mode 100644 index cbb07a3c23..0000000000 --- a/static/icons/dark/grayscale/yammer.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/static/icons/light/color/yammer.svg b/static/icons/light/color/yammer.svg deleted file mode 100644 index e6d34f1e9f..0000000000 --- a/static/icons/light/color/yammer.svg +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/static/icons/light/grayscale/yammer.svg b/static/icons/light/grayscale/yammer.svg deleted file mode 100644 index 66e6ba653e..0000000000 --- a/static/icons/light/grayscale/yammer.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - From ed8bd04d9065cebfcb07e4df5a5c4638dd6a7d7a Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Wed, 29 Apr 2026 21:46:52 +0530 Subject: [PATCH 06/19] feat: dynamic OAuth2 forms and API key scopes from console SDK - Replace hardcoded OAuth2 provider components with a single dynamic mainOAuth.svelte that fetches labels, placeholders, and hints from listOAuth2Providers; removes 10 now-unused provider-specific files - Load OAuth2 provider list and enabled state from project SDK in +page.ts instead of reading from the project model directly - Fetch API key scopes from listProjectScopes instead of static constants; migrate scopes.svelte to Svelte 5 runes - Add executions.read/write scope definitions and legacy compat pair for execution. -> executions. - Secret field uses write-only card pattern (Tag -> expandable Card); no pre-fill, no silent fallbacks --- src/lib/constants.ts | 22 +- src/lib/stores/oauth-providers.ts | 32 +-- .../auth/(providers)/appleOAuth.svelte | 87 ------- .../auth/(providers)/auth0OAuth.svelte | 92 -------- .../auth/(providers)/authentikOAuth.svelte | 96 -------- .../auth/(providers)/fusionauthOAuth.svelte | 98 -------- .../auth/(providers)/gitlabOAuth.svelte | 85 ------- .../auth/(providers)/googleOAuth.svelte | 85 ------- .../auth/(providers)/keycloakOAuth.svelte | 108 --------- .../auth/(providers)/mainOAuth.svelte | 212 +++++++++++++++--- .../auth/(providers)/microsoftOAuth.svelte | 91 -------- .../auth/(providers)/oidcOAuth.svelte | 134 ----------- .../auth/(providers)/oktaOAuth.svelte | 110 --------- .../auth/settings/+page.svelte | 5 +- .../auth/settings/+page.ts | 45 ++++ .../overview/api-keys/scopes.svelte | 94 ++++---- 16 files changed, 309 insertions(+), 1087 deletions(-) delete mode 100644 src/routes/(console)/project-[region]-[project]/auth/(providers)/appleOAuth.svelte delete mode 100644 src/routes/(console)/project-[region]-[project]/auth/(providers)/auth0OAuth.svelte delete mode 100644 src/routes/(console)/project-[region]-[project]/auth/(providers)/authentikOAuth.svelte delete mode 100644 src/routes/(console)/project-[region]-[project]/auth/(providers)/fusionauthOAuth.svelte delete mode 100644 src/routes/(console)/project-[region]-[project]/auth/(providers)/gitlabOAuth.svelte delete mode 100644 src/routes/(console)/project-[region]-[project]/auth/(providers)/googleOAuth.svelte delete mode 100644 src/routes/(console)/project-[region]-[project]/auth/(providers)/keycloakOAuth.svelte delete mode 100644 src/routes/(console)/project-[region]-[project]/auth/(providers)/microsoftOAuth.svelte delete mode 100644 src/routes/(console)/project-[region]-[project]/auth/(providers)/oidcOAuth.svelte delete mode 100644 src/routes/(console)/project-[region]-[project]/auth/(providers)/oktaOAuth.svelte create mode 100644 src/routes/(console)/project-[region]-[project]/auth/settings/+page.ts diff --git a/src/lib/constants.ts b/src/lib/constants.ts index be661eefb5..d917925ae6 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -112,8 +112,8 @@ export const defaultScopes: string[] = [ 'projects.write', 'locale.read', 'avatars.read', - 'execution.read', - 'execution.write', + 'executions.read', + 'executions.write', 'targets.read', 'targets.write', 'subscribers.write', @@ -339,17 +339,31 @@ export const scopes: ScopeDefinition[] = [ icon: 'lightning-bolt' }, { - scope: 'execution.read', + scope: 'executions.read', description: "Access to read your project's execution logs", category: 'Functions', icon: 'lightning-bolt' }, { - scope: 'execution.write', + scope: 'executions.write', description: "Access to execute your project's functions", category: 'Functions', icon: 'lightning-bolt' }, + { + scope: 'execution.read', + description: "Access to read your project's execution logs", + category: 'Functions', + icon: 'lightning-bolt', + deprecated: true + }, + { + scope: 'execution.write', + description: "Access to execute your project's functions", + category: 'Functions', + icon: 'lightning-bolt', + deprecated: true + }, { scope: 'targets.read', description: "Access to read your project's messaging targets", diff --git a/src/lib/stores/oauth-providers.ts b/src/lib/stores/oauth-providers.ts index 7e9249924a..4870b4cccb 100644 --- a/src/lib/stores/oauth-providers.ts +++ b/src/lib/stores/oauth-providers.ts @@ -1,15 +1,5 @@ import type { Component } from 'svelte'; -import Apple from '$routes/(console)/project-[region]-[project]/auth/(providers)/appleOAuth.svelte'; -import Auth0 from '$routes/(console)/project-[region]-[project]/auth/(providers)/auth0OAuth.svelte'; -import Authentik from '$routes/(console)/project-[region]-[project]/auth/(providers)/authentikOAuth.svelte'; -import FusionAuth from '$routes/(console)/project-[region]-[project]/auth/(providers)/fusionauthOAuth.svelte'; -import GitLab from '$routes/(console)/project-[region]-[project]/auth/(providers)/gitlabOAuth.svelte'; -import Google from '$routes/(console)/project-[region]-[project]/auth/(providers)/googleOAuth.svelte'; -import Keycloak from '$routes/(console)/project-[region]-[project]/auth/(providers)/keycloakOAuth.svelte'; import Main from '$routes/(console)/project-[region]-[project]/auth/(providers)/mainOAuth.svelte'; -import Microsoft from '$routes/(console)/project-[region]-[project]/auth/(providers)/microsoftOAuth.svelte'; -import Oidc from '$routes/(console)/project-[region]-[project]/auth/(providers)/oidcOAuth.svelte'; -import Okta from '$routes/(console)/project-[region]-[project]/auth/(providers)/oktaOAuth.svelte'; export type Provider = { name: string; @@ -30,19 +20,19 @@ export const oAuthProviders: Record = { name: 'Apple', icon: 'apple', docs: 'https://developer.apple.com/sign-in-with-apple/', - component: Apple + component: Main }, auth0: { name: 'Auth0', icon: 'auth0', docs: 'https://auth0.com/developers', - component: Auth0 + component: Main }, authentik: { name: 'Authentik', icon: 'authentik', docs: 'https://goauthentik.io/integrations/sources/oauth/', - component: Authentik + component: Main }, autodesk: { name: 'Autodesk', @@ -114,7 +104,7 @@ export const oAuthProviders: Record = { name: 'FusionAuth', icon: 'fusionauth', docs: 'https://fusionauth.io/docs/apis/identity-providers/oauth2', - component: FusionAuth + component: Main }, github: { name: 'GitHub', @@ -133,26 +123,26 @@ export const oAuthProviders: Record = { name: 'GitLab', icon: 'gitlab', docs: 'https://docs.gitlab.com/ee/api/', - component: GitLab + component: Main }, google: { name: 'Google', icon: 'google', docs: 'https://support.google.com/googleapi/answer/6158849', - component: Google + component: Main }, googleImagine: { name: 'Google', icon: 'google', docs: 'https://support.google.com/googleapi/answer/6158849', - component: Google, + component: Main, internal: true }, keycloak: { name: 'Keycloak', icon: 'keycloak', docs: 'https://www.keycloak.org/securing-apps/oidc-layers', - component: Keycloak + component: Main }, kick: { name: 'Kick', @@ -170,7 +160,7 @@ export const oAuthProviders: Record = { name: 'Microsoft', icon: 'microsoft', docs: 'https://developer.microsoft.com/en-us/', - component: Microsoft + component: Main }, notion: { name: 'Notion', @@ -182,13 +172,13 @@ export const oAuthProviders: Record = { name: 'OIDC', icon: 'oidc', docs: 'https://openid.net/connect/faq/', - component: Oidc + component: Main }, okta: { name: 'Okta', icon: 'okta', docs: 'https://developer.okta.com', - component: Okta + component: Main }, paypal: { name: 'Paypal', diff --git a/src/routes/(console)/project-[region]-[project]/auth/(providers)/appleOAuth.svelte b/src/routes/(console)/project-[region]-[project]/auth/(providers)/appleOAuth.svelte deleted file mode 100644 index 34aa8c847b..0000000000 --- a/src/routes/(console)/project-[region]-[project]/auth/(providers)/appleOAuth.svelte +++ /dev/null @@ -1,87 +0,0 @@ - - - -

- To use {provider.name} authentication in your application, first fill in this form. For more info - you can - - visit the docs. - -

- - - - - - - To complete set up, add this OAuth2 redirect URI to your {provider.name} app configuration. - - - - - - - diff --git a/src/routes/(console)/project-[region]-[project]/auth/(providers)/auth0OAuth.svelte b/src/routes/(console)/project-[region]-[project]/auth/(providers)/auth0OAuth.svelte deleted file mode 100644 index fb65dadd42..0000000000 --- a/src/routes/(console)/project-[region]-[project]/auth/(providers)/auth0OAuth.svelte +++ /dev/null @@ -1,92 +0,0 @@ - - - - - To use {provider.name} authentication in your application, first fill in this form. For more info - you can - visit the docs. - - - - - - - To complete set up, add this OAuth2 redirect URI to your {provider.name} app configuration. - - - - - - - diff --git a/src/routes/(console)/project-[region]-[project]/auth/(providers)/authentikOAuth.svelte b/src/routes/(console)/project-[region]-[project]/auth/(providers)/authentikOAuth.svelte deleted file mode 100644 index 0b65d7c3b1..0000000000 --- a/src/routes/(console)/project-[region]-[project]/auth/(providers)/authentikOAuth.svelte +++ /dev/null @@ -1,96 +0,0 @@ - - - -

- To use {provider.name} authentication in your application, first fill in this form. For more info - you can - visit the docs. -

- - - - - - To complete set up, add this OAuth2 redirect URI to your {provider.name} app configuration. - - - - - - -
diff --git a/src/routes/(console)/project-[region]-[project]/auth/(providers)/fusionauthOAuth.svelte b/src/routes/(console)/project-[region]-[project]/auth/(providers)/fusionauthOAuth.svelte deleted file mode 100644 index d50993b06f..0000000000 --- a/src/routes/(console)/project-[region]-[project]/auth/(providers)/fusionauthOAuth.svelte +++ /dev/null @@ -1,98 +0,0 @@ - - - -

- To use {provider.name} authentication in your application, first fill in this form. For more info - you can - visit the docs. -

- - - - - - To complete set up, add this OAuth2 redirect URI to your {provider.name} app configuration. - - - - - - -
diff --git a/src/routes/(console)/project-[region]-[project]/auth/(providers)/gitlabOAuth.svelte b/src/routes/(console)/project-[region]-[project]/auth/(providers)/gitlabOAuth.svelte deleted file mode 100644 index 025f2419e5..0000000000 --- a/src/routes/(console)/project-[region]-[project]/auth/(providers)/gitlabOAuth.svelte +++ /dev/null @@ -1,85 +0,0 @@ - - - -

- To use {provider.name} authentication in your application, first fill in this form. For more info - you can - visit the docs. -

- - - - - - To complete set up, add this OAuth2 redirect URI to your {provider.name} app configuration. - - - - - - -
diff --git a/src/routes/(console)/project-[region]-[project]/auth/(providers)/googleOAuth.svelte b/src/routes/(console)/project-[region]-[project]/auth/(providers)/googleOAuth.svelte deleted file mode 100644 index 05e5560f2f..0000000000 --- a/src/routes/(console)/project-[region]-[project]/auth/(providers)/googleOAuth.svelte +++ /dev/null @@ -1,85 +0,0 @@ - - - - {provider.name} OAuth2 settings - -

- To use {provider.name} authentication in your application, first fill in this form. For more info - you can - visit the docs. -

- - - - - To complete the setup, create an OAuth2 client ID with "Web application" as the application - type, then add this redirect URI to your {provider.name} configuration. - - - - - - - -
diff --git a/src/routes/(console)/project-[region]-[project]/auth/(providers)/keycloakOAuth.svelte b/src/routes/(console)/project-[region]-[project]/auth/(providers)/keycloakOAuth.svelte deleted file mode 100644 index 2178387368..0000000000 --- a/src/routes/(console)/project-[region]-[project]/auth/(providers)/keycloakOAuth.svelte +++ /dev/null @@ -1,108 +0,0 @@ - - - -

- To use {provider.name} authentication in your application, first fill in this form. For more info - you can - visit the docs. -

- - - - - - - To complete set up, add this OAuth2 redirect URI to your {provider.name} app configuration. - - - - - - -
diff --git a/src/routes/(console)/project-[region]-[project]/auth/(providers)/mainOAuth.svelte b/src/routes/(console)/project-[region]-[project]/auth/(providers)/mainOAuth.svelte index 44109091b1..cf45501c4a 100644 --- a/src/routes/(console)/project-[region]-[project]/auth/(providers)/mainOAuth.svelte +++ b/src/routes/(console)/project-[region]-[project]/auth/(providers)/mainOAuth.svelte @@ -1,13 +1,31 @@ @@ -50,21 +128,99 @@ target="_blank" rel="noopener noreferrer">visit the docs.

- - - + + {#if loading} + + + + {:else} + + + {#if appIdParam} + + {/if} + + {#if secretParams.length > 0} + {#if !showSecretInput} +
+ (showSecretInput = true)}> + Update {secretCardTitle} + +
+ {:else} + + + + + {secretCardTitle} + + + + {#if secretParams.length === 1} + This field is write-only. Enter a new value to update it. + {:else} + These fields are write-only. Enter new values to update them. + {/if} + + + + + + + {#each secretParams as param} + {#if isTextareaParam(param.$id)} + + {:else if isPasswordParam(param.$id)} + + {:else} + + {/if} + {/each} + + + + {/if} + {/if} + {/if} + To complete set up, add this OAuth2 redirect URI to your {provider.name} app configuration. @@ -73,12 +229,6 @@ value={`${getApiEndpoint(page.params.region)}/account/sessions/oauth2/callback/${provider.key}/${projectId}`} /> - +
diff --git a/src/routes/(console)/project-[region]-[project]/auth/(providers)/microsoftOAuth.svelte b/src/routes/(console)/project-[region]-[project]/auth/(providers)/microsoftOAuth.svelte deleted file mode 100644 index d192a3b974..0000000000 --- a/src/routes/(console)/project-[region]-[project]/auth/(providers)/microsoftOAuth.svelte +++ /dev/null @@ -1,91 +0,0 @@ - - - -

- To use {provider.name} authentication in your application, first fill in this form. For more info - you can - - visit the docs. - -

- - - - - - To complete set up, add this OAuth2 redirect URI to your {provider.name} app configuration. - - - - - - -
diff --git a/src/routes/(console)/project-[region]-[project]/auth/(providers)/oidcOAuth.svelte b/src/routes/(console)/project-[region]-[project]/auth/(providers)/oidcOAuth.svelte deleted file mode 100644 index 377ddefa17..0000000000 --- a/src/routes/(console)/project-[region]-[project]/auth/(providers)/oidcOAuth.svelte +++ /dev/null @@ -1,134 +0,0 @@ - - - -

- To use {provider.name} authentication in your application, first fill in this form. For more info - you can - visit the docs. -

- - - - - - - - - - To complete set up, add this OAuth2 redirect URI to your {provider.name} app configuration. - - - - - - -
diff --git a/src/routes/(console)/project-[region]-[project]/auth/(providers)/oktaOAuth.svelte b/src/routes/(console)/project-[region]-[project]/auth/(providers)/oktaOAuth.svelte deleted file mode 100644 index 069211370e..0000000000 --- a/src/routes/(console)/project-[region]-[project]/auth/(providers)/oktaOAuth.svelte +++ /dev/null @@ -1,110 +0,0 @@ - - - -

- To use {provider.name} authentication in your application, first fill in this form. For more info - you can - visit the docs. -

- - - - - - - - To complete set up, add this OAuth2 redirect URI to your {provider.name} app configuration. - - - - - - -
diff --git a/src/routes/(console)/project-[region]-[project]/auth/settings/+page.svelte b/src/routes/(console)/project-[region]-[project]/auth/settings/+page.svelte index 1b04701fcd..c314cf7b62 100644 --- a/src/routes/(console)/project-[region]-[project]/auth/settings/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/auth/settings/+page.svelte @@ -30,6 +30,7 @@ let { data }: PageProps = $props(); /** Must stay derived from `data` so OAuth/auth toggles reflect `invalidate(Dependencies.PROJECT)` without a full reload. */ const project = $derived(data.project); + const resolvedOAuthProviders = $derived(data.oauthProviders); let showProvider = $state(false); let selectedProvider: Models.AuthProvider | null = $state(null); @@ -204,8 +205,8 @@ OAuth2 Providers
    - {#each project.oAuthProviders - .filter((p) => p.name !== 'Mock') + {#each resolvedOAuthProviders + .filter((p) => p.key !== 'mock' && p.name !== 'Mock') .sort( (a, b) => (a.enabled === b.enabled ? 0 : a.enabled ? -1 : 1) ) as provider} {@const oAuthProvider = oAuthProviders[provider.key]} {#if oAuthProvider && !oAuthProvider.internal} diff --git a/src/routes/(console)/project-[region]-[project]/auth/settings/+page.ts b/src/routes/(console)/project-[region]-[project]/auth/settings/+page.ts new file mode 100644 index 0000000000..7a4f2a7438 --- /dev/null +++ b/src/routes/(console)/project-[region]-[project]/auth/settings/+page.ts @@ -0,0 +1,45 @@ +import { Dependencies } from '$lib/constants'; +import { oAuthProviders } from '$lib/stores/oauth-providers'; +import { sdk } from '$lib/stores/sdk'; +import type { Models } from '@appwrite.io/console'; +import type { PageLoad } from './$types'; + +/** Extract the appId equivalent from a typed OAuth2 provider model. */ +function extractAppId(p: Record): string { + return (p['clientId'] ?? + p['appId'] ?? + p['oauthClientId'] ?? + p['oauth2ClientId'] ?? + p['applicationId'] ?? + p['serviceId'] ?? + p['apiKey'] ?? + p['publicKey'] ?? + p['appKey'] ?? + p['keyString'] ?? + p['customerKey'] ?? + p['key'] ?? + '') as string; +} + +export const load: PageLoad = async ({ depends, params }) => { + depends(Dependencies.PROJECT); + + const providerList = await sdk + .forProject(params.region, params.project) + .project.listOAuth2Providers(); + + const oauthProviders: Models.AuthProvider[] = providerList.providers + .filter((p) => p.$id !== 'mock') + .map((p) => { + const raw = p as unknown as Record; + return { + key: p.$id, + name: oAuthProviders[p.$id]?.name ?? p.$id, + appId: extractAppId(raw), + secret: '', + enabled: p.enabled + }; + }); + + return { oauthProviders }; +}; diff --git a/src/routes/(console)/project-[region]-[project]/overview/api-keys/scopes.svelte b/src/routes/(console)/project-[region]-[project]/overview/api-keys/scopes.svelte index 70608e2f0a..9d146ddab3 100644 --- a/src/routes/(console)/project-[region]-[project]/overview/api-keys/scopes.svelte +++ b/src/routes/(console)/project-[region]-[project]/overview/api-keys/scopes.svelte @@ -1,8 +1,9 @@ - From 4e55de11873df1ef0a86386ef3f620349c01f033 Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Wed, 29 Apr 2026 21:55:17 +0530 Subject: [PATCH 07/19] fix: align updateOAuth secret key names with console API parameter IDs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - authentikDomain → endpoint (Authentik) - auth0Domain → endpoint (Auth0) - tenantID → tenant, clientSecret → applicationSecret (Microsoft) - oktaDomain → domain (Okta) - keyID/teamID → keyId/teamId (Apple) - wellKnownEndpoint/authorizationEndpoint/tokenEndpoint/userinfoEndpoint → wellKnownURL/authorizationURL/tokenUrl/userInfoUrl (OIDC) --- .../auth/updateOAuth.ts | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/routes/(console)/project-[region]-[project]/auth/updateOAuth.ts b/src/routes/(console)/project-[region]-[project]/auth/updateOAuth.ts index 8a9e29d036..63741d3da0 100644 --- a/src/routes/(console)/project-[region]-[project]/auth/updateOAuth.ts +++ b/src/routes/(console)/project-[region]-[project]/auth/updateOAuth.ts @@ -44,8 +44,8 @@ async function updateProjectOAuth({ region, projectId, provider, appId, secret, case OAuthProvider.Apple: return projectSdk.updateOAuth2Apple({ serviceId: appId || undefined, - keyId: parsedSecret.keyID || undefined, - teamId: parsedSecret.teamID || undefined, + keyId: parsedSecret.keyId || undefined, + teamId: parsedSecret.teamId || undefined, p8File: parsedSecret.p8 || undefined, enabled }); @@ -53,12 +53,12 @@ async function updateProjectOAuth({ region, projectId, provider, appId, secret, return projectSdk.updateOAuth2Auth0({ clientId: appId || undefined, clientSecret: parsedSecret.clientSecret || undefined, - endpoint: parsedSecret.auth0Domain || undefined, + endpoint: parsedSecret.endpoint || undefined, enabled }); case OAuthProvider.Authentik: return projectSdk.updateOAuth2Authentik({ - endpoint: parsedSecret.authentikDomain || '', + endpoint: parsedSecret.endpoint || '', clientId: appId || undefined, clientSecret: parsedSecret.clientSecret || undefined, enabled @@ -177,9 +177,9 @@ async function updateProjectOAuth({ region, projectId, provider, appId, secret, }); case OAuthProvider.Microsoft: return projectSdk.updateOAuth2Microsoft({ - tenant: parsedSecret.tenantID || 'common', + tenant: parsedSecret.tenant || 'common', applicationId: appId || undefined, - applicationSecret: parsedSecret.clientSecret || undefined, + applicationSecret: parsedSecret.applicationSecret || undefined, enabled }); case OAuthProvider.Notion: @@ -192,17 +192,17 @@ async function updateProjectOAuth({ region, projectId, provider, appId, secret, return projectSdk.updateOAuth2Oidc({ clientId: appId || undefined, clientSecret: parsedSecret.clientSecret || undefined, - wellKnownURL: parsedSecret.wellKnownEndpoint || undefined, - authorizationURL: parsedSecret.authorizationEndpoint || undefined, - tokenUrl: parsedSecret.tokenEndpoint || undefined, - userInfoUrl: parsedSecret.userinfoEndpoint || undefined, + wellKnownURL: parsedSecret.wellKnownURL || undefined, + authorizationURL: parsedSecret.authorizationURL || undefined, + tokenUrl: parsedSecret.tokenUrl || undefined, + userInfoUrl: parsedSecret.userInfoUrl || undefined, enabled }); case OAuthProvider.Okta: return projectSdk.updateOAuth2Okta({ clientId: appId || undefined, clientSecret: parsedSecret.clientSecret || undefined, - domain: parsedSecret.oktaDomain || undefined, + domain: parsedSecret.domain || undefined, authorizationServerId: parsedSecret.authorizationServerId || undefined, enabled }); From 838d6029d2ac02cad40fc470c47a6a1919378882 Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Wed, 29 Apr 2026 21:57:49 +0530 Subject: [PATCH 08/19] fix: correct OAuth2 field name mismatches found from API response - Apple: p8 -> p8File (API field is p8File) - GitLab: clientSecret -> secret (API field is secret) - oauth-providers: paypalsandbox -> paypalSandbox (matches API $id) --- src/lib/stores/oauth-providers.ts | 2 +- .../(console)/project-[region]-[project]/auth/updateOAuth.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib/stores/oauth-providers.ts b/src/lib/stores/oauth-providers.ts index 4870b4cccb..1a2143a178 100644 --- a/src/lib/stores/oauth-providers.ts +++ b/src/lib/stores/oauth-providers.ts @@ -186,7 +186,7 @@ export const oAuthProviders: Record = { docs: 'https://developer.paypal.com/docs/api/overview/', component: Main }, - paypalsandbox: { + paypalSandbox: { name: 'Paypal Sandbox', icon: 'paypal', docs: 'https://developer.paypal.com/docs/api/overview/', diff --git a/src/routes/(console)/project-[region]-[project]/auth/updateOAuth.ts b/src/routes/(console)/project-[region]-[project]/auth/updateOAuth.ts index 63741d3da0..0ae46d03d3 100644 --- a/src/routes/(console)/project-[region]-[project]/auth/updateOAuth.ts +++ b/src/routes/(console)/project-[region]-[project]/auth/updateOAuth.ts @@ -46,7 +46,7 @@ async function updateProjectOAuth({ region, projectId, provider, appId, secret, serviceId: appId || undefined, keyId: parsedSecret.keyId || undefined, teamId: parsedSecret.teamId || undefined, - p8File: parsedSecret.p8 || undefined, + p8File: parsedSecret.p8File || undefined, enabled }); case OAuthProvider.Auth0: @@ -145,7 +145,7 @@ async function updateProjectOAuth({ region, projectId, provider, appId, secret, case OAuthProvider.Gitlab: return projectSdk.updateOAuth2Gitlab({ applicationId: appId || undefined, - secret: parsedSecret.clientSecret || undefined, + secret: parsedSecret.secret || undefined, endpoint: parsedSecret.endpoint || undefined, enabled }); From 0001eb98289be8520ca6375474f060a5403f6b2d Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Thu, 30 Apr 2026 14:37:12 +0530 Subject: [PATCH 09/19] perf: prefetch OAuth2 provider params on page load, remove modal loader Fetch listOAuth2Providers alongside listOAuth2Providers (project) in +page.ts using Promise.all, pass parameters as a prop to mainOAuth. Modal now opens instantly with no spinner or async fetch. --- .../auth/(providers)/mainOAuth.svelte | 223 ++++++++---------- .../auth/settings/+page.svelte | 2 + .../auth/settings/+page.ts | 16 +- 3 files changed, 113 insertions(+), 128 deletions(-) diff --git a/src/routes/(console)/project-[region]-[project]/auth/(providers)/mainOAuth.svelte b/src/routes/(console)/project-[region]-[project]/auth/(providers)/mainOAuth.svelte index cf45501c4a..053190a372 100644 --- a/src/routes/(console)/project-[region]-[project]/auth/(providers)/mainOAuth.svelte +++ b/src/routes/(console)/project-[region]-[project]/auth/(providers)/mainOAuth.svelte @@ -8,10 +8,9 @@ InputText, InputTextarea } from '$lib/elements/forms'; - import { onMount } from 'svelte'; import { updateOAuth } from '../updateOAuth'; - import type { Models } from '@appwrite.io/console'; - import { oAuthProviders, type Provider } from '$lib/stores/oauth-providers'; + import type { Models, Models as ConsoleModels } from '@appwrite.io/console'; + import { oAuthProviders } from '$lib/stores/oauth-providers'; import { Alert, Card, @@ -19,30 +18,37 @@ Icon, Layout, Link, - Spinner, Tag, Typography } from '@appwrite.io/pink-svelte'; import { IconPencil, IconX } from '@appwrite.io/pink-icons-svelte'; - import { getApiEndpoint, sdk } from '$lib/stores/sdk'; - import type { Models as ConsoleModels } from '@appwrite.io/console'; + import { getApiEndpoint } from '$lib/stores/sdk'; const projectId = page.params.project; const region = page.params.region; export let provider: Models.AuthProvider; export let show = false; + export let parameters: ConsoleModels.ConsoleOAuth2ProviderParameter[] = []; - let loading = true; - let enabled: boolean = null; - let appId: string = null; + $: appIdParam = parameters.length >= 1 ? parameters[0] : null; + $: secretParams = parameters.slice(1); + + let enabled: boolean = provider.enabled; + let appId: string = provider.appId || null; /** Values for all non-appId parameters, keyed by parameter.$id */ let secretValues: Record = {}; - let showSecretInput = false; - let oAuthProvider: Provider; + let showSecretInput = !provider.appId; + + $: { + for (const p of secretParams) { + if (!(p.$id in secretValues)) { + secretValues[p.$id] = null; + } + } + } - let appIdParam: ConsoleModels.ConsoleOAuth2ProviderParameter | null = null; - let secretParams: ConsoleModels.ConsoleOAuth2ProviderParameter[] = []; + $: oAuthProvider = oAuthProviders[provider.key]; /** Take only the primary name before any " or " / ", or " alternatives. */ function primaryName(name: string): string { @@ -57,29 +63,6 @@ return id === 'p8' || id.toLowerCase().includes('p8file'); } - onMount(async () => { - oAuthProvider = oAuthProviders[provider.key]; - - const providerList = await sdk.forConsole.console.listOAuth2Providers(); - const consoleProvider = providerList.oAuth2Providers.find((p) => p.$id === provider.key); - - if (consoleProvider?.parameters?.length >= 1) { - appIdParam = consoleProvider.parameters[0]; - secretParams = consoleProvider.parameters.slice(1); - } - - // Initialise all secret value slots - for (const p of secretParams) { - secretValues[p.$id] = null; - } - - // Initialise form values only after params are ready — no flash - enabled = provider.enabled; - appId = provider.appId || null; - showSecretInput = !provider.appId; - loading = false; - }); - /** Build the secret string to pass to updateOAuth. */ function buildSecret(): string | null { const entries = secretParams @@ -129,95 +112,89 @@ rel="noopener noreferrer">visit the docs.

    - {#if loading} - - - - {:else} - - - {#if appIdParam} - - {/if} + - {#if secretParams.length > 0} - {#if !showSecretInput} -
    - (showSecretInput = true)}> - Update {secretCardTitle} - -
    - {:else} - - - - - {secretCardTitle} - - - - {#if secretParams.length === 1} - This field is write-only. Enter a new value to update it. - {:else} - These fields are write-only. Enter new values to update them. - {/if} - - - - - - - {#each secretParams as param} - {#if isTextareaParam(param.$id)} - - {:else if isPasswordParam(param.$id)} - - {:else} - - {/if} - {/each} + {#if appIdParam} + + {/if} + + {#if secretParams.length > 0} + {#if !showSecretInput} +
    + (showSecretInput = true)}> + Update {secretCardTitle} + +
    + {:else} + + + + + {secretCardTitle} + + + {#if secretParams.length === 1} + This field is write-only. Enter a new value to update it. + {:else} + These fields are write-only. Enter new values to update them. + {/if} + + + + + + + {#each secretParams as param} + {#if isTextareaParam(param.$id)} + + {:else if isPasswordParam(param.$id)} + + {:else} + + {/if} + {/each} - - {/if} +
    +
    {/if} {/if} @@ -229,6 +206,6 @@ value={`${getApiEndpoint(page.params.region)}/account/sessions/oauth2/callback/${provider.key}/${projectId}`} /> - + diff --git a/src/routes/(console)/project-[region]-[project]/auth/settings/+page.svelte b/src/routes/(console)/project-[region]-[project]/auth/settings/+page.svelte index c314cf7b62..3596e8d7ea 100644 --- a/src/routes/(console)/project-[region]-[project]/auth/settings/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/auth/settings/+page.svelte @@ -31,6 +31,7 @@ /** Must stay derived from `data` so OAuth/auth toggles reflect `invalidate(Dependencies.PROJECT)` without a full reload. */ const project = $derived(data.project); const resolvedOAuthProviders = $derived(data.oauthProviders); + const consoleParamsMap = $derived(data.consoleParamsMap); let showProvider = $state(false); let selectedProvider: Models.AuthProvider | null = $state(null); @@ -253,6 +254,7 @@ { selectedProvider = null; showProvider = false; diff --git a/src/routes/(console)/project-[region]-[project]/auth/settings/+page.ts b/src/routes/(console)/project-[region]-[project]/auth/settings/+page.ts index 7a4f2a7438..1040df0e41 100644 --- a/src/routes/(console)/project-[region]-[project]/auth/settings/+page.ts +++ b/src/routes/(console)/project-[region]-[project]/auth/settings/+page.ts @@ -1,7 +1,7 @@ import { Dependencies } from '$lib/constants'; import { oAuthProviders } from '$lib/stores/oauth-providers'; import { sdk } from '$lib/stores/sdk'; -import type { Models } from '@appwrite.io/console'; +import type { Models, Models as ConsoleModels } from '@appwrite.io/console'; import type { PageLoad } from './$types'; /** Extract the appId equivalent from a typed OAuth2 provider model. */ @@ -24,9 +24,15 @@ function extractAppId(p: Record): string { export const load: PageLoad = async ({ depends, params }) => { depends(Dependencies.PROJECT); - const providerList = await sdk - .forProject(params.region, params.project) - .project.listOAuth2Providers(); + const [providerList, consoleProviderList] = await Promise.all([ + sdk.forProject(params.region, params.project).project.listOAuth2Providers(), + sdk.forConsole.console.listOAuth2Providers() + ]); + + const consoleParamsMap = new Map(); + for (const cp of consoleProviderList.oAuth2Providers) { + consoleParamsMap.set(cp.$id, cp.parameters ?? []); + } const oauthProviders: Models.AuthProvider[] = providerList.providers .filter((p) => p.$id !== 'mock') @@ -41,5 +47,5 @@ export const load: PageLoad = async ({ depends, params }) => { }; }); - return { oauthProviders }; + return { oauthProviders, consoleParamsMap }; }; From e7810c324abf83201effafb19a7476e2aead46fd Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Thu, 30 Apr 2026 14:41:42 +0530 Subject: [PATCH 10/19] feat: replace Apple p8 textarea with file upload zone p8File fields now show a drag-and-drop style upload zone by default. Users can click to upload a .p8 file directly or toggle to paste mode for manual entry. Loaded files show a confirmation state with a clear button. --- .../auth/(providers)/mainOAuth.svelte | 105 ++++++++++++++++-- 1 file changed, 95 insertions(+), 10 deletions(-) diff --git a/src/routes/(console)/project-[region]-[project]/auth/(providers)/mainOAuth.svelte b/src/routes/(console)/project-[region]-[project]/auth/(providers)/mainOAuth.svelte index 053190a372..3f4e826171 100644 --- a/src/routes/(console)/project-[region]-[project]/auth/(providers)/mainOAuth.svelte +++ b/src/routes/(console)/project-[region]-[project]/auth/(providers)/mainOAuth.svelte @@ -21,7 +21,7 @@ Tag, Typography } from '@appwrite.io/pink-svelte'; - import { IconPencil, IconX } from '@appwrite.io/pink-icons-svelte'; + import { IconDocument, IconPencil, IconUpload, IconX } from '@appwrite.io/pink-icons-svelte'; import { getApiEndpoint } from '$lib/stores/sdk'; const projectId = page.params.project; @@ -59,8 +59,18 @@ return id.toLowerCase().includes('secret'); } - function isTextareaParam(id: string): boolean { - return id === 'p8' || id.toLowerCase().includes('p8file'); + function isP8Param(id: string): boolean { + return id === 'p8File' || id === 'p8' || id.toLowerCase().includes('p8file'); + } + + let p8PasteMode: Record = {}; + + async function handleP8FileUpload(id: string, event: Event) { + const input = event.target as HTMLInputElement; + const file = input.files?.[0]; + if (!file) return; + secretValues[id] = await file.text(); + input.value = ''; } /** Build the secret string to pass to updateOAuth. */ @@ -168,13 +178,64 @@ {#each secretParams as param} - {#if isTextareaParam(param.$id)} - + {#if isP8Param(param.$id)} +
    + + {primaryName(param.name)} + + + {#if p8PasteMode[param.$id]} + + {:else} + + handleP8FileUpload(param.$id, e)} /> + {/if} +
    {:else if isPasswordParam(param.$id)} Update + + From 5686812887ad260f3dfd0c24cb18f340eed14578 Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Thu, 30 Apr 2026 15:43:06 +0530 Subject: [PATCH 11/19] fix-11993-oauth-provider-form-behavior --- .../auth/(providers)/mainOAuth.svelte | 234 ++++++++++++------ .../auth/settings/+page.ts | 1 + .../auth/updateOAuth.ts | 46 ++-- 3 files changed, 195 insertions(+), 86 deletions(-) diff --git a/src/routes/(console)/project-[region]-[project]/auth/(providers)/mainOAuth.svelte b/src/routes/(console)/project-[region]-[project]/auth/(providers)/mainOAuth.svelte index 3f4e826171..efaa03c7c3 100644 --- a/src/routes/(console)/project-[region]-[project]/auth/(providers)/mainOAuth.svelte +++ b/src/routes/(console)/project-[region]-[project]/auth/(providers)/mainOAuth.svelte @@ -9,9 +9,10 @@ InputTextarea } from '$lib/elements/forms'; import { updateOAuth } from '../updateOAuth'; - import type { Models, Models as ConsoleModels } from '@appwrite.io/console'; + import { OAuthProvider, type Models, type Models as ConsoleModels } from '@appwrite.io/console'; import { oAuthProviders } from '$lib/stores/oauth-providers'; import { + Accordion, Alert, Card, Divider, @@ -31,28 +32,69 @@ export let show = false; export let parameters: ConsoleModels.ConsoleOAuth2ProviderParameter[] = []; - $: appIdParam = parameters.length >= 1 ? parameters[0] : null; - $: secretParams = parameters.slice(1); - let enabled: boolean = provider.enabled; let appId: string = provider.appId || null; - /** Values for all non-appId parameters, keyed by parameter.$id */ - let secretValues: Record = {}; - let showSecretInput = !provider.appId; + let providerKeyId = (provider as Models.AuthProvider & { keyId?: string })?.keyId || null; + let fieldValues: Record = {}; + let showSecretInput = + !provider.appId || (provider.key === OAuthProvider.Apple && !providerKeyId); + let p8PasteMode: Record = {}; + let error: string; + + $: appIdParam = parameters.length >= 1 ? parameters[0] : null; + $: additionalParams = parameters.slice(1); + $: detailParams = additionalParams.filter((param) => !isSecretParam(param.$id)); + $: secretParams = additionalParams.filter((param) => isSecretParam(param.$id)); + $: basicDetailParams = + provider?.key === OAuthProvider.Oidc + ? detailParams.filter((param) => !isOidcAdvancedParam(param.$id)) + : detailParams; + $: advancedDetailParams = + provider?.key === OAuthProvider.Oidc + ? detailParams.filter((param) => isOidcAdvancedParam(param.$id)) + : []; + $: hasSecretInput = secretParams.some((param) => { + const value = fieldValues[param.$id]; + return value !== null && value !== ''; + }); + $: hasDetailChanges = detailParams.some((param) => { + const value = fieldValues[param.$id]; + return value !== null && value !== ''; + }); + $: nothingChanged = + enabled === provider?.enabled && + appId === provider?.appId && + !hasSecretInput && + !hasDetailChanges; + $: oAuthProvider = oAuthProviders[provider.key]; + $: secretCardTitle = + secretParams.length === 1 ? primaryName(secretParams[0]?.name ?? '') : 'Credentials'; $: { - for (const p of secretParams) { - if (!(p.$id in secretValues)) { - secretValues[p.$id] = null; + for (const param of additionalParams) { + if (!(param.$id in fieldValues)) { + fieldValues[param.$id] = getInitialFieldValue(param.$id); } } } - $: oAuthProvider = oAuthProviders[provider.key]; - - /** Take only the primary name before any " or " / ", or " alternatives. */ function primaryName(name: string): string { - return name.split(/,?\s+or\s+/i)[0].trim(); + return name.trim(); + } + + function helperText(hint?: string | null): string | undefined { + const value = hint?.trim(); + + if (!value || /^example of wrong value:/i.test(value)) { + return undefined; + } + + return value; + } + + function getInitialFieldValue(id: string): string | null { + const value = (provider as Record)[id]; + return typeof value === 'string' ? value : null; } function isPasswordParam(id: string): boolean { @@ -63,32 +105,53 @@ return id === 'p8File' || id === 'p8' || id.toLowerCase().includes('p8file'); } - let p8PasteMode: Record = {}; + function isSecretParam(id: string): boolean { + return isPasswordParam(id) || isP8Param(id); + } + + function isOidcAdvancedParam(id: string): boolean { + return id === 'authorizationURL' || id === 'tokenUrl' || id === 'userInfoUrl'; + } async function handleP8FileUpload(id: string, event: Event) { const input = event.target as HTMLInputElement; const file = input.files?.[0]; if (!file) return; - secretValues[id] = await file.text(); + + fieldValues[id] = await file.text(); input.value = ''; } - /** Build the secret string to pass to updateOAuth. */ function buildSecret(): string | null { - const entries = secretParams - .map((p) => [p.$id, secretValues[p.$id]] as [string, string | null]) - .filter(([, v]) => v !== null && v !== ''); + const entries = secretParams.flatMap((param) => { + const value = fieldValues[param.$id]; + + if (value === null || value === '') { + return []; + } + + return [[param.$id, value] as [string, string]]; + }); if (entries.length === 0) return null; if (secretParams.length === 1) return entries[0]?.[1] ?? null; + return JSON.stringify(Object.fromEntries(entries)); } - $: hasSecretInput = Object.values(secretValues).some((v) => v !== null && v !== ''); - $: nothingChanged = - enabled === provider?.enabled && appId === provider?.appId && !hasSecretInput; + function buildDetails(): Record { + return Object.fromEntries( + detailParams.map((param) => [param.$id, fieldValues[param.$id] ?? '']) + ); + } - let error: string; + function resetWriteOnlyInputs() { + showSecretInput = false; + + for (const param of secretParams) { + fieldValues[param.$id] = null; + } + } const update = async () => { const result = await updateOAuth({ @@ -97,6 +160,7 @@ provider, appId, secret: buildSecret(), + details: buildDetails(), enabled }); @@ -107,8 +171,7 @@ } }; - $: secretCardTitle = - secretParams.length === 1 ? primaryName(secretParams[0]?.name ?? '') : 'Credentials'; + // @todo Revisit explicit secret-clearing UX later today. For now, credentials remain update-only. @@ -130,10 +193,34 @@ label={primaryName(appIdParam.name)} autofocus={true} placeholder={appIdParam.example || ''} - helper={appIdParam.hint || undefined} + helper={helperText(appIdParam.hint)} bind:value={appId} /> {/if} + {#each basicDetailParams as param} + + {/each} + + {#if advancedDetailParams.length > 0} + + + {#each advancedDetailParams as param} + + {/each} + + + {/if} + {#if secretParams.length > 0} {#if !showSecretInput}
    @@ -153,14 +240,7 @@ justifyContent="space-between" alignContent="center"> {secretCardTitle} - @@ -179,46 +259,46 @@ {#each secretParams as param} {#if isP8Param(param.$id)} -
    - +
    +
    {primaryName(param.name)} - - +
    + +
    +
    {#if p8PasteMode[param.$id]} + helper={helperText(param.hint)} + bind:value={fieldValues[param.$id]} /> {:else}
    - {:else if isPasswordParam(param.$id)} + {:else} - {:else} - + bind:value={fieldValues[param.$id]} /> {/if} {/each}
    @@ -293,4 +367,26 @@ border-style: solid; border-color: var(--border-neutral-primary); } + + .p8-field { + display: flex; + flex-direction: column; + gap: var(--space-3); + } + + .p8-field-header { + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--space-4); + min-width: 0; + } + + .p8-field-controls { + display: inline-flex; + align-items: center; + gap: var(--space-4); + min-width: 0; + flex-shrink: 0; + } diff --git a/src/routes/(console)/project-[region]-[project]/auth/settings/+page.ts b/src/routes/(console)/project-[region]-[project]/auth/settings/+page.ts index 1040df0e41..0aa058f7e7 100644 --- a/src/routes/(console)/project-[region]-[project]/auth/settings/+page.ts +++ b/src/routes/(console)/project-[region]-[project]/auth/settings/+page.ts @@ -39,6 +39,7 @@ export const load: PageLoad = async ({ depends, params }) => { .map((p) => { const raw = p as unknown as Record; return { + ...raw, key: p.$id, name: oAuthProviders[p.$id]?.name ?? p.$id, appId: extractAppId(raw), diff --git a/src/routes/(console)/project-[region]-[project]/auth/updateOAuth.ts b/src/routes/(console)/project-[region]-[project]/auth/updateOAuth.ts index 0ae46d03d3..55c1af5ea0 100644 --- a/src/routes/(console)/project-[region]-[project]/auth/updateOAuth.ts +++ b/src/routes/(console)/project-[region]-[project]/auth/updateOAuth.ts @@ -12,6 +12,7 @@ type Args = { provider: Models.AuthProvider; appId: string; secret: string; + details: Record; enabled: boolean; }; @@ -30,10 +31,20 @@ function parseSecret(secret: string) { } } -async function updateProjectOAuth({ region, projectId, provider, appId, secret, enabled }: Args) { +async function updateProjectOAuth({ + region, + projectId, + provider, + appId, + secret, + details, + enabled +}: Args) { const projectSdk = sdk.forProject(region, projectId).project; const parsedSecret = parseSecret(secret); + const getDetail = (key: string): string => details[key] ?? ''; + switch (provider.key) { case OAuthProvider.Amazon: return projectSdk.updateOAuth2Amazon({ @@ -44,8 +55,8 @@ async function updateProjectOAuth({ region, projectId, provider, appId, secret, case OAuthProvider.Apple: return projectSdk.updateOAuth2Apple({ serviceId: appId || undefined, - keyId: parsedSecret.keyId || undefined, - teamId: parsedSecret.teamId || undefined, + keyId: getDetail('keyId'), + teamId: getDetail('teamId'), p8File: parsedSecret.p8File || undefined, enabled }); @@ -53,12 +64,12 @@ async function updateProjectOAuth({ region, projectId, provider, appId, secret, return projectSdk.updateOAuth2Auth0({ clientId: appId || undefined, clientSecret: parsedSecret.clientSecret || undefined, - endpoint: parsedSecret.endpoint || undefined, + endpoint: getDetail('endpoint'), enabled }); case OAuthProvider.Authentik: return projectSdk.updateOAuth2Authentik({ - endpoint: parsedSecret.endpoint || '', + endpoint: getDetail('endpoint'), clientId: appId || undefined, clientSecret: parsedSecret.clientSecret || undefined, enabled @@ -131,7 +142,7 @@ async function updateProjectOAuth({ region, projectId, provider, appId, secret, }); case OAuthProvider.Fusionauth: return projectSdk.updateOAuth2FusionAuth({ - endpoint: parsedSecret.endpoint || '', + endpoint: getDetail('endpoint'), clientId: appId || undefined, clientSecret: parsedSecret.clientSecret || undefined, enabled @@ -146,7 +157,7 @@ async function updateProjectOAuth({ region, projectId, provider, appId, secret, return projectSdk.updateOAuth2Gitlab({ applicationId: appId || undefined, secret: parsedSecret.secret || undefined, - endpoint: parsedSecret.endpoint || undefined, + endpoint: getDetail('endpoint'), enabled }); case OAuthProvider.Google: @@ -157,8 +168,8 @@ async function updateProjectOAuth({ region, projectId, provider, appId, secret, }); case OAuthProvider.Keycloak: return projectSdk.updateOAuth2Keycloak({ - endpoint: parsedSecret.endpoint || '', - realmName: parsedSecret.realmName || '', + endpoint: getDetail('endpoint'), + realmName: getDetail('realmName'), clientId: appId || undefined, clientSecret: parsedSecret.clientSecret || undefined, enabled @@ -177,7 +188,7 @@ async function updateProjectOAuth({ region, projectId, provider, appId, secret, }); case OAuthProvider.Microsoft: return projectSdk.updateOAuth2Microsoft({ - tenant: parsedSecret.tenant || 'common', + tenant: getDetail('tenant'), applicationId: appId || undefined, applicationSecret: parsedSecret.applicationSecret || undefined, enabled @@ -192,18 +203,18 @@ async function updateProjectOAuth({ region, projectId, provider, appId, secret, return projectSdk.updateOAuth2Oidc({ clientId: appId || undefined, clientSecret: parsedSecret.clientSecret || undefined, - wellKnownURL: parsedSecret.wellKnownURL || undefined, - authorizationURL: parsedSecret.authorizationURL || undefined, - tokenUrl: parsedSecret.tokenUrl || undefined, - userInfoUrl: parsedSecret.userInfoUrl || undefined, + wellKnownURL: getDetail('wellKnownURL'), + authorizationURL: getDetail('authorizationURL'), + tokenUrl: getDetail('tokenUrl'), + userInfoUrl: getDetail('userInfoUrl'), enabled }); case OAuthProvider.Okta: return projectSdk.updateOAuth2Okta({ clientId: appId || undefined, clientSecret: parsedSecret.clientSecret || undefined, - domain: parsedSecret.domain || undefined, - authorizationServerId: parsedSecret.authorizationServerId || undefined, + domain: getDetail('domain'), + authorizationServerId: getDetail('authorizationServerId'), enabled }); case OAuthProvider.Paypal: @@ -313,6 +324,7 @@ export async function updateOAuth({ provider, appId, secret, + details, enabled }: Args): Promise { try { @@ -320,7 +332,7 @@ export async function updateOAuth({ throw new Error(`Invalid OAuth2 provider: ${provider.key}`); } - await updateProjectOAuth({ region, projectId, provider, appId, secret, enabled }); + await updateProjectOAuth({ region, projectId, provider, appId, secret, details, enabled }); await invalidate(Dependencies.PROJECT); addNotification({ From 2301a005637c62b42801354e645bcf97b2c56343 Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Thu, 30 Apr 2026 15:47:59 +0530 Subject: [PATCH 12/19] fix-11993-oauth-modal-dirty-state --- .../auth/(providers)/mainOAuth.svelte | 64 +++++++++++++++---- 1 file changed, 50 insertions(+), 14 deletions(-) diff --git a/src/routes/(console)/project-[region]-[project]/auth/(providers)/mainOAuth.svelte b/src/routes/(console)/project-[region]-[project]/auth/(providers)/mainOAuth.svelte index efaa03c7c3..9f39623907 100644 --- a/src/routes/(console)/project-[region]-[project]/auth/(providers)/mainOAuth.svelte +++ b/src/routes/(console)/project-[region]-[project]/auth/(providers)/mainOAuth.svelte @@ -33,13 +33,16 @@ export let parameters: ConsoleModels.ConsoleOAuth2ProviderParameter[] = []; let enabled: boolean = provider.enabled; - let appId: string = provider.appId || null; - let providerKeyId = (provider as Models.AuthProvider & { keyId?: string })?.keyId || null; + let appId: string | null = null; + let providerKeyId: string | null = null; let fieldValues: Record = {}; - let showSecretInput = - !provider.appId || (provider.key === OAuthProvider.Apple && !providerKeyId); + let showSecretInput = false; let p8PasteMode: Record = {}; let error: string; + let initializedProvider: Models.AuthProvider | null = null; + let initialEnabled = false; + let initialAppId: string | null = null; + let initialDetailValues: Record = {}; $: appIdParam = parameters.length >= 1 ? parameters[0] : null; $: additionalParams = parameters.slice(1); @@ -58,24 +61,34 @@ return value !== null && value !== ''; }); $: hasDetailChanges = detailParams.some((param) => { - const value = fieldValues[param.$id]; - return value !== null && value !== ''; + return normalizeFieldValue(fieldValues[param.$id]) !== (initialDetailValues[param.$id] ?? ''); }); $: nothingChanged = - enabled === provider?.enabled && - appId === provider?.appId && + enabled === initialEnabled && + normalizeFieldValue(appId) === normalizeFieldValue(initialAppId) && !hasSecretInput && !hasDetailChanges; $: oAuthProvider = oAuthProviders[provider.key]; $: secretCardTitle = secretParams.length === 1 ? primaryName(secretParams[0]?.name ?? '') : 'Credentials'; - $: { - for (const param of additionalParams) { - if (!(param.$id in fieldValues)) { - fieldValues[param.$id] = getInitialFieldValue(param.$id); - } - } + $: if (provider && provider !== initializedProvider) { + initializedProvider = provider; + initialEnabled = provider.enabled; + initialAppId = getInitialAppId(); + providerKeyId = getInitialFieldValue('keyId'); + initialDetailValues = Object.fromEntries( + detailParams.map((param) => [param.$id, getInitialFieldValue(param.$id) ?? '']) + ); + enabled = initialEnabled; + appId = initialAppId; + fieldValues = Object.fromEntries( + additionalParams.map((param) => [param.$id, getInitialFieldValue(param.$id)]) + ); + p8PasteMode = {}; + showSecretInput = + !appId || (provider.key === OAuthProvider.Apple && !providerKeyId); + error = undefined; } function primaryName(name: string): string { @@ -97,6 +110,29 @@ return typeof value === 'string' ? value : null; } + function getInitialAppId(): string | null { + const raw = provider as Record; + const value = + raw['clientId'] ?? + raw['appId'] ?? + raw['oauthClientId'] ?? + raw['oauth2ClientId'] ?? + raw['applicationId'] ?? + raw['serviceId'] ?? + raw['apiKey'] ?? + raw['publicKey'] ?? + raw['appKey'] ?? + raw['keyString'] ?? + raw['customerKey'] ?? + raw['key']; + + return typeof value === 'string' ? value : null; + } + + function normalizeFieldValue(value: string | null | undefined): string { + return value ?? ''; + } + function isPasswordParam(id: string): boolean { return id.toLowerCase().includes('secret'); } From 16060fbeb55162bb6706cb4bd6b73fd734108971 Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Thu, 30 Apr 2026 15:50:57 +0530 Subject: [PATCH 13/19] fix-11993-restore-oauth-helper-text --- .../auth/(providers)/mainOAuth.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/(console)/project-[region]-[project]/auth/(providers)/mainOAuth.svelte b/src/routes/(console)/project-[region]-[project]/auth/(providers)/mainOAuth.svelte index 9f39623907..d398cb5231 100644 --- a/src/routes/(console)/project-[region]-[project]/auth/(providers)/mainOAuth.svelte +++ b/src/routes/(console)/project-[region]-[project]/auth/(providers)/mainOAuth.svelte @@ -98,7 +98,7 @@ function helperText(hint?: string | null): string | undefined { const value = hint?.trim(); - if (!value || /^example of wrong value:/i.test(value)) { + if (!value) { return undefined; } From 5654f12dddc21523477a5ac0919ea185849a4093 Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Thu, 30 Apr 2026 15:57:37 +0530 Subject: [PATCH 14/19] fix-11993-clear-non-secret-oauth-fields --- .../auth/updateOAuth.ts | 87 ++++++++++--------- 1 file changed, 44 insertions(+), 43 deletions(-) diff --git a/src/routes/(console)/project-[region]-[project]/auth/updateOAuth.ts b/src/routes/(console)/project-[region]-[project]/auth/updateOAuth.ts index 55c1af5ea0..6bdb87a4b2 100644 --- a/src/routes/(console)/project-[region]-[project]/auth/updateOAuth.ts +++ b/src/routes/(console)/project-[region]-[project]/auth/updateOAuth.ts @@ -10,7 +10,7 @@ type Args = { region: string; projectId: string; provider: Models.AuthProvider; - appId: string; + appId: string | null; secret: string; details: Record; enabled: boolean; @@ -43,18 +43,19 @@ async function updateProjectOAuth({ const projectSdk = sdk.forProject(region, projectId).project; const parsedSecret = parseSecret(secret); + const getAppId = (): string => appId ?? ''; const getDetail = (key: string): string => details[key] ?? ''; switch (provider.key) { case OAuthProvider.Amazon: return projectSdk.updateOAuth2Amazon({ - clientId: appId || undefined, + clientId: getAppId(), clientSecret: secret || undefined, enabled }); case OAuthProvider.Apple: return projectSdk.updateOAuth2Apple({ - serviceId: appId || undefined, + serviceId: getAppId(), keyId: getDetail('keyId'), teamId: getDetail('teamId'), p8File: parsedSecret.p8File || undefined, @@ -62,7 +63,7 @@ async function updateProjectOAuth({ }); case OAuthProvider.Auth0: return projectSdk.updateOAuth2Auth0({ - clientId: appId || undefined, + clientId: getAppId(), clientSecret: parsedSecret.clientSecret || undefined, endpoint: getDetail('endpoint'), enabled @@ -70,99 +71,99 @@ async function updateProjectOAuth({ case OAuthProvider.Authentik: return projectSdk.updateOAuth2Authentik({ endpoint: getDetail('endpoint'), - clientId: appId || undefined, + clientId: getAppId(), clientSecret: parsedSecret.clientSecret || undefined, enabled }); case OAuthProvider.Autodesk: return projectSdk.updateOAuth2Autodesk({ - clientId: appId || undefined, + clientId: getAppId(), clientSecret: secret || undefined, enabled }); case OAuthProvider.Bitbucket: return projectSdk.updateOAuth2Bitbucket({ - key: appId || undefined, + key: getAppId(), secret: secret || undefined, enabled }); case OAuthProvider.Bitly: return projectSdk.updateOAuth2Bitly({ - clientId: appId || undefined, + clientId: getAppId(), clientSecret: secret || undefined, enabled }); case OAuthProvider.Box: return projectSdk.updateOAuth2Box({ - clientId: appId || undefined, + clientId: getAppId(), clientSecret: secret || undefined, enabled }); case OAuthProvider.Dailymotion: return projectSdk.updateOAuth2Dailymotion({ - apiKey: appId || undefined, + apiKey: getAppId(), apiSecret: secret || undefined, enabled }); case OAuthProvider.Discord: return projectSdk.updateOAuth2Discord({ - clientId: appId || undefined, + clientId: getAppId(), clientSecret: secret || undefined, enabled }); case OAuthProvider.Disqus: return projectSdk.updateOAuth2Disqus({ - publicKey: appId || undefined, + publicKey: getAppId(), secretKey: secret || undefined, enabled }); case OAuthProvider.Dropbox: return projectSdk.updateOAuth2Dropbox({ - appKey: appId || undefined, + appKey: getAppId(), appSecret: secret || undefined, enabled }); case OAuthProvider.Etsy: return projectSdk.updateOAuth2Etsy({ - keyString: appId || undefined, + keyString: getAppId(), sharedSecret: secret || undefined, enabled }); case OAuthProvider.Facebook: return projectSdk.updateOAuth2Facebook({ - appId: appId || undefined, + appId: getAppId(), appSecret: secret || undefined, enabled }); case OAuthProvider.Figma: return projectSdk.updateOAuth2Figma({ - clientId: appId || undefined, + clientId: getAppId(), clientSecret: secret || undefined, enabled }); case OAuthProvider.Fusionauth: return projectSdk.updateOAuth2FusionAuth({ endpoint: getDetail('endpoint'), - clientId: appId || undefined, + clientId: getAppId(), clientSecret: parsedSecret.clientSecret || undefined, enabled }); case OAuthProvider.Github: return projectSdk.updateOAuth2GitHub({ - clientId: appId || undefined, + clientId: getAppId(), clientSecret: secret || undefined, enabled }); case OAuthProvider.Gitlab: return projectSdk.updateOAuth2Gitlab({ - applicationId: appId || undefined, + applicationId: getAppId(), secret: parsedSecret.secret || undefined, endpoint: getDetail('endpoint'), enabled }); case OAuthProvider.Google: return projectSdk.updateOAuth2Google({ - clientId: appId || undefined, + clientId: getAppId(), clientSecret: secret || undefined, enabled }); @@ -170,38 +171,38 @@ async function updateProjectOAuth({ return projectSdk.updateOAuth2Keycloak({ endpoint: getDetail('endpoint'), realmName: getDetail('realmName'), - clientId: appId || undefined, + clientId: getAppId(), clientSecret: parsedSecret.clientSecret || undefined, enabled }); case OAuthProvider.Kick: return projectSdk.updateOAuth2Kick({ - clientId: appId || undefined, + clientId: getAppId(), clientSecret: secret || undefined, enabled }); case OAuthProvider.Linkedin: return projectSdk.updateOAuth2Linkedin({ - clientId: appId || undefined, + clientId: getAppId(), primaryClientSecret: secret || undefined, enabled }); case OAuthProvider.Microsoft: return projectSdk.updateOAuth2Microsoft({ tenant: getDetail('tenant'), - applicationId: appId || undefined, + applicationId: getAppId(), applicationSecret: parsedSecret.applicationSecret || undefined, enabled }); case OAuthProvider.Notion: return projectSdk.updateOAuth2Notion({ - oauthClientId: appId || undefined, + oauthClientId: getAppId(), oauthClientSecret: secret || undefined, enabled }); case OAuthProvider.Oidc: return projectSdk.updateOAuth2Oidc({ - clientId: appId || undefined, + clientId: getAppId(), clientSecret: parsedSecret.clientSecret || undefined, wellKnownURL: getDetail('wellKnownURL'), authorizationURL: getDetail('authorizationURL'), @@ -211,7 +212,7 @@ async function updateProjectOAuth({ }); case OAuthProvider.Okta: return projectSdk.updateOAuth2Okta({ - clientId: appId || undefined, + clientId: getAppId(), clientSecret: parsedSecret.clientSecret || undefined, domain: getDetail('domain'), authorizationServerId: getDetail('authorizationServerId'), @@ -219,97 +220,97 @@ async function updateProjectOAuth({ }); case OAuthProvider.Paypal: return projectSdk.updateOAuth2Paypal({ - clientId: appId || undefined, + clientId: getAppId(), secretKey: secret || undefined, enabled }); case OAuthProvider.PaypalSandbox: return projectSdk.updateOAuth2PaypalSandbox({ - clientId: appId || undefined, + clientId: getAppId(), secretKey: secret || undefined, enabled }); case OAuthProvider.Podio: return projectSdk.updateOAuth2Podio({ - clientId: appId || undefined, + clientId: getAppId(), clientSecret: secret || undefined, enabled }); case OAuthProvider.Salesforce: return projectSdk.updateOAuth2Salesforce({ - customerKey: appId || undefined, + customerKey: getAppId(), customerSecret: secret || undefined, enabled }); case OAuthProvider.Slack: return projectSdk.updateOAuth2Slack({ - clientId: appId || undefined, + clientId: getAppId(), clientSecret: secret || undefined, enabled }); case OAuthProvider.Spotify: return projectSdk.updateOAuth2Spotify({ - clientId: appId || undefined, + clientId: getAppId(), clientSecret: secret || undefined, enabled }); case OAuthProvider.Stripe: return projectSdk.updateOAuth2Stripe({ - clientId: appId || undefined, + clientId: getAppId(), apiSecretKey: secret || undefined, enabled }); case OAuthProvider.Tradeshift: return projectSdk.updateOAuth2Tradeshift({ - oauth2ClientId: appId || undefined, + oauth2ClientId: getAppId(), oauth2ClientSecret: secret || undefined, enabled }); case OAuthProvider.TradeshiftBox: return projectSdk.updateOAuth2TradeshiftSandbox({ - oauth2ClientId: appId || undefined, + oauth2ClientId: getAppId(), oauth2ClientSecret: secret || undefined, enabled }); case OAuthProvider.Twitch: return projectSdk.updateOAuth2Twitch({ - clientId: appId || undefined, + clientId: getAppId(), clientSecret: secret || undefined, enabled }); case OAuthProvider.Wordpress: return projectSdk.updateOAuth2WordPress({ - clientId: appId || undefined, + clientId: getAppId(), clientSecret: secret || undefined, enabled }); case OAuthProvider.X: return projectSdk.updateOAuth2X({ - customerKey: appId || undefined, + customerKey: getAppId(), secretKey: secret || undefined, enabled }); case OAuthProvider.Yahoo: return projectSdk.updateOAuth2Yahoo({ - clientId: appId || undefined, + clientId: getAppId(), clientSecret: secret || undefined, enabled }); case OAuthProvider.Yandex: return projectSdk.updateOAuth2Yandex({ - clientId: appId || undefined, + clientId: getAppId(), clientSecret: secret || undefined, enabled }); case OAuthProvider.Zoho: return projectSdk.updateOAuth2Zoho({ - clientId: appId || undefined, + clientId: getAppId(), clientSecret: secret || undefined, enabled }); case OAuthProvider.Zoom: return projectSdk.updateOAuth2Zoom({ - clientId: appId || undefined, + clientId: getAppId(), clientSecret: secret || undefined, enabled }); From 02b1d5e4fc5ab60777f1ea6de986700f56d0573f Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Thu, 30 Apr 2026 16:00:32 +0530 Subject: [PATCH 15/19] fix lint --- .../auth/(providers)/mainOAuth.svelte | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/routes/(console)/project-[region]-[project]/auth/(providers)/mainOAuth.svelte b/src/routes/(console)/project-[region]-[project]/auth/(providers)/mainOAuth.svelte index d398cb5231..a101474282 100644 --- a/src/routes/(console)/project-[region]-[project]/auth/(providers)/mainOAuth.svelte +++ b/src/routes/(console)/project-[region]-[project]/auth/(providers)/mainOAuth.svelte @@ -61,7 +61,9 @@ return value !== null && value !== ''; }); $: hasDetailChanges = detailParams.some((param) => { - return normalizeFieldValue(fieldValues[param.$id]) !== (initialDetailValues[param.$id] ?? ''); + return ( + normalizeFieldValue(fieldValues[param.$id]) !== (initialDetailValues[param.$id] ?? '') + ); }); $: nothingChanged = enabled === initialEnabled && @@ -86,8 +88,7 @@ additionalParams.map((param) => [param.$id, getInitialFieldValue(param.$id)]) ); p8PasteMode = {}; - showSecretInput = - !appId || (provider.key === OAuthProvider.Apple && !providerKeyId); + showSecretInput = !appId || (provider.key === OAuthProvider.Apple && !providerKeyId); error = undefined; } From 84ad093201735fbfdb062974f054b21a125c097b Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Thu, 30 Apr 2026 16:04:46 +0530 Subject: [PATCH 16/19] fix-11993-normalize-oauth-secret-payloads --- .../auth/(providers)/mainOAuth.svelte | 1 - .../auth/updateOAuth.ts | 96 +++++++++++-------- 2 files changed, 54 insertions(+), 43 deletions(-) diff --git a/src/routes/(console)/project-[region]-[project]/auth/(providers)/mainOAuth.svelte b/src/routes/(console)/project-[region]-[project]/auth/(providers)/mainOAuth.svelte index a101474282..4c826b647b 100644 --- a/src/routes/(console)/project-[region]-[project]/auth/(providers)/mainOAuth.svelte +++ b/src/routes/(console)/project-[region]-[project]/auth/(providers)/mainOAuth.svelte @@ -171,7 +171,6 @@ }); if (entries.length === 0) return null; - if (secretParams.length === 1) return entries[0]?.[1] ?? null; return JSON.stringify(Object.fromEntries(entries)); } diff --git a/src/routes/(console)/project-[region]-[project]/auth/updateOAuth.ts b/src/routes/(console)/project-[region]-[project]/auth/updateOAuth.ts index 6bdb87a4b2..0c21955027 100644 --- a/src/routes/(console)/project-[region]-[project]/auth/updateOAuth.ts +++ b/src/routes/(console)/project-[region]-[project]/auth/updateOAuth.ts @@ -45,12 +45,24 @@ async function updateProjectOAuth({ const getAppId = (): string => appId ?? ''; const getDetail = (key: string): string => details[key] ?? ''; + const getSecret = (key?: string): string | undefined => { + if (key) { + const value = parsedSecret[key]; + return typeof value === 'string' && value !== '' ? value : undefined; + } + + const firstValue = Object.values(parsedSecret).find( + (value): value is string => typeof value === 'string' && value !== '' + ); + + return firstValue; + }; switch (provider.key) { case OAuthProvider.Amazon: return projectSdk.updateOAuth2Amazon({ clientId: getAppId(), - clientSecret: secret || undefined, + clientSecret: getSecret(), enabled }); case OAuthProvider.Apple: @@ -58,13 +70,13 @@ async function updateProjectOAuth({ serviceId: getAppId(), keyId: getDetail('keyId'), teamId: getDetail('teamId'), - p8File: parsedSecret.p8File || undefined, + p8File: getSecret('p8File'), enabled }); case OAuthProvider.Auth0: return projectSdk.updateOAuth2Auth0({ clientId: getAppId(), - clientSecret: parsedSecret.clientSecret || undefined, + clientSecret: getSecret('clientSecret'), endpoint: getDetail('endpoint'), enabled }); @@ -72,99 +84,99 @@ async function updateProjectOAuth({ return projectSdk.updateOAuth2Authentik({ endpoint: getDetail('endpoint'), clientId: getAppId(), - clientSecret: parsedSecret.clientSecret || undefined, + clientSecret: getSecret('clientSecret'), enabled }); case OAuthProvider.Autodesk: return projectSdk.updateOAuth2Autodesk({ clientId: getAppId(), - clientSecret: secret || undefined, + clientSecret: getSecret(), enabled }); case OAuthProvider.Bitbucket: return projectSdk.updateOAuth2Bitbucket({ key: getAppId(), - secret: secret || undefined, + secret: getSecret(), enabled }); case OAuthProvider.Bitly: return projectSdk.updateOAuth2Bitly({ clientId: getAppId(), - clientSecret: secret || undefined, + clientSecret: getSecret(), enabled }); case OAuthProvider.Box: return projectSdk.updateOAuth2Box({ clientId: getAppId(), - clientSecret: secret || undefined, + clientSecret: getSecret(), enabled }); case OAuthProvider.Dailymotion: return projectSdk.updateOAuth2Dailymotion({ apiKey: getAppId(), - apiSecret: secret || undefined, + apiSecret: getSecret(), enabled }); case OAuthProvider.Discord: return projectSdk.updateOAuth2Discord({ clientId: getAppId(), - clientSecret: secret || undefined, + clientSecret: getSecret(), enabled }); case OAuthProvider.Disqus: return projectSdk.updateOAuth2Disqus({ publicKey: getAppId(), - secretKey: secret || undefined, + secretKey: getSecret(), enabled }); case OAuthProvider.Dropbox: return projectSdk.updateOAuth2Dropbox({ appKey: getAppId(), - appSecret: secret || undefined, + appSecret: getSecret(), enabled }); case OAuthProvider.Etsy: return projectSdk.updateOAuth2Etsy({ keyString: getAppId(), - sharedSecret: secret || undefined, + sharedSecret: getSecret(), enabled }); case OAuthProvider.Facebook: return projectSdk.updateOAuth2Facebook({ appId: getAppId(), - appSecret: secret || undefined, + appSecret: getSecret(), enabled }); case OAuthProvider.Figma: return projectSdk.updateOAuth2Figma({ clientId: getAppId(), - clientSecret: secret || undefined, + clientSecret: getSecret(), enabled }); case OAuthProvider.Fusionauth: return projectSdk.updateOAuth2FusionAuth({ endpoint: getDetail('endpoint'), clientId: getAppId(), - clientSecret: parsedSecret.clientSecret || undefined, + clientSecret: getSecret('clientSecret'), enabled }); case OAuthProvider.Github: return projectSdk.updateOAuth2GitHub({ clientId: getAppId(), - clientSecret: secret || undefined, + clientSecret: getSecret(), enabled }); case OAuthProvider.Gitlab: return projectSdk.updateOAuth2Gitlab({ applicationId: getAppId(), - secret: parsedSecret.secret || undefined, + secret: getSecret('secret'), endpoint: getDetail('endpoint'), enabled }); case OAuthProvider.Google: return projectSdk.updateOAuth2Google({ clientId: getAppId(), - clientSecret: secret || undefined, + clientSecret: getSecret(), enabled }); case OAuthProvider.Keycloak: @@ -172,38 +184,38 @@ async function updateProjectOAuth({ endpoint: getDetail('endpoint'), realmName: getDetail('realmName'), clientId: getAppId(), - clientSecret: parsedSecret.clientSecret || undefined, + clientSecret: getSecret('clientSecret'), enabled }); case OAuthProvider.Kick: return projectSdk.updateOAuth2Kick({ clientId: getAppId(), - clientSecret: secret || undefined, + clientSecret: getSecret(), enabled }); case OAuthProvider.Linkedin: return projectSdk.updateOAuth2Linkedin({ clientId: getAppId(), - primaryClientSecret: secret || undefined, + primaryClientSecret: getSecret(), enabled }); case OAuthProvider.Microsoft: return projectSdk.updateOAuth2Microsoft({ tenant: getDetail('tenant'), applicationId: getAppId(), - applicationSecret: parsedSecret.applicationSecret || undefined, + applicationSecret: getSecret('applicationSecret'), enabled }); case OAuthProvider.Notion: return projectSdk.updateOAuth2Notion({ oauthClientId: getAppId(), - oauthClientSecret: secret || undefined, + oauthClientSecret: getSecret(), enabled }); case OAuthProvider.Oidc: return projectSdk.updateOAuth2Oidc({ clientId: getAppId(), - clientSecret: parsedSecret.clientSecret || undefined, + clientSecret: getSecret('clientSecret'), wellKnownURL: getDetail('wellKnownURL'), authorizationURL: getDetail('authorizationURL'), tokenUrl: getDetail('tokenUrl'), @@ -213,7 +225,7 @@ async function updateProjectOAuth({ case OAuthProvider.Okta: return projectSdk.updateOAuth2Okta({ clientId: getAppId(), - clientSecret: parsedSecret.clientSecret || undefined, + clientSecret: getSecret('clientSecret'), domain: getDetail('domain'), authorizationServerId: getDetail('authorizationServerId'), enabled @@ -221,97 +233,97 @@ async function updateProjectOAuth({ case OAuthProvider.Paypal: return projectSdk.updateOAuth2Paypal({ clientId: getAppId(), - secretKey: secret || undefined, + secretKey: getSecret(), enabled }); case OAuthProvider.PaypalSandbox: return projectSdk.updateOAuth2PaypalSandbox({ clientId: getAppId(), - secretKey: secret || undefined, + secretKey: getSecret(), enabled }); case OAuthProvider.Podio: return projectSdk.updateOAuth2Podio({ clientId: getAppId(), - clientSecret: secret || undefined, + clientSecret: getSecret(), enabled }); case OAuthProvider.Salesforce: return projectSdk.updateOAuth2Salesforce({ customerKey: getAppId(), - customerSecret: secret || undefined, + customerSecret: getSecret(), enabled }); case OAuthProvider.Slack: return projectSdk.updateOAuth2Slack({ clientId: getAppId(), - clientSecret: secret || undefined, + clientSecret: getSecret(), enabled }); case OAuthProvider.Spotify: return projectSdk.updateOAuth2Spotify({ clientId: getAppId(), - clientSecret: secret || undefined, + clientSecret: getSecret(), enabled }); case OAuthProvider.Stripe: return projectSdk.updateOAuth2Stripe({ clientId: getAppId(), - apiSecretKey: secret || undefined, + apiSecretKey: getSecret(), enabled }); case OAuthProvider.Tradeshift: return projectSdk.updateOAuth2Tradeshift({ oauth2ClientId: getAppId(), - oauth2ClientSecret: secret || undefined, + oauth2ClientSecret: getSecret(), enabled }); case OAuthProvider.TradeshiftBox: return projectSdk.updateOAuth2TradeshiftSandbox({ oauth2ClientId: getAppId(), - oauth2ClientSecret: secret || undefined, + oauth2ClientSecret: getSecret(), enabled }); case OAuthProvider.Twitch: return projectSdk.updateOAuth2Twitch({ clientId: getAppId(), - clientSecret: secret || undefined, + clientSecret: getSecret(), enabled }); case OAuthProvider.Wordpress: return projectSdk.updateOAuth2WordPress({ clientId: getAppId(), - clientSecret: secret || undefined, + clientSecret: getSecret(), enabled }); case OAuthProvider.X: return projectSdk.updateOAuth2X({ customerKey: getAppId(), - secretKey: secret || undefined, + secretKey: getSecret(), enabled }); case OAuthProvider.Yahoo: return projectSdk.updateOAuth2Yahoo({ clientId: getAppId(), - clientSecret: secret || undefined, + clientSecret: getSecret(), enabled }); case OAuthProvider.Yandex: return projectSdk.updateOAuth2Yandex({ clientId: getAppId(), - clientSecret: secret || undefined, + clientSecret: getSecret(), enabled }); case OAuthProvider.Zoho: return projectSdk.updateOAuth2Zoho({ clientId: getAppId(), - clientSecret: secret || undefined, + clientSecret: getSecret(), enabled }); case OAuthProvider.Zoom: return projectSdk.updateOAuth2Zoom({ clientId: getAppId(), - clientSecret: secret || undefined, + clientSecret: getSecret(), enabled }); default: From 05e672b61862538240e7805ee23812a97ee8a87d Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Thu, 30 Apr 2026 19:13:15 +0530 Subject: [PATCH 17/19] fix: show error alert when listProjectScopes fails in scopes.svelte Previously a rejected API call left allScopesList empty and mounted stuck at false with no feedback to the user. Now catches the error, displays an inline alert, and sets mounted in finally so the component settles cleanly. --- .../overview/api-keys/scopes.svelte | 35 ++++++++++++------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/src/routes/(console)/project-[region]-[project]/overview/api-keys/scopes.svelte b/src/routes/(console)/project-[region]-[project]/overview/api-keys/scopes.svelte index 9d146ddab3..18d1a1acc5 100644 --- a/src/routes/(console)/project-[region]-[project]/overview/api-keys/scopes.svelte +++ b/src/routes/(console)/project-[region]-[project]/overview/api-keys/scopes.svelte @@ -33,13 +33,14 @@ import { symmetricDifference } from '$lib/helpers/array'; import { cloudOnlyBackupScopes, type ScopeDefinition } from '$lib/constants'; import { sdk } from '$lib/stores/sdk'; - import { Accordion, Badge, Divider, Layout, Selector } from '@appwrite.io/pink-svelte'; + import { Accordion, Alert, Badge, Divider, Layout, Selector } from '@appwrite.io/pink-svelte'; import type { Scopes } from '@appwrite.io/console'; let { scopes = $bindable([]) }: { scopes: Scopes[] } = $props(); let allScopesList: ScopeDefinition[] = $state([]); let mounted = $state(false); + let loadError: string | null = $state(null); enum Category { Auth = 'Auth', @@ -92,19 +93,24 @@ }); onMount(async () => { - const result = await sdk.forConsole.console.listProjectScopes(); - allScopesList = result.scopes.map((s) => ({ - scope: s.$id, - description: s.description, - category: s.category, - deprecated: s.deprecated, - icon: '' - })); - - for (const s of filteredScopes) { - activeScopes[s.scope] = scopes.includes(s.scope as Scopes); + try { + const result = await sdk.forConsole.console.listProjectScopes(); + allScopesList = result.scopes.map((s) => ({ + scope: s.$id, + description: s.description, + category: s.category, + deprecated: s.deprecated, + icon: '' + })); + + for (const s of filteredScopes) { + activeScopes[s.scope] = scopes.includes(s.scope as Scopes); + } + } catch (e) { + loadError = e?.message ?? 'Failed to load available scopes.'; + } finally { + mounted = true; } - mounted = true; }); function selectAll() { @@ -152,6 +158,9 @@ + {#if loadError} + {loadError} + {/if} From 2e7e3e75451df3140056d16c5f1a4b163407d05a Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Thu, 30 Apr 2026 19:35:16 +0530 Subject: [PATCH 18/19] fix: use example presence to infer required fields when provider is enabled Parameters with a non-empty example value are treated as required when the provider is enabled. Params without an example (e.g. GitLab endpoint for self-hosted) remain optional regardless of enabled state. --- .../auth/(providers)/mainOAuth.svelte | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/routes/(console)/project-[region]-[project]/auth/(providers)/mainOAuth.svelte b/src/routes/(console)/project-[region]-[project]/auth/(providers)/mainOAuth.svelte index 4c826b647b..acc3a90c55 100644 --- a/src/routes/(console)/project-[region]-[project]/auth/(providers)/mainOAuth.svelte +++ b/src/routes/(console)/project-[region]-[project]/auth/(providers)/mainOAuth.svelte @@ -230,6 +230,7 @@ autofocus={true} placeholder={appIdParam.example || ''} helper={helperText(appIdParam.hint)} + required={enabled && !!appIdParam.example} bind:value={appId} /> {/if} @@ -239,6 +240,7 @@ label={primaryName(param.name)} placeholder={param.example || ''} helper={helperText(param.hint)} + required={enabled && !!param.example} bind:value={fieldValues[param.$id]} /> {/each} From 20d09f798499b539fa7072ac2812d0a78c5e3944 Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Thu, 30 Apr 2026 19:37:00 +0530 Subject: [PATCH 19/19] fix: prevent scope data-loss when listProjectScopes fails mounted is now only set to true on successful load. On error, it stays false so the $effect never fires, activeScopes stays empty, and the parent-bound scopes prop is never overwritten with []. --- .../overview/api-keys/scopes.svelte | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/routes/(console)/project-[region]-[project]/overview/api-keys/scopes.svelte b/src/routes/(console)/project-[region]-[project]/overview/api-keys/scopes.svelte index 18d1a1acc5..35b0ddf23b 100644 --- a/src/routes/(console)/project-[region]-[project]/overview/api-keys/scopes.svelte +++ b/src/routes/(console)/project-[region]-[project]/overview/api-keys/scopes.svelte @@ -106,10 +106,12 @@ for (const s of filteredScopes) { activeScopes[s.scope] = scopes.includes(s.scope as Scopes); } + mounted = true; } catch (e) { loadError = e?.message ?? 'Failed to load available scopes.'; - } finally { - mounted = true; + // mounted intentionally stays false — the $effect guards on it, + // so leaving it false prevents activeScopes = {} from overwriting + // the parent-bound scopes prop with an empty array on fetch failure. } });