Merge pull request #420 from appwrite/feat-4299-oidc-oauth2-provider

Add a generic OIDC OAuth2 provider
This commit is contained in:
Eldad A. Fux
2023-08-01 22:48:05 +03:00
committed by GitHub
15 changed files with 159 additions and 29 deletions
+13 -6
View File
@@ -1,16 +1,18 @@
import { writable } from 'svelte/store';
import type { Models } from '@appwrite.io/console';
import type { SvelteComponent } from 'svelte';
import { writable } from 'svelte/store';
import Apple from '../../routes/console/project-[project]/auth/appleOAuth.svelte';
import Microsoft from '../../routes/console/project-[project]/auth/microsoftOAuth.svelte';
import Okta from '../../routes/console/project-[project]/auth/oktaOAuth.svelte';
import Auth0 from '../../routes/console/project-[project]/auth/auth0OAuth.svelte';
import Authentik from '../../routes/console/project-[project]/auth/authentikOAuth.svelte';
import GitLab from '../../routes/console/project-[project]/auth/gitlabOAuth.svelte';
import Google from '../../routes/console/project-[project]/auth/googleOAuth.svelte';
import Main from '../../routes/console/project-[project]/auth/mainOAuth.svelte';
import Microsoft from '../../routes/console/project-[project]/auth/microsoftOAuth.svelte';
import Oidc from '../../routes/console/project-[project]/auth/oidcOAuth.svelte';
import Okta from '../../routes/console/project-[project]/auth/oktaOAuth.svelte';
export type Provider = Models.Provider & {
key: string;
icon: string;
docs?: string;
component?: typeof SvelteComponent;
@@ -23,11 +25,12 @@ export type Providers = {
const setProviders = (project: Models.Project): Provider[] => {
return (
project?.providers.map((n) => {
const p = n as Models.Provider & { key: string };
let docs: Provider['docs'];
let icon: Provider['icon'] = n.name.toLowerCase();
let icon: Provider['icon'] = p.key.toLowerCase();
let component: Provider['component'] = Main;
switch (n.name.toLowerCase()) {
switch (p.key.toLowerCase()) {
case 'amazon':
docs = 'https://developer.amazon.com/apps-and-games/services-and-apis';
break;
@@ -94,6 +97,10 @@ const setProviders = (project: Models.Project): Provider[] => {
case 'notion':
docs = 'https://developers.notion.com/docs';
break;
case 'oidc':
docs = 'https://openid.net/connect/faq/';
component = Oidc;
break;
case 'okta':
docs = 'https://developer.okta.com';
component = Okta;
@@ -151,7 +158,7 @@ const setProviders = (project: Models.Project): Provider[] => {
}
return {
...n,
...p,
icon,
docs,
component
@@ -63,9 +63,7 @@
<div>
<p>URI</p>
<CopyInput
value={`${
sdk.forConsole.client.config.endpoint
}/account/sessions/oauth2/callback/${provider.name.toLocaleLowerCase()}/${projectId}`} />
value={`${sdk.forConsole.client.config.endpoint}/account/sessions/oauth2/callback/${provider.key}/${projectId}`} />
</div>
</FormList>
<svelte:fragment slot="footer">
@@ -72,9 +72,7 @@
<div>
<p>URI</p>
<CopyInput
value={`${
sdk.forConsole.client.config.endpoint
}/account/sessions/oauth2/callback/${provider.name.toLocaleLowerCase()}/${projectId}`} />
value={`${sdk.forConsole.client.config.endpoint}/account/sessions/oauth2/callback/${provider.key}/${projectId}`} />
</div>
</FormList>
<svelte:fragment slot="footer">
@@ -72,9 +72,7 @@
<div>
<p>URI</p>
<CopyInput
value={`${
sdk.forConsole.client.config.endpoint
}/account/sessions/oauth2/callback/${provider.name.toLocaleLowerCase()}/${projectId}`} />
value={`${sdk.forConsole.client.config.endpoint}/account/sessions/oauth2/callback/${provider.key}/${projectId}`} />
</div>
</FormList>
<svelte:fragment slot="footer">
@@ -70,9 +70,7 @@
<div>
<p>URI</p>
<CopyInput
value={`${
sdk.forConsole.client.config.endpoint
}/account/sessions/oauth2/callback/${provider.name.toLocaleLowerCase()}/${projectId}`} />
value={`${sdk.forConsole.client.config.endpoint}/account/sessions/oauth2/callback/${provider.key}/${projectId}`} />
</div>
</FormList>
<svelte:fragment slot="footer">
@@ -63,9 +63,7 @@
<div>
<p>URI</p>
<CopyInput
value={`${
sdk.forConsole.client.config.endpoint
}/account/sessions/oauth2/callback/${provider.name.toLocaleLowerCase()}/${projectId}`} />
value={`${sdk.forConsole.client.config.endpoint}/account/sessions/oauth2/callback/${provider.key}/${projectId}`} />
</div>
</FormList>
<svelte:fragment slot="footer">
@@ -72,9 +72,7 @@
<div>
<p>URI</p>
<CopyInput
value={`${
sdk.forConsole.client.config.endpoint
}/account/sessions/oauth2/callback/${provider.name.toLocaleLowerCase()}/${projectId}`} />
value={`${sdk.forConsole.client.config.endpoint}/account/sessions/oauth2/callback/${provider.key}/${projectId}`} />
</div>
</FormList>
<svelte:fragment slot="footer">
@@ -0,0 +1,125 @@
<script lang="ts">
import { page } from '$app/stores';
import { Alert, CopyInput, Modal } from '$lib/components';
import { Button, FormList, InputPassword, InputSwitch, InputText } from '$lib/elements/forms';
import type { Provider } from '$lib/stores/oauth-providers';
import { sdk } from '$lib/stores/sdk';
import { onMount } from 'svelte';
import { updateOAuth } from './updateOAuth';
const projectId = $page.params.project;
export let provider: Provider;
let appId: string = null;
let enabled: boolean = null;
let clientSecret: string = null;
let wellKnownEndpoint: string = null;
let authorizationEndpoint: string = null;
let tokenEndpoint: string = null;
let userinfoEndpoint: string = null;
let error: string;
// secret is valid if clientSecret is set and either wellKnownEndpoint or all of the other endpoints are set
$: isValidSecret =
clientSecret &&
(wellKnownEndpoint || (authorizationEndpoint && tokenEndpoint && userinfoEndpoint));
onMount(() => {
appId ??= provider.appId;
enabled ??= provider.enabled;
if (provider.secret) {
({
clientSecret,
wellKnownEndpoint,
authorizationEndpoint,
tokenEndpoint,
userinfoEndpoint
} = JSON.parse(provider.secret));
}
});
const update = async () => {
const result = await updateOAuth({ projectId, provider, secret, appId, enabled });
if (result.status === 'error') {
error = result.message;
} else {
provider = null;
}
};
$: secret = isValidSecret
? JSON.stringify({
clientSecret,
wellKnownEndpoint,
authorizationEndpoint,
tokenEndpoint,
userinfoEndpoint
})
: provider.secret;
</script>
<Modal {error} onSubmit={update} size="big" show on:close>
<svelte:fragment slot="header">{provider.name} OAuth2 Settings</svelte:fragment>
<FormList>
<p>
To use {provider.name} authentication in your application, first fill in this form. For more
info you can
<a class="link" href={provider.docs} target="_blank" rel="noopener noreferrer"
>visit the docs.</a>
</p>
<InputSwitch id="state" bind:value={enabled} label={enabled ? 'Enabled' : 'Disabled'} />
<InputText
id="appID"
label="Client ID"
autofocus={true}
placeholder="Enter ID"
bind:value={appId} />
<InputPassword
id="secret"
label="Client Secret"
placeholder="Enter Client Secret"
minlength={0}
showPasswordButton
bind:value={clientSecret} />
<InputText
id="well-known-endpoint"
label="Well-Known Endpoint"
placeholder="https://example.com/.well-known/openid-configuration"
bind:value={wellKnownEndpoint} />
<InputText
id="authorization-endpoint"
label="Authorization Endpoint"
placeholder="https://example.com/authorize"
bind:value={authorizationEndpoint} />
<InputText
id="token-endpoint"
label="Token Endpoint"
placeholder="https://example.com/token"
bind:value={tokenEndpoint} />
<InputText
id="userinfo-endpoint"
label="User Info Endpoint"
placeholder="https://example.com/userinfo"
bind:value={userinfoEndpoint} />
<Alert type="info">
To complete set up, add this OAuth2 redirect URI to your {provider.name} app configuration.
</Alert>
<div>
<p>URI</p>
<CopyInput
value={`${sdk.forConsole.client.config.endpoint}/account/sessions/oauth2/callback/${provider.key}/${projectId}`} />
</div>
</FormList>
<svelte:fragment slot="footer">
<Button secondary on:click={() => (provider = null)}>Cancel</Button>
<Button
disabled={(secret === provider.secret &&
enabled === provider.enabled &&
appId === provider.appId) ||
!(appId && isValidSecret)}
submit>Update</Button>
</svelte:fragment>
</Modal>
@@ -82,9 +82,7 @@
<div>
<p>URI</p>
<CopyInput
value={`${
sdk.forConsole.client.config.endpoint
}/account/sessions/oauth2/callback/${provider.name.toLocaleLowerCase()}/${projectId}`} />
value={`${sdk.forConsole.client.config.endpoint}/account/sessions/oauth2/callback/${provider.key}/${projectId}`} />
</div>
</FormList>
<svelte:fragment slot="footer">
@@ -75,7 +75,7 @@
on:click={() => {
selectedProvider = provider;
trackEvent(`click_select_provider`, {
provider: provider.name.toLowerCase()
provider: provider.key.toLowerCase()
});
}}>
<div class="avatar">
@@ -28,7 +28,7 @@ export async function updateOAuth({
try {
await sdk.forConsole.projects.updateOAuth2(
projectId,
provider.name.toLowerCase(),
provider.key,
appId || undefined,
secret || undefined,
enabled
+3
View File
@@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.25 3H18.75C19.9926 3 21 4.00736 21 5.25V18.75C21 19.9926 19.9926 21 18.75 21H5.25C4.00736 21 3 19.9926 3 18.75V5.25C3 4.00736 4.00736 3 5.25 3ZM13.5467 9.2378C14.8834 9.40936 16.2798 9.84718 17.6601 10.6272L19.0312 9.71202V12.3053H15.0701L16.2892 11.5423C15.3212 11.1022 14.4026 10.8227 13.5467 10.6742V17.0335L11.4138 18.4063C1.04033 16.518 4.80411 9.69874 11.4138 9.18233V6.96653L13.5467 5.59371V9.2378ZM11.4137 10.5686V17.0335C4.98567 16.1994 6.46334 10.9884 11.4137 10.5686Z" fill="#F68423"/>
</svg>

After

Width:  |  Height:  |  Size: 652 B

+3
View File
@@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.25 3H18.75C19.9926 3 21 4.00736 21 5.25V18.75C21 19.9926 19.9926 21 18.75 21H5.25C4.00736 21 3 19.9926 3 18.75V5.25C3 4.00736 4.00736 3 5.25 3ZM13.5467 9.2378C14.8834 9.40936 16.2798 9.84718 17.6601 10.6272L19.0312 9.71202V12.3053H15.0701L16.2892 11.5423C15.3212 11.1022 14.4026 10.8227 13.5467 10.6742V17.0335L11.4138 18.4063C1.04033 16.518 4.80411 9.69874 11.4138 9.18233V6.96653L13.5467 5.59371V9.2378ZM11.4137 10.5686V17.0335C4.98567 16.1994 6.46334 10.9884 11.4137 10.5686Z" fill="#C4C6D7"/>
</svg>

After

Width:  |  Height:  |  Size: 652 B

+3
View File
@@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.25 3H18.75C19.9926 3 21 4.00736 21 5.25V18.75C21 19.9926 19.9926 21 18.75 21H5.25C4.00736 21 3 19.9926 3 18.75V5.25C3 4.00736 4.00736 3 5.25 3ZM13.5467 9.2378C14.8834 9.40936 16.2798 9.84718 17.6601 10.6272L19.0312 9.71202V12.3053H15.0701L16.2892 11.5423C15.3212 11.1022 14.4026 10.8227 13.5467 10.6742V17.0335L11.4138 18.4063C1.04033 16.518 4.80411 9.69874 11.4138 9.18233V6.96653L13.5467 5.59371V9.2378ZM11.4137 10.5686V17.0335C4.98567 16.1994 6.46334 10.9884 11.4137 10.5686Z" fill="#F68423"/>
</svg>

After

Width:  |  Height:  |  Size: 652 B

+3
View File
@@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.25 3H18.75C19.9926 3 21 4.00736 21 5.25V18.75C21 19.9926 19.9926 21 18.75 21H5.25C4.00736 21 3 19.9926 3 18.75V5.25C3 4.00736 4.00736 3 5.25 3ZM13.5467 9.2378C14.8834 9.40936 16.2798 9.84718 17.6601 10.6272L19.0312 9.71202V12.3053H15.0701L16.2892 11.5423C15.3212 11.1022 14.4026 10.8227 13.5467 10.6742V17.0335L11.4138 18.4063C1.04033 16.518 4.80411 9.69874 11.4138 9.18233V6.96653L13.5467 5.59371V9.2378ZM11.4137 10.5686V17.0335C4.98567 16.1994 6.46334 10.9884 11.4137 10.5686Z" fill="#373B4D"/>
</svg>

After

Width:  |  Height:  |  Size: 652 B