diff --git a/shared/studio/tabs/auth/index.tsx b/shared/studio/tabs/auth/index.tsx index 272e0b4c..0bb4d596 100644 --- a/shared/studio/tabs/auth/index.tsx +++ b/shared/studio/tabs/auth/index.tsx @@ -30,6 +30,7 @@ import { SMTPSecurity, smtpSecurity, _providersInfo, + LocalWebAuthnProviderData, } from "./state"; import {encodeB64} from "edgedb/dist/primitives/buffer"; @@ -1009,6 +1010,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
@@ -1113,12 +1137,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 43d033c3..70aa7929 100644 --- a/shared/studio/tabs/auth/state/index.tsx +++ b/shared/studio/tabs/auth/state/index.tsx @@ -41,9 +41,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; @@ -121,6 +128,11 @@ export const _providersInfo: { displayName: "Email + Password", icon: <>, }, + "ext::auth::WebAuthnProviderConfig": { + kind: "Local", + displayName: "WebAuthn", + icon: <>, + }, }; export type ProviderTypename = keyof typeof _providersInfo; @@ -297,7 +309,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, @@ -631,6 +647,8 @@ export class DraftProviderConfig extends Model({ oauthSecret: prop("").withSetter(), additionalScope: prop("").withSetter(), + webauthnRelyingOrigin: prop("").withSetter(), + requireEmailVerification: prop(true).withSetter(), }) { @computed @@ -643,13 +661,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; } } @@ -672,28 +700,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();