@@ -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();