diff --git a/admin/app/authentication/oidc/form.tsx b/admin/app/authentication/oidc/form.tsx index 0d607c3c7dd..9401b9626c5 100644 --- a/admin/app/authentication/oidc/form.tsx +++ b/admin/app/authentication/oidc/form.tsx @@ -3,9 +3,9 @@ import isEmpty from "lodash/isEmpty"; import Link from "next/link"; import { useForm } from "react-hook-form"; // types -import { IFormattedInstanceConfiguration, TInstanceOpenIDConnectAuthenticationConfigurationKeys } from "@plane/types"; +import { IFormattedInstanceConfiguration, TInstanceConfigurationKeys, TInstanceOpenIDConnectAuthenticationConfigurationKeys } from "@plane/types"; // ui -import { Button, TOAST_TYPE, getButtonStyling, setToast } from "@plane/ui"; +import { Button, TOAST_TYPE, getButtonStyling, setToast, ToggleSwitch, setPromiseToast } from "@plane/ui"; // components import { CodeBlock, @@ -30,8 +30,9 @@ export const InstanceOpenIDConnectConfigForm: FC = (props) => { const { config } = props; // states const [isDiscardChangesModalOpen, setIsDiscardChangesModalOpen] = useState(false); + const [isSubmittingAuto, setIsSubmittingAuto] = useState(false); // store hooks - const { updateInstanceConfigurations } = useInstance(); + const { formattedConfig, updateInstanceConfigurations } = useInstance(); // form data const { handleSubmit, @@ -49,6 +50,37 @@ export const InstanceOpenIDConnectConfigForm: FC = (props) => { }, }); + const updateConfig = async (key: TInstanceConfigurationKeys, value: string) => { + setIsSubmittingAuto(true); + + const payload = { + [key]: value, + }; + + const updateConfigPromise = updateInstanceConfigurations(payload); + + setPromiseToast(updateConfigPromise, { + loading: "Saving configuration", + success: { + title: "Success", + message: () => "Configuration saved successfully", + }, + error: { + title: "Error", + message: () => "Failed to save configuration", + }, + }); + + await updateConfigPromise + .then(() => { + setIsSubmittingAuto(false); + }) + .catch((err) => { + console.error(err); + setIsSubmittingAuto(false); + }); + }; + const originURL = !isEmpty(API_BASE_URL) ? API_BASE_URL : typeof window !== "undefined" ? window.location.origin : ""; const OIDC_FORM_FIELDS: TControllerInputFormField[] = [ @@ -57,9 +89,7 @@ export const InstanceOpenIDConnectConfigForm: FC = (props) => { type: "text", label: "Authorization Endpoint", description: ( - <> - Example: https://idp.your-company.com/o/authorize/. This is the URL where users will be redirected to - + <>Example: https://idp.your-company.com/o/authorize/. This is the URL where users will be redirected to ), placeholder: "https://idp.your-company.com/o/authorize/", error: Boolean(errors.OIDC_URL_AUTHORIZATION), @@ -71,8 +101,8 @@ export const InstanceOpenIDConnectConfigForm: FC = (props) => { label: "Token Endpoint", description: ( <> - Example: https://idp.your-company.com/o/token/. This is the URL where we will exchange the code for an - access token. + Example: https://idp.your-company.com/o/token/. This is the URL where we will exchange the code for an access + token. ), placeholder: "https://idp.your-company.com/o/token/", @@ -84,9 +114,7 @@ export const InstanceOpenIDConnectConfigForm: FC = (props) => { type: "text", label: "UserInfo Endpoint", description: ( - <> - Example: https://idp.your-company.com/o/userinfo/. This is the URL where we will get user information. - + <>Example: https://idp.your-company.com/o/userinfo/. This is the URL where we will get user information. ), placeholder: "https://idp.your-company.com/o/userinfo/", error: Boolean(errors.OIDC_URL_USERINFO), @@ -97,9 +125,7 @@ export const InstanceOpenIDConnectConfigForm: FC = (props) => { type: "text", label: "EndSession Endpoint", description: ( - <> - Example: https://idp.your-company.com/o/revoke/. This is the URL where we will revoke the users session. - + <>Example: https://idp.your-company.com/o/revoke/. This is the URL where we will revoke the users session. ), placeholder: "https://idp.your-company.com/o/revoke/", error: Boolean(errors.OIDC_URL_ENDSESSION), @@ -109,11 +135,7 @@ export const InstanceOpenIDConnectConfigForm: FC = (props) => { key: "OIDC_CLIENT_ID", type: "text", label: "Client ID", - description: ( - <> - Get this from your OpenID Connect Provider. - - ), + description: <>Get this from your OpenID Connect Provider., placeholder: "c2ef2e7fc4e9d15aa7630f5637d59e8e4a27ff01dceebdb26b0d267b9adcf3c3", error: Boolean(errors.OIDC_CLIENT_ID), required: true, @@ -122,11 +144,7 @@ export const InstanceOpenIDConnectConfigForm: FC = (props) => { key: "OIDC_CLIENT_SECRET", type: "password", label: "Client Secret", - description: ( - <> - Get this from your OpenID Connect Provider as well. - - ), + description: <>Get this from your OpenID Connect Provider as well., placeholder: "gloas-f79cfa9a03c97f6ffab303177a5a6778a53c61e3914ba093412f68a9298a1b28", error: Boolean(errors.OIDC_CLIENT_SECRET), required: true, @@ -140,8 +158,8 @@ export const InstanceOpenIDConnectConfigForm: FC = (props) => { url: `${originURL}/auth/oidc/callback/`, description: ( <> - We will auto-generate this. Paste this into the{" "} - Redirect URI field of your OpenID Connect Provider. + We will auto-generate this. Paste this into the Redirect URI field of your + OpenID Connect Provider. ), }, @@ -176,6 +194,9 @@ export const InstanceOpenIDConnectConfigForm: FC = (props) => { } }; + + const automaticOpenIDConnectRedirect = formattedConfig?.IS_OIDC_AUTO ?? ""; + return ( <> = (props) => { {OIDC_SERVICE_FIELD.map((field) => ( ))} + { + Boolean(parseInt(automaticOpenIDConnectRedirect)) === true + ? updateConfig("IS_OIDC_AUTO", "0") + : updateConfig("IS_OIDC_AUTO", "1"); + }} + size="sm" + disabled={isSubmittingAuto} + /> diff --git a/admin/app/authentication/oidc/page.tsx b/admin/app/authentication/oidc/page.tsx index 5dc746aacd1..584104c3be2 100644 --- a/admin/app/authentication/oidc/page.tsx +++ b/admin/app/authentication/oidc/page.tsx @@ -11,7 +11,7 @@ import { PageHeader } from "@/components/common"; // hooks import { useInstance } from "@/hooks/store"; // icons -import OpenIDConnectLogo from "@/public/logos/gitlab-logo.svg"; +import OpenIDConnectLogo from "@/public/logos/oidc-logo.svg"; // local components import { InstanceOpenIDConnectConfigForm } from "./form"; @@ -38,7 +38,7 @@ const InstanceOpenIDConnectAuthenticationPage = observer(() => { loading: "Saving Configuration...", success: { title: "Configuration saved", - message: () => `GitLab authentication is now ${value ? "active" : "disabled"}.`, + message: () => `OIDC authentication is now ${value ? "active" : "disabled"}.`, }, error: { title: "Error", @@ -57,13 +57,13 @@ const InstanceOpenIDConnectAuthenticationPage = observer(() => { }; return ( <> - +
} + name="OIDC" + description="Allow members to login or sign up to plane with their OIDC accounts." + icon={OIDC Logo} config={ = observer((props) => { const { formattedConfig } = useInstance(); // derived values const enableOpenIDConnectConfig = formattedConfig?.IS_OIDC_ENABLED ?? ""; - const automaticOpenIDConnectRedirect = formattedConfig?.IS_OIDC_AUTO ?? ""; const isOpenIDConnectConfigured = !!formattedConfig?.OIDC_CLIENT_ID && !!formattedConfig?.OIDC_CLIENT_SECRET; return ( <> {isOpenIDConnectConfigured ? (
- + Edit = observer((props) => { />
) : ( - <> - { - Boolean(parseInt(automaticOpenIDConnectRedirect)) === true - ? updateConfig("IS_OIDC_AUTO", "0") - : updateConfig("IS_OIDC_AUTO", "1"); - }} - size="sm" - /> - - - Configure - - + + + Configure + )} ); diff --git a/web/core/components/account/oauth/oauth-options.tsx b/web/core/components/account/oauth/oauth-options.tsx index 798111fb9e6..5219004348a 100644 --- a/web/core/components/account/oauth/oauth-options.tsx +++ b/web/core/components/account/oauth/oauth-options.tsx @@ -12,7 +12,7 @@ export const OAuthOptions: React.FC = observer(() => { // hooks const { config } = useInstance(); - const isOAuthEnabled = (config && (config?.is_google_enabled || config?.is_github_enabled || config?.is_gitlab_enabled)) || false; + const isOAuthEnabled = (config && (config?.is_google_enabled || config?.is_github_enabled || config?.is_gitlab_enabled || config?.is_oidc_enabled)) || false; if (!isOAuthEnabled) return null;