Enforce system wide sso providers (#9058)
We have recently introduced the possibility to specify workspace specific auth providers. I'm: - introducing system wide auth providers (provided by clientConfig) - making sure workspace specific auth providers belong to system wide auth providers set
This commit is contained in:
@ -7,14 +7,14 @@ import { RecoilRoot, useRecoilValue } from 'recoil';
|
||||
import { iconsState } from 'twenty-ui';
|
||||
|
||||
import { useAuth } from '@/auth/hooks/useAuth';
|
||||
import { authProvidersState } from '@/client-config/states/authProvidersState';
|
||||
import { billingState } from '@/client-config/states/billingState';
|
||||
import { isDebugModeState } from '@/client-config/states/isDebugModeState';
|
||||
import { isDeveloperDefaultSignInPrefilledState } from '@/client-config/states/isDeveloperDefaultSignInPrefilledState';
|
||||
import { supportChatState } from '@/client-config/states/supportChatState';
|
||||
import { workspaceAuthProvidersState } from '@/workspace/states/workspaceAuthProvidersState';
|
||||
|
||||
import { email, mocks, password, results, token } from '../__mocks__/useAuth';
|
||||
import { isMultiWorkspaceEnabledState } from '@/client-config/states/isMultiWorkspaceEnabledState';
|
||||
import { email, mocks, password, results, token } from '../__mocks__/useAuth';
|
||||
|
||||
const Wrapper = ({ children }: { children: ReactNode }) => (
|
||||
<MockedProvider mocks={mocks} addTypename={false}>
|
||||
@ -77,7 +77,9 @@ describe('useAuth', () => {
|
||||
() => {
|
||||
const client = useApolloClient();
|
||||
const icons = useRecoilValue(iconsState);
|
||||
const authProviders = useRecoilValue(authProvidersState);
|
||||
const workspaceAuthProviders = useRecoilValue(
|
||||
workspaceAuthProvidersState,
|
||||
);
|
||||
const billing = useRecoilValue(billingState);
|
||||
const isDeveloperDefaultSignInPrefilled = useRecoilValue(
|
||||
isDeveloperDefaultSignInPrefilledState,
|
||||
@ -92,7 +94,7 @@ describe('useAuth', () => {
|
||||
client,
|
||||
state: {
|
||||
icons,
|
||||
authProviders,
|
||||
workspaceAuthProviders,
|
||||
billing,
|
||||
isDeveloperDefaultSignInPrefilled,
|
||||
supportChat,
|
||||
@ -118,7 +120,7 @@ describe('useAuth', () => {
|
||||
const { state } = result.current;
|
||||
|
||||
expect(state.icons).toEqual({});
|
||||
expect(state.authProviders).toEqual({
|
||||
expect(state.workspaceAuthProviders).toEqual({
|
||||
google: true,
|
||||
microsoft: false,
|
||||
magicLink: false,
|
||||
|
||||
@ -13,7 +13,6 @@ import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
||||
import { isCurrentUserLoadedState } from '@/auth/states/isCurrentUserLoadingState';
|
||||
import { isVerifyPendingState } from '@/auth/states/isVerifyPendingState';
|
||||
import { workspacesState } from '@/auth/states/workspaces';
|
||||
import { authProvidersState } from '@/client-config/states/authProvidersState';
|
||||
import { billingState } from '@/client-config/states/billingState';
|
||||
import { captchaProviderState } from '@/client-config/states/captchaProviderState';
|
||||
import { clientConfigApiStatusState } from '@/client-config/states/clientConfigApiStatusState';
|
||||
@ -48,6 +47,7 @@ import { useLastAuthenticatedWorkspaceDomain } from '@/domain-manager/hooks/useL
|
||||
import { useReadWorkspaceSubdomainFromCurrentLocation } from '@/domain-manager/hooks/useReadWorkspaceSubdomainFromCurrentLocation';
|
||||
import { domainConfigurationState } from '@/domain-manager/states/domainConfigurationState';
|
||||
import { isAppWaitingForFreshObjectMetadataState } from '@/object-metadata/states/isAppWaitingForFreshObjectMetadataState';
|
||||
import { workspaceAuthProvidersState } from '@/workspace/states/workspaceAuthProvidersState';
|
||||
|
||||
export const useAuth = () => {
|
||||
const setTokenPair = useSetRecoilState(tokenPairState);
|
||||
@ -90,7 +90,7 @@ export const useAuth = () => {
|
||||
const emptySnapshot = snapshot_UNSTABLE();
|
||||
const iconsValue = snapshot.getLoadable(iconsState).getValue();
|
||||
const authProvidersValue = snapshot
|
||||
.getLoadable(authProvidersState)
|
||||
.getLoadable(workspaceAuthProvidersState)
|
||||
.getValue();
|
||||
const billing = snapshot.getLoadable(billingState).getValue();
|
||||
const isDeveloperDefaultSignInPrefilled = snapshot
|
||||
@ -115,7 +115,7 @@ export const useAuth = () => {
|
||||
.getValue();
|
||||
const initialSnapshot = emptySnapshot.map(({ set }) => {
|
||||
set(iconsState, iconsValue);
|
||||
set(authProvidersState, authProvidersValue);
|
||||
set(workspaceAuthProvidersState, authProvidersValue);
|
||||
set(billingState, billing);
|
||||
set(
|
||||
isDeveloperDefaultSignInPrefilledState,
|
||||
|
||||
@ -1,37 +1,31 @@
|
||||
import styled from '@emotion/styled';
|
||||
import {
|
||||
IconGoogle,
|
||||
IconMicrosoft,
|
||||
Loader,
|
||||
MainButton,
|
||||
HorizontalSeparator,
|
||||
} from 'twenty-ui';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import { useSignInWithGoogle } from '@/auth/sign-in-up/hooks/useSignInWithGoogle';
|
||||
import { useSignInWithMicrosoft } from '@/auth/sign-in-up/hooks/useSignInWithMicrosoft';
|
||||
import { FormProvider } from 'react-hook-form';
|
||||
import { motion } from 'framer-motion';
|
||||
import { useState } from 'react';
|
||||
import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil';
|
||||
import { FormProvider } from 'react-hook-form';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil';
|
||||
import { HorizontalSeparator, Loader, MainButton } from 'twenty-ui';
|
||||
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
import { useAuth } from '@/auth/hooks/useAuth';
|
||||
import { SignInUpEmailField } from '@/auth/sign-in-up/components/SignInUpEmailField';
|
||||
import { SignInUpPasswordField } from '@/auth/sign-in-up/components/SignInUpPasswordField';
|
||||
import { SignInUpWithGoogle } from '@/auth/sign-in-up/components/SignInUpWithGoogle';
|
||||
import { SignInUpWithMicrosoft } from '@/auth/sign-in-up/components/SignInUpWithMicrosoft';
|
||||
import { useSignInUp } from '@/auth/sign-in-up/hooks/useSignInUp';
|
||||
import { useSignInUpForm } from '@/auth/sign-in-up/hooks/useSignInUpForm';
|
||||
import { signInUpModeState } from '@/auth/states/signInUpModeState';
|
||||
import {
|
||||
SignInUpStep,
|
||||
signInUpStepState,
|
||||
} from '@/auth/states/signInUpStepState';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
|
||||
import { useSignInUp } from '@/auth/sign-in-up/hooks/useSignInUp';
|
||||
import { useSignInUpForm } from '@/auth/sign-in-up/hooks/useSignInUpForm';
|
||||
import { SignInUpEmailField } from '@/auth/sign-in-up/components/SignInUpEmailField';
|
||||
import { SignInUpPasswordField } from '@/auth/sign-in-up/components/SignInUpPasswordField';
|
||||
import { useAuth } from '@/auth/hooks/useAuth';
|
||||
import { useReadCaptchaToken } from '@/captcha/hooks/useReadCaptchaToken';
|
||||
import { signInUpModeState } from '@/auth/states/signInUpModeState';
|
||||
import { useRequestFreshCaptchaToken } from '@/captcha/hooks/useRequestFreshCaptchaToken';
|
||||
import { SignInUpMode } from '@/auth/types/signInUpMode';
|
||||
import { useReadCaptchaToken } from '@/captcha/hooks/useReadCaptchaToken';
|
||||
import { useRequestFreshCaptchaToken } from '@/captcha/hooks/useRequestFreshCaptchaToken';
|
||||
import { authProvidersState } from '@/client-config/states/authProvidersState';
|
||||
import { useRedirectToWorkspaceDomain } from '@/domain-manager/hooks/useRedirectToWorkspaceDomain';
|
||||
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
const StyledContentContainer = styled(motion.div)`
|
||||
margin-bottom: ${({ theme }) => theme.spacing(8)};
|
||||
@ -46,11 +40,9 @@ const StyledForm = styled.form`
|
||||
`;
|
||||
|
||||
export const SignInUpGlobalScopeForm = () => {
|
||||
const theme = useTheme();
|
||||
const authProviders = useRecoilValue(authProvidersState);
|
||||
const signInUpStep = useRecoilValue(signInUpStepState);
|
||||
|
||||
const { signInWithGoogle } = useSignInWithGoogle();
|
||||
const { signInWithMicrosoft } = useSignInWithMicrosoft();
|
||||
const { checkUserExists } = useAuth();
|
||||
const { readCaptchaToken } = useReadCaptchaToken();
|
||||
const { redirectToWorkspaceDomain } = useRedirectToWorkspaceDomain();
|
||||
@ -116,20 +108,9 @@ export const SignInUpGlobalScopeForm = () => {
|
||||
return (
|
||||
<>
|
||||
<StyledContentContainer>
|
||||
<MainButton
|
||||
Icon={() => <IconGoogle size={theme.icon.size.lg} />}
|
||||
title="Continue with Google"
|
||||
onClick={signInWithGoogle}
|
||||
fullWidth
|
||||
/>
|
||||
<HorizontalSeparator visible={false} />
|
||||
<MainButton
|
||||
Icon={() => <IconMicrosoft size={theme.icon.size.lg} />}
|
||||
title="Continue with Microsoft"
|
||||
onClick={signInWithMicrosoft}
|
||||
fullWidth
|
||||
/>
|
||||
<HorizontalSeparator visible={false} />
|
||||
{authProviders.google && <SignInUpWithGoogle />}
|
||||
|
||||
{authProviders.microsoft && <SignInUpWithMicrosoft />}
|
||||
<HorizontalSeparator visible />
|
||||
{/* eslint-disable-next-line react/jsx-props-no-spreading */}
|
||||
<FormProvider {...form}>
|
||||
|
||||
@ -4,10 +4,10 @@ import { useSSO } from '@/auth/sign-in-up/hooks/useSSO';
|
||||
import { guessSSOIdentityProviderIconByUrl } from '@/settings/security/utils/guessSSOIdentityProviderIconByUrl';
|
||||
import styled from '@emotion/styled';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { MainButton, HorizontalSeparator } from 'twenty-ui';
|
||||
import { HorizontalSeparator, MainButton } from 'twenty-ui';
|
||||
|
||||
import { workspaceAuthProvidersState } from '@/workspace/states/workspaceAuthProvidersState';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
import { authProvidersState } from '@/client-config/states/authProvidersState';
|
||||
|
||||
const StyledContentContainer = styled.div`
|
||||
margin-bottom: ${({ theme }) => theme.spacing(8)};
|
||||
@ -15,15 +15,15 @@ const StyledContentContainer = styled.div`
|
||||
`;
|
||||
|
||||
export const SignInUpSSOIdentityProviderSelection = () => {
|
||||
const authProviders = useRecoilValue(authProvidersState);
|
||||
const workspaceAuthProviders = useRecoilValue(workspaceAuthProvidersState);
|
||||
|
||||
const { redirectToSSOLoginPage } = useSSO();
|
||||
|
||||
return (
|
||||
<>
|
||||
<StyledContentContainer>
|
||||
{isDefined(authProviders?.sso) &&
|
||||
authProviders?.sso.map((idp) => (
|
||||
{isDefined(workspaceAuthProviders?.sso) &&
|
||||
workspaceAuthProviders?.sso.map((idp) => (
|
||||
<>
|
||||
<MainButton
|
||||
key={idp.id}
|
||||
|
||||
@ -1,25 +1,25 @@
|
||||
import { IconLock, MainButton, HorizontalSeparator } from 'twenty-ui';
|
||||
import { useSSO } from '@/auth/sign-in-up/hooks/useSSO';
|
||||
import {
|
||||
SignInUpStep,
|
||||
signInUpStepState,
|
||||
} from '@/auth/states/signInUpStepState';
|
||||
import { workspaceAuthProvidersState } from '@/workspace/states/workspaceAuthProvidersState';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import { useRecoilValue, useSetRecoilState } from 'recoil';
|
||||
import { useSSO } from '@/auth/sign-in-up/hooks/useSSO';
|
||||
import { authProvidersState } from '@/client-config/states/authProvidersState';
|
||||
import { HorizontalSeparator, IconLock, MainButton } from 'twenty-ui';
|
||||
|
||||
export const SignInUpWithSSO = () => {
|
||||
const theme = useTheme();
|
||||
const setSignInUpStep = useSetRecoilState(signInUpStepState);
|
||||
const authProviders = useRecoilValue(authProvidersState);
|
||||
const workspaceAuthProviders = useRecoilValue(workspaceAuthProvidersState);
|
||||
|
||||
const signInUpStep = useRecoilValue(signInUpStepState);
|
||||
|
||||
const { redirectToSSOLoginPage } = useSSO();
|
||||
|
||||
const signInWithSSO = () => {
|
||||
if (authProviders.sso.length === 1) {
|
||||
return redirectToSSOLoginPage(authProviders.sso[0].id);
|
||||
if (workspaceAuthProviders.sso.length === 1) {
|
||||
return redirectToSSOLoginPage(workspaceAuthProviders.sso[0].id);
|
||||
}
|
||||
|
||||
setSignInUpStep(SignInUpStep.SSOIdentityProviderSelection);
|
||||
|
||||
@ -1,15 +1,15 @@
|
||||
import { SignInUpWithCredentials } from '@/auth/sign-in-up/components/SignInUpWithCredentials';
|
||||
import { SignInUpWithGoogle } from '@/auth/sign-in-up/components/SignInUpWithGoogle';
|
||||
import { SignInUpWithMicrosoft } from '@/auth/sign-in-up/components/SignInUpWithMicrosoft';
|
||||
import { SignInUpWithSSO } from '@/auth/sign-in-up/components/SignInUpWithSSO';
|
||||
import { useHandleResetPassword } from '@/auth/sign-in-up/hooks/useHandleResetPassword';
|
||||
import { useSignInUp } from '@/auth/sign-in-up/hooks/useSignInUp';
|
||||
import { useSignInUpForm } from '@/auth/sign-in-up/hooks/useSignInUpForm';
|
||||
import { SignInUpStep } from '@/auth/states/signInUpStepState';
|
||||
import { authProvidersState } from '@/client-config/states/authProvidersState';
|
||||
import { workspaceAuthProvidersState } from '@/workspace/states/workspaceAuthProvidersState';
|
||||
import styled from '@emotion/styled';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { ActionLink, HorizontalSeparator } from 'twenty-ui';
|
||||
import { SignInUpWithGoogle } from '@/auth/sign-in-up/components/SignInUpWithGoogle';
|
||||
import { SignInUpWithMicrosoft } from '@/auth/sign-in-up/components/SignInUpWithMicrosoft';
|
||||
import { SignInUpWithSSO } from '@/auth/sign-in-up/components/SignInUpWithSSO';
|
||||
import { SignInUpWithCredentials } from '@/auth/sign-in-up/components/SignInUpWithCredentials';
|
||||
|
||||
const StyledContentContainer = styled.div`
|
||||
margin-bottom: ${({ theme }) => theme.spacing(8)};
|
||||
@ -17,7 +17,7 @@ const StyledContentContainer = styled.div`
|
||||
`;
|
||||
|
||||
export const SignInUpWorkspaceScopeForm = () => {
|
||||
const [authProviders] = useRecoilState(authProvidersState);
|
||||
const workspaceAuthProviders = useRecoilValue(workspaceAuthProvidersState);
|
||||
|
||||
const { form } = useSignInUpForm();
|
||||
const { handleResetPassword } = useHandleResetPassword();
|
||||
@ -27,20 +27,20 @@ export const SignInUpWorkspaceScopeForm = () => {
|
||||
return (
|
||||
<>
|
||||
<StyledContentContainer>
|
||||
{authProviders.google && <SignInUpWithGoogle />}
|
||||
{workspaceAuthProviders.google && <SignInUpWithGoogle />}
|
||||
|
||||
{authProviders.microsoft && <SignInUpWithMicrosoft />}
|
||||
{workspaceAuthProviders.microsoft && <SignInUpWithMicrosoft />}
|
||||
|
||||
{authProviders.sso.length > 0 && <SignInUpWithSSO />}
|
||||
{workspaceAuthProviders.sso.length > 0 && <SignInUpWithSSO />}
|
||||
|
||||
{(authProviders.google ||
|
||||
authProviders.microsoft ||
|
||||
authProviders.sso.length > 0) &&
|
||||
authProviders.password ? (
|
||||
{(workspaceAuthProviders.google ||
|
||||
workspaceAuthProviders.microsoft ||
|
||||
workspaceAuthProviders.sso.length > 0) &&
|
||||
workspaceAuthProviders.password ? (
|
||||
<HorizontalSeparator visible />
|
||||
) : null}
|
||||
|
||||
{authProviders.password && <SignInUpWithCredentials />}
|
||||
{workspaceAuthProviders.password && <SignInUpWithCredentials />}
|
||||
</StyledContentContainer>
|
||||
{signInUpStep === SignInUpStep.Password && (
|
||||
<ActionLink onClick={handleResetPassword(form.getValues('email'))}>
|
||||
|
||||
@ -1,16 +1,16 @@
|
||||
import { SignInUpStep } from '@/auth/states/signInUpStepState';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
import { useSignInUp } from '@/auth/sign-in-up/hooks/useSignInUp';
|
||||
import { authProvidersState } from '@/client-config/states/authProvidersState';
|
||||
import { useSignInUpForm } from '@/auth/sign-in-up/hooks/useSignInUpForm';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import { SignInUpStep } from '@/auth/states/signInUpStepState';
|
||||
import { workspaceAuthProvidersState } from '@/workspace/states/workspaceAuthProvidersState';
|
||||
import { useCallback, useEffect } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
const searchParams = new URLSearchParams(window.location.search);
|
||||
const email = searchParams.get('email');
|
||||
|
||||
export const SignInUpWorkspaceScopeFormEffect = () => {
|
||||
const [authProviders] = useRecoilState(authProvidersState);
|
||||
const workspaceAuthProviders = useRecoilValue(workspaceAuthProvidersState);
|
||||
|
||||
const { form } = useSignInUpForm();
|
||||
|
||||
@ -20,22 +20,22 @@ export const SignInUpWorkspaceScopeFormEffect = () => {
|
||||
const checkAuthProviders = useCallback(() => {
|
||||
if (
|
||||
signInUpStep === SignInUpStep.Init &&
|
||||
!authProviders.google &&
|
||||
!authProviders.microsoft &&
|
||||
!authProviders.sso
|
||||
!workspaceAuthProviders.google &&
|
||||
!workspaceAuthProviders.microsoft &&
|
||||
!workspaceAuthProviders.sso
|
||||
) {
|
||||
return continueWithEmail();
|
||||
}
|
||||
|
||||
if (isDefined(email) && authProviders.password) {
|
||||
if (isDefined(email) && workspaceAuthProviders.password) {
|
||||
return continueWithCredentials();
|
||||
}
|
||||
}, [
|
||||
signInUpStep,
|
||||
authProviders.google,
|
||||
authProviders.microsoft,
|
||||
authProviders.sso,
|
||||
authProviders.password,
|
||||
workspaceAuthProviders.google,
|
||||
workspaceAuthProviders.microsoft,
|
||||
workspaceAuthProviders.sso,
|
||||
workspaceAuthProviders.password,
|
||||
continueWithEmail,
|
||||
continueWithCredentials,
|
||||
]);
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { apiConfigState } from '@/client-config/states/apiConfigState';
|
||||
import { authProvidersState } from '@/client-config/states/authProvidersState';
|
||||
import { billingState } from '@/client-config/states/billingState';
|
||||
import { captchaProviderState } from '@/client-config/states/captchaProviderState';
|
||||
import { chromeExtensionIdState } from '@/client-config/states/chromeExtensionIdState';
|
||||
@ -7,19 +8,20 @@ import { isAnalyticsEnabledState } from '@/client-config/states/isAnalyticsEnabl
|
||||
import { isDebugModeState } from '@/client-config/states/isDebugModeState';
|
||||
import { isDeveloperDefaultSignInPrefilledState } from '@/client-config/states/isDeveloperDefaultSignInPrefilledState';
|
||||
import { isMultiWorkspaceEnabledState } from '@/client-config/states/isMultiWorkspaceEnabledState';
|
||||
import { isSSOEnabledState } from '@/client-config/states/isSSOEnabledState';
|
||||
import { sentryConfigState } from '@/client-config/states/sentryConfigState';
|
||||
import { supportChatState } from '@/client-config/states/supportChatState';
|
||||
import { domainConfigurationState } from '@/domain-manager/states/domainConfigurationState';
|
||||
import { useEffect } from 'react';
|
||||
import { useRecoilState, useSetRecoilState } from 'recoil';
|
||||
import { useGetClientConfigQuery } from '~/generated/graphql';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
import { domainConfigurationState } from '@/domain-manager/states/domainConfigurationState';
|
||||
import { isSSOEnabledState } from '@/client-config/states/isSSOEnabledState';
|
||||
|
||||
export const ClientConfigProviderEffect = () => {
|
||||
const setIsDebugMode = useSetRecoilState(isDebugModeState);
|
||||
const setIsAnalyticsEnabled = useSetRecoilState(isAnalyticsEnabledState);
|
||||
const setDomainConfiguration = useSetRecoilState(domainConfigurationState);
|
||||
const setAuthProviders = useSetRecoilState(authProvidersState);
|
||||
|
||||
const setIsDeveloperDefaultSignInPrefilled = useSetRecoilState(
|
||||
isDeveloperDefaultSignInPrefilledState,
|
||||
@ -73,6 +75,13 @@ export const ClientConfigProviderEffect = () => {
|
||||
error: undefined,
|
||||
}));
|
||||
|
||||
setAuthProviders({
|
||||
google: data?.clientConfig.authProviders.google,
|
||||
microsoft: data?.clientConfig.authProviders.microsoft,
|
||||
password: data?.clientConfig.authProviders.password,
|
||||
magicLink: false,
|
||||
sso: data?.clientConfig.authProviders.sso,
|
||||
});
|
||||
setIsDebugMode(data?.clientConfig.debugMode);
|
||||
setIsAnalyticsEnabled(data?.clientConfig.analyticsEnabled);
|
||||
setIsDeveloperDefaultSignInPrefilled(data?.clientConfig.signInPrefilled);
|
||||
@ -115,6 +124,7 @@ export const ClientConfigProviderEffect = () => {
|
||||
error,
|
||||
setDomainConfiguration,
|
||||
setIsSSOEnabledState,
|
||||
setAuthProviders,
|
||||
]);
|
||||
|
||||
return <></>;
|
||||
|
||||
@ -8,6 +8,18 @@ export const GET_CLIENT_CONFIG = gql`
|
||||
billingUrl
|
||||
billingFreeTrialDurationInDays
|
||||
}
|
||||
authProviders {
|
||||
google
|
||||
password
|
||||
microsoft
|
||||
sso {
|
||||
id
|
||||
name
|
||||
type
|
||||
status
|
||||
issuer
|
||||
}
|
||||
}
|
||||
signInPrefilled
|
||||
isMultiWorkspaceEnabled
|
||||
isSSOEnabled
|
||||
|
||||
@ -1,17 +1,19 @@
|
||||
import { useGetPublicWorkspaceDataBySubdomainQuery } from '~/generated/graphql';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
import { authProvidersState } from '@/client-config/states/authProvidersState';
|
||||
import { workspacePublicDataState } from '@/auth/states/workspacePublicDataState';
|
||||
import { useRecoilValue, useSetRecoilState } from 'recoil';
|
||||
import { useRedirectToDefaultDomain } from '@/domain-manager/hooks/useRedirectToDefaultDomain';
|
||||
import { useLastAuthenticatedWorkspaceDomain } from '@/domain-manager/hooks/useLastAuthenticatedWorkspaceDomain';
|
||||
import { isMultiWorkspaceEnabledState } from '@/client-config/states/isMultiWorkspaceEnabledState';
|
||||
import { useIsCurrentLocationOnDefaultDomain } from '@/domain-manager/hooks/useIsCurrentLocationOnDefaultDomain';
|
||||
import { useLastAuthenticatedWorkspaceDomain } from '@/domain-manager/hooks/useLastAuthenticatedWorkspaceDomain';
|
||||
import { useRedirectToDefaultDomain } from '@/domain-manager/hooks/useRedirectToDefaultDomain';
|
||||
import { workspaceAuthProvidersState } from '@/workspace/states/workspaceAuthProvidersState';
|
||||
import { useRecoilValue, useSetRecoilState } from 'recoil';
|
||||
import { useGetPublicWorkspaceDataBySubdomainQuery } from '~/generated/graphql';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
export const useGetPublicWorkspaceDataBySubdomain = () => {
|
||||
const { isDefaultDomain } = useIsCurrentLocationOnDefaultDomain();
|
||||
const isMultiWorkspaceEnabled = useRecoilValue(isMultiWorkspaceEnabledState);
|
||||
const setAuthProviders = useSetRecoilState(authProvidersState);
|
||||
const setWorkspaceAuthProviders = useSetRecoilState(
|
||||
workspaceAuthProvidersState,
|
||||
);
|
||||
const workspacePublicData = useRecoilValue(workspacePublicDataState);
|
||||
const { redirectToDefaultDomain } = useRedirectToDefaultDomain();
|
||||
const setWorkspacePublicDataState = useSetRecoilState(
|
||||
@ -25,7 +27,9 @@ export const useGetPublicWorkspaceDataBySubdomain = () => {
|
||||
(isMultiWorkspaceEnabled && isDefaultDomain) ||
|
||||
isDefined(workspacePublicData),
|
||||
onCompleted: (data) => {
|
||||
setAuthProviders(data.getPublicWorkspaceDataBySubdomain.authProviders);
|
||||
setWorkspaceAuthProviders(
|
||||
data.getPublicWorkspaceDataBySubdomain.authProviders,
|
||||
);
|
||||
setWorkspacePublicDataState(data.getPublicWorkspaceDataBySubdomain);
|
||||
},
|
||||
onError: (error) => {
|
||||
|
||||
@ -18,6 +18,7 @@ import { ChipGeneratorsDecorator } from '~/testing/decorators/ChipGeneratorsDeco
|
||||
import { MemoryRouterDecorator } from '~/testing/decorators/MemoryRouterDecorator';
|
||||
import { getProfilingStory } from '~/testing/profiling/utils/getProfilingStory';
|
||||
|
||||
import { RecordIndexContextProvider } from '@/object-record/record-index/contexts/RecordIndexContext';
|
||||
import { RecordTableBodyContextProvider } from '@/object-record/record-table/contexts/RecordTableBodyContext';
|
||||
import { RecordTableContextProvider } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||
import { RecordTableRowContextProvider } from '@/object-record/record-table/contexts/RecordTableRowContext';
|
||||
@ -63,87 +64,98 @@ const meta: Meta = {
|
||||
(Story) => {
|
||||
return (
|
||||
<RecordFieldValueSelectorContextProvider>
|
||||
<RecordTableContextProvider
|
||||
<RecordIndexContextProvider
|
||||
value={{
|
||||
recordTableId: 'recordTableId',
|
||||
viewBarId: mockPerformance.recordId,
|
||||
indexIdentifierUrl: (_recordId: string) => '',
|
||||
onIndexRecordsLoaded: () => {},
|
||||
objectNamePlural: 'companies',
|
||||
objectNameSingular: 'company',
|
||||
objectMetadataItem: mockPerformance.objectMetadataItem as any,
|
||||
visibleTableColumns: mockPerformance.visibleTableColumns as any,
|
||||
objectNameSingular:
|
||||
mockPerformance.objectMetadataItem.nameSingular,
|
||||
recordIndexId: 'recordIndexId',
|
||||
}}
|
||||
>
|
||||
<RecordTableComponentInstance
|
||||
recordTableId="asd"
|
||||
onColumnsChange={() => {}}
|
||||
<RecordTableContextProvider
|
||||
value={{
|
||||
recordTableId: 'recordTableId',
|
||||
viewBarId: mockPerformance.recordId,
|
||||
objectMetadataItem: mockPerformance.objectMetadataItem as any,
|
||||
visibleTableColumns: mockPerformance.visibleTableColumns as any,
|
||||
objectNameSingular:
|
||||
mockPerformance.objectMetadataItem.nameSingular,
|
||||
}}
|
||||
>
|
||||
<RecordTableBodyContextProvider
|
||||
value={{
|
||||
onUpsertRecord: () => {},
|
||||
onOpenTableCell: () => {},
|
||||
onMoveFocus: () => {},
|
||||
onCloseTableCell: () => {},
|
||||
onMoveSoftFocusToCell: () => {},
|
||||
onActionMenuDropdownOpened: () => {},
|
||||
onCellMouseEnter: () => {},
|
||||
}}
|
||||
<RecordTableComponentInstance
|
||||
recordTableId="asd"
|
||||
onColumnsChange={() => {}}
|
||||
>
|
||||
<RecordTableRowContextProvider
|
||||
<RecordTableBodyContextProvider
|
||||
value={{
|
||||
objectNameSingular:
|
||||
mockPerformance.entityValue.__typename.toLocaleLowerCase(),
|
||||
recordId: mockPerformance.recordId,
|
||||
rowIndex: 0,
|
||||
pathToShowPage:
|
||||
getBasePathToShowPage({
|
||||
objectNameSingular:
|
||||
mockPerformance.entityValue.__typename.toLocaleLowerCase(),
|
||||
}) + mockPerformance.recordId,
|
||||
isSelected: false,
|
||||
isPendingRow: false,
|
||||
inView: true,
|
||||
onUpsertRecord: () => {},
|
||||
onOpenTableCell: () => {},
|
||||
onMoveFocus: () => {},
|
||||
onCloseTableCell: () => {},
|
||||
onMoveSoftFocusToCell: () => {},
|
||||
onActionMenuDropdownOpened: () => {},
|
||||
onCellMouseEnter: () => {},
|
||||
}}
|
||||
>
|
||||
<RecordTableRowDraggableContextProvider
|
||||
<RecordTableRowContextProvider
|
||||
value={{
|
||||
isDragging: false,
|
||||
dragHandleProps: null,
|
||||
objectNameSingular:
|
||||
mockPerformance.entityValue.__typename.toLocaleLowerCase(),
|
||||
recordId: mockPerformance.recordId,
|
||||
rowIndex: 0,
|
||||
pathToShowPage:
|
||||
getBasePathToShowPage({
|
||||
objectNameSingular:
|
||||
mockPerformance.entityValue.__typename.toLocaleLowerCase(),
|
||||
}) + mockPerformance.recordId,
|
||||
isSelected: false,
|
||||
isPendingRow: false,
|
||||
inView: true,
|
||||
}}
|
||||
>
|
||||
<RecordTableCellContext.Provider
|
||||
<RecordTableRowDraggableContextProvider
|
||||
value={{
|
||||
columnDefinition: mockPerformance.fieldDefinition,
|
||||
columnIndex: 0,
|
||||
cellPosition: { row: 0, column: 0 },
|
||||
hasSoftFocus: false,
|
||||
isInEditMode: false,
|
||||
isDragging: false,
|
||||
dragHandleProps: null,
|
||||
}}
|
||||
>
|
||||
<FieldContext.Provider
|
||||
<RecordTableCellContext.Provider
|
||||
value={{
|
||||
recordId: mockPerformance.recordId,
|
||||
isLabelIdentifier: false,
|
||||
fieldDefinition: {
|
||||
...mockPerformance.fieldDefinition,
|
||||
},
|
||||
hotkeyScope: 'hotkey-scope',
|
||||
columnDefinition: mockPerformance.fieldDefinition,
|
||||
columnIndex: 0,
|
||||
cellPosition: { row: 0, column: 0 },
|
||||
hasSoftFocus: false,
|
||||
isInEditMode: false,
|
||||
}}
|
||||
>
|
||||
<RelationFieldValueSetterEffect />
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<Story />
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</FieldContext.Provider>
|
||||
</RecordTableCellContext.Provider>
|
||||
</RecordTableRowDraggableContextProvider>
|
||||
</RecordTableRowContextProvider>
|
||||
</RecordTableBodyContextProvider>
|
||||
</RecordTableComponentInstance>
|
||||
</RecordTableContextProvider>
|
||||
<FieldContext.Provider
|
||||
value={{
|
||||
recordId: mockPerformance.recordId,
|
||||
isLabelIdentifier: false,
|
||||
fieldDefinition: {
|
||||
...mockPerformance.fieldDefinition,
|
||||
},
|
||||
hotkeyScope: 'hotkey-scope',
|
||||
}}
|
||||
>
|
||||
<RelationFieldValueSetterEffect />
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<Story />
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</FieldContext.Provider>
|
||||
</RecordTableCellContext.Provider>
|
||||
</RecordTableRowDraggableContextProvider>
|
||||
</RecordTableRowContextProvider>
|
||||
</RecordTableBodyContextProvider>
|
||||
</RecordTableComponentInstance>
|
||||
</RecordTableContextProvider>
|
||||
</RecordIndexContextProvider>
|
||||
</RecordFieldValueSelectorContextProvider>
|
||||
);
|
||||
},
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
||||
import { authProvidersState } from '@/client-config/states/authProvidersState';
|
||||
import { SettingsOptionCardContentToggle } from '@/settings/components/SettingsOptions/SettingsOptionCardContentToggle';
|
||||
import { SSOIdentitiesProvidersState } from '@/settings/security/states/SSOIdentitiesProvidersState';
|
||||
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
|
||||
@ -25,6 +26,7 @@ const StyledSettingsSecurityOptionsList = styled.div`
|
||||
export const SettingsSecurityOptionsList = () => {
|
||||
const { enqueueSnackBar } = useSnackBar();
|
||||
const SSOIdentitiesProviders = useRecoilValue(SSOIdentitiesProvidersState);
|
||||
const authProviders = useRecoilValue(authProvidersState);
|
||||
|
||||
const [currentWorkspace, setCurrentWorkspace] = useRecoilState(
|
||||
currentWorkspaceState,
|
||||
@ -123,32 +125,38 @@ export const SettingsSecurityOptionsList = () => {
|
||||
{currentWorkspace && (
|
||||
<>
|
||||
<Card rounded>
|
||||
<SettingsOptionCardContentToggle
|
||||
Icon={IconGoogle}
|
||||
title="Google"
|
||||
description="Allow logins through Google's single sign-on functionality."
|
||||
checked={currentWorkspace.isGoogleAuthEnabled}
|
||||
advancedMode
|
||||
divider
|
||||
onChange={() => toggleAuthMethod('google')}
|
||||
/>
|
||||
<SettingsOptionCardContentToggle
|
||||
Icon={IconMicrosoft}
|
||||
title="Microsoft"
|
||||
description="Allow logins through Microsoft's single sign-on functionality."
|
||||
checked={currentWorkspace.isMicrosoftAuthEnabled}
|
||||
advancedMode
|
||||
divider
|
||||
onChange={() => toggleAuthMethod('microsoft')}
|
||||
/>
|
||||
<SettingsOptionCardContentToggle
|
||||
Icon={IconPassword}
|
||||
title="Password"
|
||||
description="Allow users to sign in with an email and password."
|
||||
checked={currentWorkspace.isPasswordAuthEnabled}
|
||||
advancedMode
|
||||
onChange={() => toggleAuthMethod('password')}
|
||||
/>
|
||||
{authProviders.google === true && (
|
||||
<SettingsOptionCardContentToggle
|
||||
Icon={IconGoogle}
|
||||
title="Google"
|
||||
description="Allow logins through Google's single sign-on functionality."
|
||||
checked={currentWorkspace.isGoogleAuthEnabled}
|
||||
advancedMode
|
||||
divider
|
||||
onChange={() => toggleAuthMethod('google')}
|
||||
/>
|
||||
)}
|
||||
{authProviders.microsoft === true && (
|
||||
<SettingsOptionCardContentToggle
|
||||
Icon={IconMicrosoft}
|
||||
title="Microsoft"
|
||||
description="Allow logins through Microsoft's single sign-on functionality."
|
||||
checked={currentWorkspace.isMicrosoftAuthEnabled}
|
||||
advancedMode
|
||||
divider
|
||||
onChange={() => toggleAuthMethod('microsoft')}
|
||||
/>
|
||||
)}
|
||||
{authProviders.password === true && (
|
||||
<SettingsOptionCardContentToggle
|
||||
Icon={IconPassword}
|
||||
title="Password"
|
||||
description="Allow users to sign in with an email and password."
|
||||
checked={currentWorkspace.isPasswordAuthEnabled}
|
||||
advancedMode
|
||||
onChange={() => toggleAuthMethod('password')}
|
||||
/>
|
||||
)}
|
||||
</Card>
|
||||
<Card rounded>
|
||||
<SettingsOptionCardContentToggle
|
||||
|
||||
@ -0,0 +1,14 @@
|
||||
import { createState } from 'twenty-ui';
|
||||
|
||||
import { AuthProviders } from '~/generated/graphql';
|
||||
|
||||
export const workspaceAuthProvidersState = createState<AuthProviders>({
|
||||
key: 'workspaceAuthProvidersState',
|
||||
defaultValue: {
|
||||
google: true,
|
||||
magicLink: false,
|
||||
password: true,
|
||||
microsoft: false,
|
||||
sso: [],
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user