[GIT-66] improvement: prevent disabling last enabled authentication method (#8570)
This commit is contained in:
parent
f7d5200ed8
commit
32a2584578
7 changed files with 167 additions and 127 deletions
|
|
@ -1,16 +1,18 @@
|
||||||
import { useState } from "react";
|
import { useCallback, useRef, useState } from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { useTheme } from "next-themes";
|
import { useTheme } from "next-themes";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
// plane internal packages
|
// plane internal packages
|
||||||
import { setPromiseToast } from "@plane/propel/toast";
|
import { setPromiseToast, setToast, TOAST_TYPE } from "@plane/propel/toast";
|
||||||
import type { TInstanceConfigurationKeys } from "@plane/types";
|
import type { TInstanceConfigurationKeys, TInstanceAuthenticationModes } from "@plane/types";
|
||||||
import { Loader, ToggleSwitch } from "@plane/ui";
|
import { Loader, ToggleSwitch } from "@plane/ui";
|
||||||
import { cn, resolveGeneralTheme } from "@plane/utils";
|
import { cn, resolveGeneralTheme } from "@plane/utils";
|
||||||
// components
|
// components
|
||||||
import { PageWrapper } from "@/components/common/page-wrapper";
|
import { PageWrapper } from "@/components/common/page-wrapper";
|
||||||
// hooks
|
|
||||||
import { AuthenticationMethodCard } from "@/components/authentication/authentication-method-card";
|
import { AuthenticationMethodCard } from "@/components/authentication/authentication-method-card";
|
||||||
|
// helpers
|
||||||
|
import { canDisableAuthMethod } from "@/helpers/authentication";
|
||||||
|
// hooks
|
||||||
import { useAuthenticationModes } from "@/hooks/oauth";
|
import { useAuthenticationModes } from "@/hooks/oauth";
|
||||||
import { useInstance } from "@/hooks/store";
|
import { useInstance } from "@/hooks/store";
|
||||||
// types
|
// types
|
||||||
|
|
@ -19,48 +21,87 @@ import type { Route } from "./+types/page";
|
||||||
const InstanceAuthenticationPage = observer(function InstanceAuthenticationPage(_props: Route.ComponentProps) {
|
const InstanceAuthenticationPage = observer(function InstanceAuthenticationPage(_props: Route.ComponentProps) {
|
||||||
// theme
|
// theme
|
||||||
const { resolvedTheme: resolvedThemeAdmin } = useTheme();
|
const { resolvedTheme: resolvedThemeAdmin } = useTheme();
|
||||||
// store
|
const resolvedTheme = resolveGeneralTheme(resolvedThemeAdmin);
|
||||||
const { fetchInstanceConfigurations, formattedConfig, updateInstanceConfigurations } = useInstance();
|
// Ref to store authentication modes for validation (avoids circular dependency)
|
||||||
|
const authenticationModesRef = useRef<TInstanceAuthenticationModes[]>([]);
|
||||||
// state
|
// state
|
||||||
const [isSubmitting, setIsSubmitting] = useState<boolean>(false);
|
const [isSubmitting, setIsSubmitting] = useState<boolean>(false);
|
||||||
|
// store hooks
|
||||||
|
const { fetchInstanceConfigurations, formattedConfig, updateInstanceConfigurations } = useInstance();
|
||||||
// derived values
|
// derived values
|
||||||
const enableSignUpConfig = formattedConfig?.ENABLE_SIGNUP ?? "";
|
const enableSignUpConfig = formattedConfig?.ENABLE_SIGNUP ?? "";
|
||||||
const resolvedTheme = resolveGeneralTheme(resolvedThemeAdmin);
|
|
||||||
|
|
||||||
useSWR("INSTANCE_CONFIGURATIONS", () => fetchInstanceConfigurations());
|
useSWR("INSTANCE_CONFIGURATIONS", () => fetchInstanceConfigurations());
|
||||||
|
|
||||||
const updateConfig = async (key: TInstanceConfigurationKeys, value: string) => {
|
// Create updateConfig with validation - uses authenticationModesRef for current modes
|
||||||
setIsSubmitting(true);
|
const updateConfig = useCallback(
|
||||||
|
(key: TInstanceConfigurationKeys, value: string): void => {
|
||||||
|
// Check if trying to disable (value === "0")
|
||||||
|
if (value === "0") {
|
||||||
|
// Check if this key is an authentication method key
|
||||||
|
const currentAuthModes = authenticationModesRef.current;
|
||||||
|
const isAuthMethodKey = currentAuthModes.some((method) => method.enabledConfigKey === key);
|
||||||
|
|
||||||
const payload = {
|
// Only validate if this is an authentication method key
|
||||||
[key]: value,
|
if (isAuthMethodKey) {
|
||||||
};
|
const canDisable = canDisableAuthMethod(key, currentAuthModes, formattedConfig);
|
||||||
|
|
||||||
const updateConfigPromise = updateInstanceConfigurations(payload);
|
if (!canDisable) {
|
||||||
|
setToast({
|
||||||
|
type: TOAST_TYPE.ERROR,
|
||||||
|
title: "Cannot disable authentication",
|
||||||
|
message:
|
||||||
|
"At least one authentication method must remain enabled. Please enable another method before disabling this one.",
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
setPromiseToast(updateConfigPromise, {
|
// Proceed with the update
|
||||||
loading: "Saving configuration",
|
setIsSubmitting(true);
|
||||||
success: {
|
|
||||||
title: "Success",
|
|
||||||
message: () => "Configuration saved successfully",
|
|
||||||
},
|
|
||||||
error: {
|
|
||||||
title: "Error",
|
|
||||||
message: () => "Failed to save configuration",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
await updateConfigPromise
|
const payload = {
|
||||||
.then(() => {
|
[key]: value,
|
||||||
setIsSubmitting(false);
|
};
|
||||||
})
|
|
||||||
.catch((err) => {
|
const updateConfigPromise = updateInstanceConfigurations(payload);
|
||||||
console.error(err);
|
|
||||||
setIsSubmitting(false);
|
setPromiseToast(updateConfigPromise, {
|
||||||
|
loading: "Saving configuration",
|
||||||
|
success: {
|
||||||
|
title: "Success",
|
||||||
|
message: () => "Configuration saved successfully",
|
||||||
|
},
|
||||||
|
error: {
|
||||||
|
title: "Error",
|
||||||
|
message: () => "Failed to save configuration",
|
||||||
|
},
|
||||||
});
|
});
|
||||||
};
|
|
||||||
|
|
||||||
const authenticationModes = useAuthenticationModes({ disabled: isSubmitting, updateConfig, resolvedTheme });
|
void updateConfigPromise
|
||||||
|
.then(() => {
|
||||||
|
setIsSubmitting(false);
|
||||||
|
return undefined;
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error(err);
|
||||||
|
setIsSubmitting(false);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[formattedConfig, updateInstanceConfigurations]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Get authentication modes - this will use updateConfig which includes validation
|
||||||
|
const authenticationModes = useAuthenticationModes({
|
||||||
|
disabled: isSubmitting,
|
||||||
|
updateConfig,
|
||||||
|
resolvedTheme,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update ref with latest authentication modes
|
||||||
|
authenticationModesRef.current = authenticationModes;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageWrapper
|
<PageWrapper
|
||||||
header={{
|
header={{
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,7 @@
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { KeyRound, Mails } from "lucide-react";
|
|
||||||
// plane packages
|
// plane packages
|
||||||
import type { TAdminAuthErrorInfo } from "@plane/constants";
|
import type { TAdminAuthErrorInfo } from "@plane/constants";
|
||||||
import { SUPPORT_EMAIL, EAdminAuthErrorCodes } from "@plane/constants";
|
import { SUPPORT_EMAIL, EAdminAuthErrorCodes } from "@plane/constants";
|
||||||
import type { TGetBaseAuthenticationModeProps, TInstanceAuthenticationModes } from "@plane/types";
|
|
||||||
import { resolveGeneralTheme } from "@plane/utils";
|
|
||||||
// components
|
|
||||||
import githubLightModeImage from "@/app/assets/logos/github-black.png?url";
|
|
||||||
import githubDarkModeImage from "@/app/assets/logos/github-white.png?url";
|
|
||||||
import GitlabLogo from "@/app/assets/logos/gitlab-logo.svg?url";
|
|
||||||
import GoogleLogo from "@/app/assets/logos/google-logo.svg?url";
|
|
||||||
import { EmailCodesConfiguration } from "@/components/authentication/email-config-switch";
|
|
||||||
import { GithubConfiguration } from "@/components/authentication/github-config";
|
|
||||||
import { GitlabConfiguration } from "@/components/authentication/gitlab-config";
|
|
||||||
import { GoogleConfiguration } from "@/components/authentication/google-config";
|
|
||||||
import { PasswordLoginConfiguration } from "@/components/authentication/password-config-switch";
|
|
||||||
// images
|
|
||||||
|
|
||||||
export enum EErrorAlertType {
|
export enum EErrorAlertType {
|
||||||
BANNER_ALERT = "BANNER_ALERT",
|
BANNER_ALERT = "BANNER_ALERT",
|
||||||
|
|
@ -106,53 +92,3 @@ export const authErrorHandler = (errorCode: EAdminAuthErrorCodes, email?: string
|
||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getBaseAuthenticationModes: (props: TGetBaseAuthenticationModeProps) => TInstanceAuthenticationModes[] = ({
|
|
||||||
disabled,
|
|
||||||
updateConfig,
|
|
||||||
resolvedTheme,
|
|
||||||
}) => [
|
|
||||||
{
|
|
||||||
key: "unique-codes",
|
|
||||||
name: "Unique codes",
|
|
||||||
description:
|
|
||||||
"Log in or sign up for Plane using codes sent via email. You need to have set up SMTP to use this method.",
|
|
||||||
icon: <Mails className="h-6 w-6 p-0.5 text-tertiary" />,
|
|
||||||
config: <EmailCodesConfiguration disabled={disabled} updateConfig={updateConfig} />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "passwords-login",
|
|
||||||
name: "Passwords",
|
|
||||||
description: "Allow members to create accounts with passwords and use it with their email addresses to sign in.",
|
|
||||||
icon: <KeyRound className="h-6 w-6 p-0.5 text-tertiary" />,
|
|
||||||
config: <PasswordLoginConfiguration disabled={disabled} updateConfig={updateConfig} />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "google",
|
|
||||||
name: "Google",
|
|
||||||
description: "Allow members to log in or sign up for Plane with their Google accounts.",
|
|
||||||
icon: <img src={GoogleLogo} height={20} width={20} alt="Google Logo" />,
|
|
||||||
config: <GoogleConfiguration disabled={disabled} updateConfig={updateConfig} />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "github",
|
|
||||||
name: "GitHub",
|
|
||||||
description: "Allow members to log in or sign up for Plane with their GitHub accounts.",
|
|
||||||
icon: (
|
|
||||||
<img
|
|
||||||
src={resolveGeneralTheme(resolvedTheme) === "dark" ? githubDarkModeImage : githubLightModeImage}
|
|
||||||
height={20}
|
|
||||||
width={20}
|
|
||||||
alt="GitHub Logo"
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
config: <GithubConfiguration disabled={disabled} updateConfig={updateConfig} />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "gitlab",
|
|
||||||
name: "GitLab",
|
|
||||||
description: "Allow members to log in or sign up to plane with their GitLab accounts.",
|
|
||||||
icon: <img src={GitlabLogo} height={20} width={20} alt="GitLab Logo" />,
|
|
||||||
config: <GitlabConfiguration disabled={disabled} updateConfig={updateConfig} />,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
|
||||||
30
apps/admin/core/helpers/authentication.ts
Normal file
30
apps/admin/core/helpers/authentication.ts
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
import type {
|
||||||
|
IFormattedInstanceConfiguration,
|
||||||
|
TInstanceAuthenticationModes,
|
||||||
|
TInstanceConfigurationKeys,
|
||||||
|
} from "@plane/types";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a given authentication method can be disabled.
|
||||||
|
* @param configKey - The configuration key to check.
|
||||||
|
* @param authModes - The authentication modes to check.
|
||||||
|
* @param formattedConfig - The formatted configuration to check.
|
||||||
|
* @returns True if the authentication method can be disabled, false otherwise.
|
||||||
|
*/
|
||||||
|
export const canDisableAuthMethod = (
|
||||||
|
configKey: TInstanceConfigurationKeys,
|
||||||
|
authModes: TInstanceAuthenticationModes[],
|
||||||
|
formattedConfig: IFormattedInstanceConfiguration | undefined
|
||||||
|
): boolean => {
|
||||||
|
// Count currently enabled methods
|
||||||
|
const enabledCount = authModes.reduce((count, method) => {
|
||||||
|
const enabledKey = method.enabledConfigKey;
|
||||||
|
if (!enabledKey || !formattedConfig) return count;
|
||||||
|
const isEnabled = Boolean(parseInt(formattedConfig[enabledKey] ?? "0"));
|
||||||
|
return isEnabled ? count + 1 : count;
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
// If trying to disable and only 1 method is enabled, prevent it
|
||||||
|
const isCurrentlyEnabled = Boolean(parseInt(formattedConfig?.[configKey] ?? "0"));
|
||||||
|
return !(isCurrentlyEnabled && enabledCount === 1);
|
||||||
|
};
|
||||||
|
|
@ -34,6 +34,7 @@ export const getCoreAuthenticationModesMap: (
|
||||||
"Log in or sign up for Plane using codes sent via email. You need to have set up SMTP to use this method.",
|
"Log in or sign up for Plane using codes sent via email. You need to have set up SMTP to use this method.",
|
||||||
icon: <Mails className="h-6 w-6 p-0.5 text-tertiary" />,
|
icon: <Mails className="h-6 w-6 p-0.5 text-tertiary" />,
|
||||||
config: <EmailCodesConfiguration disabled={disabled} updateConfig={updateConfig} />,
|
config: <EmailCodesConfiguration disabled={disabled} updateConfig={updateConfig} />,
|
||||||
|
enabledConfigKey: "ENABLE_MAGIC_LINK_LOGIN",
|
||||||
},
|
},
|
||||||
"passwords-login": {
|
"passwords-login": {
|
||||||
key: "passwords-login",
|
key: "passwords-login",
|
||||||
|
|
@ -41,6 +42,7 @@ export const getCoreAuthenticationModesMap: (
|
||||||
description: "Allow members to create accounts with passwords and use it with their email addresses to sign in.",
|
description: "Allow members to create accounts with passwords and use it with their email addresses to sign in.",
|
||||||
icon: <KeyRound className="h-6 w-6 p-0.5 text-tertiary" />,
|
icon: <KeyRound className="h-6 w-6 p-0.5 text-tertiary" />,
|
||||||
config: <PasswordLoginConfiguration disabled={disabled} updateConfig={updateConfig} />,
|
config: <PasswordLoginConfiguration disabled={disabled} updateConfig={updateConfig} />,
|
||||||
|
enabledConfigKey: "ENABLE_EMAIL_PASSWORD",
|
||||||
},
|
},
|
||||||
google: {
|
google: {
|
||||||
key: "google",
|
key: "google",
|
||||||
|
|
@ -48,6 +50,7 @@ export const getCoreAuthenticationModesMap: (
|
||||||
description: "Allow members to log in or sign up for Plane with their Google accounts.",
|
description: "Allow members to log in or sign up for Plane with their Google accounts.",
|
||||||
icon: <img src={googleLogo} height={20} width={20} alt="Google Logo" />,
|
icon: <img src={googleLogo} height={20} width={20} alt="Google Logo" />,
|
||||||
config: <GoogleConfiguration disabled={disabled} updateConfig={updateConfig} />,
|
config: <GoogleConfiguration disabled={disabled} updateConfig={updateConfig} />,
|
||||||
|
enabledConfigKey: "IS_GOOGLE_ENABLED",
|
||||||
},
|
},
|
||||||
github: {
|
github: {
|
||||||
key: "github",
|
key: "github",
|
||||||
|
|
@ -62,6 +65,7 @@ export const getCoreAuthenticationModesMap: (
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
config: <GithubConfiguration disabled={disabled} updateConfig={updateConfig} />,
|
config: <GithubConfiguration disabled={disabled} updateConfig={updateConfig} />,
|
||||||
|
enabledConfigKey: "IS_GITHUB_ENABLED",
|
||||||
},
|
},
|
||||||
gitlab: {
|
gitlab: {
|
||||||
key: "gitlab",
|
key: "gitlab",
|
||||||
|
|
@ -69,6 +73,7 @@ export const getCoreAuthenticationModesMap: (
|
||||||
description: "Allow members to log in or sign up to plane with their GitLab accounts.",
|
description: "Allow members to log in or sign up to plane with their GitLab accounts.",
|
||||||
icon: <img src={gitlabLogo} height={20} width={20} alt="GitLab Logo" />,
|
icon: <img src={gitlabLogo} height={20} width={20} alt="GitLab Logo" />,
|
||||||
config: <GitlabConfiguration disabled={disabled} updateConfig={updateConfig} />,
|
config: <GitlabConfiguration disabled={disabled} updateConfig={updateConfig} />,
|
||||||
|
enabledConfigKey: "IS_GITLAB_ENABLED",
|
||||||
},
|
},
|
||||||
gitea: {
|
gitea: {
|
||||||
key: "gitea",
|
key: "gitea",
|
||||||
|
|
@ -76,5 +81,6 @@ export const getCoreAuthenticationModesMap: (
|
||||||
description: "Allow members to log in or sign up to plane with their Gitea accounts.",
|
description: "Allow members to log in or sign up to plane with their Gitea accounts.",
|
||||||
icon: <img src={giteaLogo} height={20} width={20} alt="Gitea Logo" />,
|
icon: <img src={giteaLogo} height={20} width={20} alt="Gitea Logo" />,
|
||||||
config: <GiteaConfiguration disabled={disabled} updateConfig={updateConfig} />,
|
config: <GiteaConfiguration disabled={disabled} updateConfig={updateConfig} />,
|
||||||
|
enabledConfigKey: "IS_GITEA_ENABLED",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -14,10 +14,11 @@ import {
|
||||||
} from "@/helpers/authentication.helper";
|
} from "@/helpers/authentication.helper";
|
||||||
// hooks
|
// hooks
|
||||||
import { useOAuthConfig } from "@/hooks/oauth";
|
import { useOAuthConfig } from "@/hooks/oauth";
|
||||||
|
import { useInstance } from "@/hooks/store/use-instance";
|
||||||
// local imports
|
// local imports
|
||||||
import { TermsAndConditions } from "../terms-and-conditions";
|
import { TermsAndConditions } from "../terms-and-conditions";
|
||||||
import { AuthBanner } from "./auth-banner";
|
import { AuthBanner } from "./auth-banner";
|
||||||
import { AuthHeader } from "./auth-header";
|
import { AuthHeader, AuthHeaderBase } from "./auth-header";
|
||||||
import { AuthFormRoot } from "./form-root";
|
import { AuthFormRoot } from "./form-root";
|
||||||
|
|
||||||
type TAuthRoot = {
|
type TAuthRoot = {
|
||||||
|
|
@ -39,9 +40,13 @@ export const AuthRoot = observer(function AuthRoot(props: TAuthRoot) {
|
||||||
const [authStep, setAuthStep] = useState<EAuthSteps>(EAuthSteps.EMAIL);
|
const [authStep, setAuthStep] = useState<EAuthSteps>(EAuthSteps.EMAIL);
|
||||||
const [email, setEmail] = useState(emailParam ? emailParam.toString() : "");
|
const [email, setEmail] = useState(emailParam ? emailParam.toString() : "");
|
||||||
const [errorInfo, setErrorInfo] = useState<TAuthErrorInfo | undefined>(undefined);
|
const [errorInfo, setErrorInfo] = useState<TAuthErrorInfo | undefined>(undefined);
|
||||||
|
// store hooks
|
||||||
|
const { config } = useInstance();
|
||||||
// derived values
|
// derived values
|
||||||
const oAuthActionText = authMode === EAuthModes.SIGN_UP ? "Sign up" : "Sign in";
|
const oAuthActionText = authMode === EAuthModes.SIGN_UP ? "Sign up" : "Sign in";
|
||||||
const { isOAuthEnabled, oAuthOptions } = useOAuthConfig(oAuthActionText);
|
const { isOAuthEnabled, oAuthOptions } = useOAuthConfig(oAuthActionText);
|
||||||
|
const isEmailBasedAuthEnabled = config?.is_email_password_enabled || config?.is_magic_login_enabled;
|
||||||
|
const noAuthMethodsAvailable = !isOAuthEnabled && !isEmailBasedAuthEnabled;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!authMode && currentAuthMode) setAuthMode(currentAuthMode);
|
if (!authMode && currentAuthMode) setAuthMode(currentAuthMode);
|
||||||
|
|
@ -91,22 +96,37 @@ export const AuthRoot = observer(function AuthRoot(props: TAuthRoot) {
|
||||||
|
|
||||||
if (!authMode) return <></>;
|
if (!authMode) return <></>;
|
||||||
|
|
||||||
return (
|
if (noAuthMethodsAvailable) {
|
||||||
<div className="flex flex-col justify-center items-center flex-grow w-full py-6 mt-10">
|
return (
|
||||||
<div className="relative flex flex-col gap-6 max-w-[22.5rem] w-full">
|
<AuthContainer>
|
||||||
{errorInfo && errorInfo?.type === EErrorAlertType.BANNER_ALERT && (
|
<AuthHeaderBase
|
||||||
<AuthBanner message={errorInfo.message} handleBannerData={(value) => setErrorInfo(value)} />
|
header="No authentication methods available"
|
||||||
)}
|
subHeader="Please contact your administrator to enable authentication for your instance."
|
||||||
<AuthHeader
|
|
||||||
workspaceSlug={workspaceSlug?.toString() || undefined}
|
|
||||||
invitationId={invitation_id?.toString() || undefined}
|
|
||||||
invitationEmail={email || undefined}
|
|
||||||
authMode={authMode}
|
|
||||||
currentAuthStep={authStep}
|
|
||||||
/>
|
/>
|
||||||
|
</AuthContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
{isOAuthEnabled && <OAuthOptions options={oAuthOptions} compact={authStep === EAuthSteps.PASSWORD} />}
|
return (
|
||||||
|
<AuthContainer>
|
||||||
|
{errorInfo && errorInfo?.type === EErrorAlertType.BANNER_ALERT && (
|
||||||
|
<AuthBanner message={errorInfo.message} handleBannerData={(value) => setErrorInfo(value)} />
|
||||||
|
)}
|
||||||
|
<AuthHeader
|
||||||
|
workspaceSlug={workspaceSlug?.toString() || undefined}
|
||||||
|
invitationId={invitation_id?.toString() || undefined}
|
||||||
|
invitationEmail={email || undefined}
|
||||||
|
authMode={authMode}
|
||||||
|
currentAuthStep={authStep}
|
||||||
|
/>
|
||||||
|
{isOAuthEnabled && (
|
||||||
|
<OAuthOptions
|
||||||
|
options={oAuthOptions}
|
||||||
|
compact={authStep === EAuthSteps.PASSWORD}
|
||||||
|
showDivider={isEmailBasedAuthEnabled}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{isEmailBasedAuthEnabled && (
|
||||||
<AuthFormRoot
|
<AuthFormRoot
|
||||||
authStep={authStep}
|
authStep={authStep}
|
||||||
authMode={authMode}
|
authMode={authMode}
|
||||||
|
|
@ -117,8 +137,16 @@ export const AuthRoot = observer(function AuthRoot(props: TAuthRoot) {
|
||||||
setErrorInfo={(errorInfo) => setErrorInfo(errorInfo)}
|
setErrorInfo={(errorInfo) => setErrorInfo(errorInfo)}
|
||||||
currentAuthMode={currentAuthMode}
|
currentAuthMode={currentAuthMode}
|
||||||
/>
|
/>
|
||||||
<TermsAndConditions authType={authMode} />
|
)}
|
||||||
</div>
|
<TermsAndConditions authType={authMode} />
|
||||||
</div>
|
</AuthContainer>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function AuthContainer({ children }: { children: React.ReactNode }) {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col justify-center items-center flex-grow w-full py-6 mt-10">
|
||||||
|
<div className="relative flex flex-col gap-6 max-w-[22.5rem] w-full">{children}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
import type { TExtendedInstanceAuthenticationModeKeys } from "./auth-ee";
|
|
||||||
|
|
||||||
export type TCoreInstanceAuthenticationModeKeys =
|
export type TCoreInstanceAuthenticationModeKeys =
|
||||||
| "unique-codes"
|
| "unique-codes"
|
||||||
| "passwords-login"
|
| "passwords-login"
|
||||||
|
|
@ -8,9 +6,7 @@ export type TCoreInstanceAuthenticationModeKeys =
|
||||||
| "gitlab"
|
| "gitlab"
|
||||||
| "gitea";
|
| "gitea";
|
||||||
|
|
||||||
export type TInstanceAuthenticationModeKeys =
|
export type TInstanceAuthenticationModeKeys = TCoreInstanceAuthenticationModeKeys;
|
||||||
| TCoreInstanceAuthenticationModeKeys
|
|
||||||
| TExtendedInstanceAuthenticationModeKeys;
|
|
||||||
|
|
||||||
export type TInstanceAuthenticationModes = {
|
export type TInstanceAuthenticationModes = {
|
||||||
key: TInstanceAuthenticationModeKeys;
|
key: TInstanceAuthenticationModeKeys;
|
||||||
|
|
@ -18,6 +14,7 @@ export type TInstanceAuthenticationModes = {
|
||||||
description: string;
|
description: string;
|
||||||
icon: React.ReactNode;
|
icon: React.ReactNode;
|
||||||
config: React.ReactNode;
|
config: React.ReactNode;
|
||||||
|
enabledConfigKey: TInstanceAuthenticationMethodKeys;
|
||||||
unavailable?: boolean;
|
unavailable?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,13 +13,13 @@ export type TOAuthOption = {
|
||||||
type OAuthOptionsProps = {
|
type OAuthOptionsProps = {
|
||||||
options: TOAuthOption[];
|
options: TOAuthOption[];
|
||||||
compact?: boolean;
|
compact?: boolean;
|
||||||
|
showDivider?: boolean;
|
||||||
className?: string;
|
className?: string;
|
||||||
containerClassName?: string;
|
containerClassName?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function OAuthOptions(props: OAuthOptionsProps) {
|
export function OAuthOptions(props: OAuthOptionsProps) {
|
||||||
const { options, compact = false, className = "", containerClassName = "" } = props;
|
const { options, compact = false, showDivider = true, className = "", containerClassName = "" } = props;
|
||||||
|
|
||||||
// Filter enabled options
|
// Filter enabled options
|
||||||
const enabledOptions = options.filter((option) => option.enabled !== false);
|
const enabledOptions = options.filter((option) => option.enabled !== false);
|
||||||
|
|
@ -47,11 +47,13 @@ export function OAuthOptions(props: OAuthOptionsProps) {
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-4 flex items-center transition-all duration-300">
|
{showDivider && (
|
||||||
<hr className="w-full border-strong transition-colors duration-300" />
|
<div className="mt-4 flex items-center transition-all duration-300">
|
||||||
<p className="mx-3 flex-shrink-0 text-center text-13 text-placeholder transition-colors duration-300">or</p>
|
<hr className="w-full border-strong transition-colors duration-300" />
|
||||||
<hr className="w-full border-strong transition-colors duration-300" />
|
<p className="mx-3 flex-shrink-0 text-center text-13 text-placeholder transition-colors duration-300">or</p>
|
||||||
</div>
|
<hr className="w-full border-strong transition-colors duration-300" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue