Skip to content

Commit f348973

Browse files
authored
Merge pull request #3115 from appwrite/feat-oauth2-improvements
Feat oauth2 improvements
2 parents c63636e + 395a268 commit f348973

6 files changed

Lines changed: 122 additions & 13 deletions

File tree

bun.lock

Lines changed: 2 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
},
2121
"dependencies": {
2222
"@ai-sdk/svelte": "^1.1.24",
23-
"@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@4b3ae78",
23+
"@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@6be9e62",
2424
"@appwrite.io/pink-icons": "0.25.0",
2525
"@appwrite.io/pink-icons-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@bfe7ce3",
2626
"@appwrite.io/pink-legacy": "^1.0.3",

src/lib/stores/oauth-providers.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,12 @@ export const oAuthProviders: Record<string, Provider> = {
2323
docs: 'https://developer.apple.com/sign-in-with-apple/',
2424
component: Main
2525
},
26+
appwrite: {
27+
name: 'Appwrite',
28+
icon: 'appwrite',
29+
docs: 'https://appwrite.io/docs/products/auth/oauth2',
30+
component: Main
31+
},
2632
auth0: {
2733
name: 'Auth0',
2834
icon: 'auth0',

src/routes/(console)/project-[region]-[project]/auth/(providers)/mainOAuth.svelte

Lines changed: 53 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,18 @@
33
import { CopyInput, Modal } from '$lib/components';
44
import {
55
Button,
6+
InputNumber,
67
InputPassword,
78
InputSwitch,
89
InputText,
910
InputTextarea
1011
} from '$lib/elements/forms';
1112
import { updateOAuth } from '../updateOAuth';
12-
import { OAuthProvider, type Models as ConsoleModels } from '@appwrite.io/console';
13+
import {
14+
OAuthProvider,
15+
ProjectOAuth2OidcPrompt,
16+
type Models as ConsoleModels
17+
} from '@appwrite.io/console';
1318
import type { AuthProvider } from '../updateOAuth';
1419
import { oAuthProviders } from '$lib/stores/oauth-providers';
1520
import {
@@ -25,6 +30,7 @@
2530
} from '@appwrite.io/pink-svelte';
2631
import { IconDocument, IconPencil, IconUpload, IconX } from '@appwrite.io/pink-icons-svelte';
2732
import { getApiEndpoint } from '$lib/stores/sdk';
33+
import OidcPromptPicker from './oidcPromptPicker.svelte';
2834
2935
const projectId = page.params.project;
3036
const region = page.params.region;
@@ -44,14 +50,21 @@
4450
let initialEnabled = false;
4551
let initialAppId: string | null = null;
4652
let initialDetailValues: Record<string, string> = {};
53+
let oidcPrompt: ProjectOAuth2OidcPrompt[] = [];
54+
let initialOidcPrompt: ProjectOAuth2OidcPrompt[] = [];
55+
let oidcMaxAge: number | null = null;
56+
let initialOidcMaxAge: number | null = null;
4757
58+
$: isOidc = provider?.key === OAuthProvider.Oidc;
4859
$: appIdParam = parameters.length >= 1 ? parameters[0] : null;
4960
$: additionalParams = parameters.slice(1);
5061
$: detailParams = additionalParams.filter((param) => !isSecretParam(param.$id));
5162
$: secretParams = additionalParams.filter((param) => isSecretParam(param.$id));
5263
$: basicDetailParams =
5364
provider?.key === OAuthProvider.Oidc
54-
? detailParams.filter((param) => !isOidcAdvancedParam(param.$id))
65+
? detailParams.filter(
66+
(param) => !isOidcAdvancedParam(param.$id) && !isOidcCustomParam(param.$id)
67+
)
5568
: detailParams;
5669
$: advancedDetailParams =
5770
provider?.key === OAuthProvider.Oidc
@@ -66,11 +79,16 @@
6679
normalizeFieldValue(fieldValues[param.$id]) !== (initialDetailValues[param.$id] ?? '')
6780
);
6881
});
82+
$: hasOidcChanges =
83+
isOidc &&
84+
([...oidcPrompt].sort().join(',') !== [...initialOidcPrompt].sort().join(',') ||
85+
(oidcMaxAge ?? 0) !== (initialOidcMaxAge ?? 0));
6986
$: nothingChanged =
7087
enabled === initialEnabled &&
7188
normalizeFieldValue(appId) === normalizeFieldValue(initialAppId) &&
7289
!hasSecretInput &&
73-
!hasDetailChanges;
90+
!hasDetailChanges &&
91+
!hasOidcChanges;
7492
$: oAuthProvider = oAuthProviders[provider.key];
7593
$: secretCardTitle =
7694
secretParams.length === 1 ? primaryName(secretParams[0]?.name ?? '') : 'Credentials';
@@ -90,6 +108,19 @@
90108
);
91109
p8PasteMode = {};
92110
showSecretInput = !appId || (provider.key === OAuthProvider.Apple && !providerKeyId);
111+
if (provider.key === OAuthProvider.Oidc) {
112+
const raw = provider as Record<string, unknown>;
113+
initialOidcPrompt = Array.isArray(raw['prompt'])
114+
? ([...(raw['prompt'] as ProjectOAuth2OidcPrompt[])] as ProjectOAuth2OidcPrompt[])
115+
: [];
116+
initialOidcMaxAge =
117+
typeof raw['maxAge'] === 'number' && raw['maxAge'] > 0 ? raw['maxAge'] : null;
118+
} else {
119+
initialOidcPrompt = [];
120+
initialOidcMaxAge = null;
121+
}
122+
oidcPrompt = [...initialOidcPrompt];
123+
oidcMaxAge = initialOidcMaxAge;
93124
error = undefined;
94125
}
95126
@@ -151,6 +182,11 @@
151182
return id !== 'wellKnownURL' && id.toLowerCase().includes('url');
152183
}
153184
185+
// prompt and maxAge get dedicated controls in the Advanced section
186+
function isOidcCustomParam(id: string): boolean {
187+
return id === 'prompt' || id === 'maxAge';
188+
}
189+
154190
async function handleP8FileUpload(id: string, event: Event) {
155191
const input = event.target as HTMLInputElement;
156192
const file = input.files?.[0];
@@ -198,6 +234,8 @@
198234
appId,
199235
secret: buildSecret(),
200236
details: buildDetails(),
237+
promptValues: isOidc ? oidcPrompt : undefined,
238+
maxAge: isOidc ? (oidcMaxAge ?? 0) : undefined,
201239
enabled
202240
});
203241
@@ -357,7 +395,7 @@
357395
{/if}
358396
{/if}
359397

