diff --git a/shared/studio/tabs/auth/authAdmin.module.scss b/shared/studio/tabs/auth/authAdmin.module.scss index c4e5ca6b..e00e2aed 100644 --- a/shared/studio/tabs/auth/authAdmin.module.scss +++ b/shared/studio/tabs/auth/authAdmin.module.scss @@ -258,6 +258,32 @@ } } +.checkbox { + display: flex; + padding: 7px; + margin: 0 -7px; + cursor: pointer; + + input { + appearance: none; + width: 20px; + height: 20px; + margin: 0; + outline: 0; + border: 2px solid #9a9a9a; + border-radius: 4px; + background: transparent no-repeat center center; + box-sizing: border-box; + cursor: pointer; + + &:checked { + background-color: #1f8aed; + border: none; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14'%3E%3Cpath fill='%23fff' d='M0,8 5,13 14,4 12,2 5,9 2,6z'/%3E%3C/svg%3E"); + } + } +} + .providersList { display: flex; flex-direction: column; @@ -368,6 +394,13 @@ @include hideScrollbar; } + .providerConfigValue { + span { + opacity: 0.7; + font-style: italic; + } + } + @include darkTheme { background-color: #2f2f2f; diff --git a/shared/studio/tabs/auth/index.tsx b/shared/studio/tabs/auth/index.tsx index f23a2faa..bf19fdea 100644 --- a/shared/studio/tabs/auth/index.tsx +++ b/shared/studio/tabs/auth/index.tsx @@ -19,6 +19,7 @@ import { DraftUIConfig, ProviderKind, OAuthProviderData, + LocalEmailPasswordProviderData, } from "./state"; import {useTabState} from "../../state"; import {encodeB64} from "edgedb/dist/primitives/buffer"; @@ -268,7 +269,26 @@ const UIConfigForm = observer(function UIConfig({ />
- The url to redirect to after successful login. + The url to redirect to after successful sign in. +
+ + + +
+
redirect_to_on_signup
+
+
+ + draft.setConfigValue("redirect_to_on_signup", val) + } + /> +
+
+ The url to redirect to after a new user signs up. If not set, + 'redirect_to' will be used instead.
@@ -573,7 +593,44 @@ const DraftProviderConfigForm = observer(function DraftProviderConfigForm({ +
+
additional_scope
+
+
+ draftState.setAdditionalScope(val)} + /> +
+
+ Space-separated list of scopes to be included in the + authorize request to the OAuth provider. +
+
+
+ ) : providerKind === "Local" ? ( +
+
require_verification
+
+
+ +
+
+ Whether the email needs to be verified before the user is + allowed to sign in. +
+
+
) : null} @@ -612,7 +669,7 @@ function ProviderCard({provider}: {provider: AuthProviderData}) {
setExpanded(!expanded)} > @@ -636,15 +693,39 @@ function ProviderCard({provider}: {provider: AuthProviderData}) { }} />
- {expanded && kind === "OAuth" ? ( + {expanded ? (
-
client_id
-
- {(provider as OAuthProviderData).client_id} -
+ {kind === "OAuth" ? ( + <> +
client_id
+
+ {(provider as OAuthProviderData).client_id} +
+ +
secret
+
+ {secretPlaceholder} +
-
secret
-
{secretPlaceholder}
+
additional_scope
+
+ {(provider as OAuthProviderData).additional_scope || ( + none + )} +
+ + ) : kind === "Local" ? ( + <> +
+ require_verification +
+
+ {( + provider as LocalEmailPasswordProviderData + ).require_verification.toString()} +
+ + ) : null}
) : null} diff --git a/shared/studio/tabs/auth/state/index.tsx b/shared/studio/tabs/auth/state/index.tsx index 8ef8479e..eb30e375 100644 --- a/shared/studio/tabs/auth/state/index.tsx +++ b/shared/studio/tabs/auth/state/index.tsx @@ -25,13 +25,20 @@ export type OAuthProviderData = { | "ext::auth::GitHubOAuthProvider" | "ext::auth::GoogleOAuthProvider"; client_id: string; + additional_scope: string; +}; +export type LocalEmailPasswordProviderData = { + name: string; + _typename: "ext::auth::EmailPasswordProviderConfig"; + require_verification: boolean; }; export type AuthProviderData = | OAuthProviderData - | {name: string; _typename: "ext::auth::EmailPasswordProviderConfig"}; + | LocalEmailPasswordProviderData; export interface AuthUIConfigData { redirect_to: string; + redirect_to_on_signup: string; app_name: string | null; logo_url: string | null; dark_logo_url: string | null; @@ -187,9 +194,12 @@ export class AuthAdminState extends Model({ _typename := .__type__.name, name, [is OAuthProviderConfig].client_id, + [is OAuthProviderConfig].additional_scope, + [is EmailPasswordProviderConfig].require_verification, }, ui: { redirect_to, + redirect_to_on_signup, app_name, logo_url, dark_logo_url, @@ -221,6 +231,7 @@ export class AuthAdminState extends Model({ @model("AdminDraftUIConfig") export class DraftUIConfig extends Model({ _redirect_to: prop(null), + _redirect_to_on_signup: prop(null), _app_name: prop(null), _logo_url: prop(null), _dark_logo_url: prop(null), @@ -257,6 +268,7 @@ export class DraftUIConfig extends Model({ get formChanged() { return ( this._redirect_to != null || + this._redirect_to_on_signup != null || this._app_name != null || this._logo_url != null || this._dark_logo_url != null || @@ -267,6 +279,7 @@ export class DraftUIConfig extends Model({ @modelAction clearForm() { this._redirect_to = null; + this._redirect_to_on_signup = null; this._app_name = null; this._logo_url = null; this._dark_logo_url = null; @@ -298,7 +311,13 @@ export class DraftUIConfig extends Model({ this.getConfigValue("redirect_to") )}, ${( - ["app_name", "logo_url", "dark_logo_url", "brand_color"] as const + [ + "redirect_to_on_signup", + "app_name", + "logo_url", + "dark_logo_url", + "brand_color", + ] as const ) .map((name) => { const val = this.getConfigValue(name); @@ -325,6 +344,9 @@ export class DraftProviderConfig extends Model({ oauthClientId: prop("").withSetter(), oauthSecret: prop("").withSetter(), + additionalScope: prop("").withSetter(), + + requireEmailVerification: prop(true).withSetter(), }) { @computed get oauthClientIdError() { @@ -372,8 +394,19 @@ export class DraftProviderConfig extends Model({ provider.kind === "OAuth" ? ` client_id := ${JSON.stringify(this.oauthClientId)}, - secret := ${JSON.stringify(this.oauthSecret)} + secret := ${JSON.stringify(this.oauthSecret)}, + ${ + this.additionalScope.trim() + ? `additional_scope := ${JSON.stringify( + this.additionalScope.trim() + )}` + : "" + } ` + : provider.kind === "Local" + ? `require_verification := ${ + this.requireEmailVerification ? "true" : "false" + },` : "" } }`