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 @@
-
-
-
-
+
+ {#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}
-