360-
{#if advancedDetailParams.length > 0}
398+
{#if advancedDetailParams.length > 0 || isOidc}
361399
<Accordion title="Advanced" badge="Optional" hideDivider>
362400
<Layout.Stack gap="l">
363401
{#each advancedDetailParams as param}
@@ -368,6 +406,17 @@
368406
helper={helperText(param.hint)}
369407
bind:value={fieldValues[param.$id]} />
370408
{/each}
409+
{#if isOidc}
410+
<OidcPromptPicker bind:value={oidcPrompt} />
411+
<InputNumber
412+
id="maxAge"
413+
label="Max age"
414+
placeholder="3600"
415+
min={0}
416+
nullable
417+
helper="Maximum authentication age in seconds. If exceeded, the user must re-authenticate. Leave empty to disable."
418+
bind:value={oidcMaxAge} />
419+
{/if}
371420
</Layout.Stack>
372421
</Accordion>
373422
{/if}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<script lang="ts">
2+
import { ProjectOAuth2OidcPrompt } from '@appwrite.io/console';
3+
import { Layout, Tag, Typography } from '@appwrite.io/pink-svelte';
4+
5+
type Props = {
6+
value: ProjectOAuth2OidcPrompt[];
7+
onchange?: (value: ProjectOAuth2OidcPrompt[]) => void;
8+
};
9+
10+
let { value = $bindable([]), onchange }: Props = $props();
11+
12+
const options: { val: ProjectOAuth2OidcPrompt; label: string }[] = [
13+
{ val: ProjectOAuth2OidcPrompt.None, label: 'None' },
14+
{ val: ProjectOAuth2OidcPrompt.Login, label: 'Login' },
15+
{ val: ProjectOAuth2OidcPrompt.Consent, label: 'Consent' },
16+
{ val: ProjectOAuth2OidcPrompt.SelectAccount, label: 'Select account' }
17+
];
18+
19+
function toggle(opt: ProjectOAuth2OidcPrompt): void {
20+
let next: ProjectOAuth2OidcPrompt[];
21+
if (opt === ProjectOAuth2OidcPrompt.None) {
22+
next = value.includes(opt) ? [] : [opt];
23+
} else {
24+
next = value.includes(opt)
25+
? value.filter((v) => v !== opt)
26+
: [...value.filter((v) => v !== ProjectOAuth2OidcPrompt.None), opt];
27+
}
28+
value = next;
29+
onchange?.(next);
30+
}
31+
</script>
32+
33+
<Layout.Stack gap="xs">
34+
<Typography.Text variant="m-500">Prompt</Typography.Text>
35+
<Layout.Stack direction="row" gap="s" flexWrap="wrap">
36+
{#each options as option (option.val)}
37+
<Tag size="s" selected={value.includes(option.val)} onclick={() => toggle(option.val)}>
38+
{option.label}
39+
</Tag>
40+
{/each}
41+
</Layout.Stack>
42+
</Layout.Stack>

src/routes/(console)/project-[region]-[project]/auth/updateOAuth.ts

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { sdk } from '$lib/stores/sdk';
77
import {
88
OAuthProvider,
99
ProjectOAuth2GooglePrompt,
10+
ProjectOAuth2OidcPrompt,
1011
type Models as ConsoleModels
1112
} from '@appwrite.io/console';
1213

@@ -25,7 +26,8 @@ type Args = {
2526
secret: string | null;
2627
details: Record<string, string>;
2728
enabled: boolean;
28-
promptValues?: ProjectOAuth2GooglePrompt[];
29+
promptValues?: ProjectOAuth2GooglePrompt[] | ProjectOAuth2OidcPrompt[];
30+
maxAge?: number;
2931
};
3032

3133
type Return = {
@@ -51,7 +53,8 @@ async function updateProjectOAuth({
5153
secret,
5254
details,
5355
enabled,
54-
promptValues
56+
promptValues,
57+
maxAge
5558
}: Args) {
5659
const projectSdk = sdk.forProject(region, projectId).project;
5760
const parsedSecret = parseSecret(secret);
@@ -86,6 +89,12 @@ async function updateProjectOAuth({
8689
p8File: getSecret('p8File'),
8790
enabled
8891
});
92+
case OAuthProvider.Appwrite:
93+
return projectSdk.updateOAuth2Appwrite({
94+
clientId: getAppId(),
95+
clientSecret: getSecret(),
96+
enabled
97+
});
8998
case OAuthProvider.Auth0:
9099
return projectSdk.updateOAuth2Auth0({
91100
clientId: getAppId(),
@@ -190,7 +199,7 @@ async function updateProjectOAuth({
190199
return projectSdk.updateOAuth2Google({
191200
clientId: getAppId(),
192201
clientSecret: getSecret(),
193-
prompt: promptValues ?? [],
202+
prompt: (promptValues as ProjectOAuth2GooglePrompt[]) ?? [],
194203
enabled
195204
});
196205
case OAuthProvider.Keycloak:
@@ -234,6 +243,8 @@ async function updateProjectOAuth({
234243
authorizationURL: getDetail('authorizationURL'),
235244
tokenURL: getDetail('tokenUrl'),
236245
userInfoURL: getDetail('userInfoUrl'),
246+
prompt: (promptValues as ProjectOAuth2OidcPrompt[]) ?? [],
247+
maxAge: maxAge ?? 0,
237248
enabled
238249
});
239250
case OAuthProvider.Okta:
@@ -353,7 +364,8 @@ export async function updateOAuth({
353364
secret,
354365
details,
355366
enabled,
356-
promptValues
367+
promptValues,
368+
maxAge
357369
}: Args): Promise<Return> {
358370
try {
359371
if (!isValueOfStringEnum(OAuthProvider, provider.key)) {
@@ -368,7 +380,8 @@ export async function updateOAuth({
368380
secret,
369381
details,
370382
enabled,
371-
promptValues
383+
promptValues,
384+
maxAge
372385
});
373386
await invalidate(Dependencies.PROJECT);
374387

0 commit comments

Comments
 (0)