From 13615fa6dfa8eff5ee80cec6cc4df362ad8e2df6 Mon Sep 17 00:00:00 2001 From: James Clarke Date: Thu, 8 Feb 2024 16:12:38 +0000 Subject: [PATCH] Add support for webauthn provider --- shared/studio/tabs/auth/index.tsx | 41 +++++++++++- shared/studio/tabs/auth/state/index.tsx | 84 ++++++++++++++++++------- 2 files changed, 102 insertions(+), 23 deletions(-) diff --git a/shared/studio/tabs/auth/index.tsx b/shared/studio/tabs/auth/index.tsx index 6a4d7821..37c1ba85 100644 --- a/shared/studio/tabs/auth/index.tsx +++ b/shared/studio/tabs/auth/index.tsx @@ -32,6 +32,7 @@ import { DraftAppConfig, AbstractDraftConfig, _providersInfo, + LocalWebAuthnProviderData, } from "./state"; import {encodeB64} from "edgedb/dist/primitives/buffer"; @@ -1002,6 +1003,29 @@ const DraftProviderConfigForm = observer(function DraftProviderConfigForm({ ) : providerKind === "Local" ? (
+ {draftState.selectedProviderType === + "ext::auth::WebAuthnProviderConfig" ? ( +
+
relying_party_origin
+
+
+ + draftState.setWebauthnRelyingOrigin(val) + } + error={draftState.webauthnRelyingOriginError} + /> +
+
+ The full origin of the sign-in page including protocol and + port of the application. If using the built-in UI, this + should be the origin of the EdgeDB server. +
+
+
+ ) : null}
require_verification
@@ -1106,12 +1130,27 @@ function ProviderCard({provider}: {provider: AuthProviderData}) { ) : kind === "Local" ? ( <> + {provider.name === "builtin::local_webauthn" ? ( + <> +
+ relying_party_origin +
+
+ { + (provider as LocalWebAuthnProviderData) + .relying_party_origin + } +
+ + ) : null}
require_verification
{( - provider as LocalEmailPasswordProviderData + provider as + | LocalEmailPasswordProviderData + | LocalWebAuthnProviderData ).require_verification.toString()}
diff --git a/shared/studio/tabs/auth/state/index.tsx b/shared/studio/tabs/auth/state/index.tsx index 77429f7d..44cb1149 100644 --- a/shared/studio/tabs/auth/state/index.tsx +++ b/shared/studio/tabs/auth/state/index.tsx @@ -49,9 +49,16 @@ export type LocalEmailPasswordProviderData = { _typename: "ext::auth::EmailPasswordProviderConfig"; require_verification: boolean; }; +export type LocalWebAuthnProviderData = { + name: string; + _typename: "ext::auth::WebAuthnProviderConfig"; + relying_party_origin: string; + require_verification: boolean; +}; export type AuthProviderData = | OAuthProviderData - | LocalEmailPasswordProviderData; + | LocalEmailPasswordProviderData + | LocalWebAuthnProviderData; export interface AuthUIConfigData { redirect_to: string; @@ -125,6 +132,11 @@ export const _providersInfo: { displayName: "Email + Password", icon: <>, }, + "ext::auth::WebAuthnProviderConfig": { + kind: "Local", + displayName: "WebAuthn", + icon: <>, + }, }; export type ProviderTypename = keyof typeof _providersInfo; @@ -266,7 +278,11 @@ export class AuthAdminState extends Model({ name, [is OAuthProviderConfig].client_id, [is OAuthProviderConfig].additional_scope, - [is EmailPasswordProviderConfig].require_verification, + require_verification := ( + [is EmailPasswordProviderConfig].require_verification ?? + [is WebAuthnProviderConfig].require_verification + ), + [is WebAuthnProviderConfig].relying_party_origin }, ui: { redirect_to, @@ -846,6 +862,8 @@ export class DraftProviderConfig extends Model({ oauthSecret: prop("").withSetter(), additionalScope: prop("").withSetter(), + webauthnRelyingOrigin: prop("").withSetter(), + requireEmailVerification: prop(true).withSetter(), }) { @computed @@ -858,13 +876,23 @@ export class DraftProviderConfig extends Model({ return this.oauthSecret.trim() === "" ? "Secret is required" : null; } + @computed + get webauthnRelyingOriginError() { + return this.webauthnRelyingOrigin.trim() === "" + ? "Relying origin is required" + : null; + } + @computed get formValid(): boolean { switch (_providersInfo[this.selectedProviderType].kind) { case "OAuth": return !this.oauthClientIdError && !this.oauthSecretError; case "Local": - return true; + return this.selectedProviderType === + "ext::auth::WebAuthnProviderConfig" + ? !this.webauthnRelyingOriginError + : true; } } @@ -887,28 +915,40 @@ export class DraftProviderConfig extends Model({ try { const provider = _providersInfo[this.selectedProviderType]; + const queryFields: string[] = []; + if (provider.kind === "OAuth") { + queryFields.push( + `client_id := ${JSON.stringify(this.oauthClientId)}`, + `secret := ${JSON.stringify(this.oauthSecret)}` + ); + if (this.additionalScope.trim()) { + queryFields.push( + `additional_scope := ${JSON.stringify( + this.additionalScope.trim() + )}` + ); + } + } else if (provider.kind === "Local") { + if ( + this.selectedProviderType === "ext::auth::WebAuthnProviderConfig" + ) { + queryFields.push( + `relying_party_origin := ${JSON.stringify( + this.webauthnRelyingOrigin + )}` + ); + } + queryFields.push( + `require_verification := ${ + this.requireEmailVerification ? "true" : "false" + }` + ); + } + await conn.execute( `configure current database insert ${this.selectedProviderType} { - ${ - provider.kind === "OAuth" - ? ` - client_id := ${JSON.stringify(this.oauthClientId)}, - secret := ${JSON.stringify(this.oauthSecret)}, - ${ - this.additionalScope.trim() - ? `additional_scope := ${JSON.stringify( - this.additionalScope.trim() - )}` - : "" - } - ` - : provider.kind === "Local" - ? `require_verification := ${ - this.requireEmailVerification ? "true" : "false" - },` - : "" - } + ${queryFields.join(",\n")} }` ); await state.refreshConfig();