diff --git a/packages/twenty-front/src/modules/app/components/AppRouterProviders.tsx b/packages/twenty-front/src/modules/app/components/AppRouterProviders.tsx index 9cd28314ce11..5ba248a56a66 100644 --- a/packages/twenty-front/src/modules/app/components/AppRouterProviders.tsx +++ b/packages/twenty-front/src/modules/app/components/AppRouterProviders.tsx @@ -22,6 +22,7 @@ import { WorkspaceProviderEffect } from '@/workspace/components/WorkspaceProvide import { StrictMode } from 'react'; import { Outlet, useLocation } from 'react-router-dom'; import { getPageTitleFromPath } from '~/utils/title-utils'; +import { PageFavicon } from '@/ui/utilities/page-favicon/components/PageFavicon'; export const AppRouterProviders = () => { const { pathname } = useLocation(); @@ -49,6 +50,7 @@ export const AppRouterProviders = () => { + diff --git a/packages/twenty-front/src/modules/auth/sign-in-up/components/SignInUpGlobalScopeForm.tsx b/packages/twenty-front/src/modules/auth/sign-in-up/components/SignInUpGlobalScopeForm.tsx index a441b1ee19a2..ad934cd299c2 100644 --- a/packages/twenty-front/src/modules/auth/sign-in-up/components/SignInUpGlobalScopeForm.tsx +++ b/packages/twenty-front/src/modules/auth/sign-in-up/components/SignInUpGlobalScopeForm.tsx @@ -31,7 +31,7 @@ import { useReadCaptchaToken } from '@/captcha/hooks/useReadCaptchaToken'; import { signInUpModeState } from '@/auth/states/signInUpModeState'; import { useRequestFreshCaptchaToken } from '@/captcha/hooks/useRequestFreshCaptchaToken'; import { useUrlManager } from '@/url-manager/hooks/useUrlManager'; -import { SignInUpMode } from '@/auth/types/signInUpMode.type'; +import { SignInUpMode } from '@/auth/types/signInUpMode'; const StyledContentContainer = styled(motion.div)` margin-bottom: ${({ theme }) => theme.spacing(8)}; diff --git a/packages/twenty-front/src/modules/auth/sign-in-up/components/SignInUpPasswordField.tsx b/packages/twenty-front/src/modules/auth/sign-in-up/components/SignInUpPasswordField.tsx index 44ba13b603fe..49d7a0ca2db6 100644 --- a/packages/twenty-front/src/modules/auth/sign-in-up/components/SignInUpPasswordField.tsx +++ b/packages/twenty-front/src/modules/auth/sign-in-up/components/SignInUpPasswordField.tsx @@ -5,7 +5,7 @@ import { motion } from 'framer-motion'; import { StyledText } from 'twenty-ui'; import { useTheme } from '@emotion/react'; import { Form } from '@/auth/sign-in-up/hooks/useSignInUpForm'; -import { SignInUpMode } from '@/auth/types/signInUpMode.type'; +import { SignInUpMode } from '@/auth/types/signInUpMode'; const StyledFullWidthMotionDiv = styled(motion.div)` width: 100%; diff --git a/packages/twenty-front/src/modules/auth/sign-in-up/components/SignInUpWithCredentials.tsx b/packages/twenty-front/src/modules/auth/sign-in-up/components/SignInUpWithCredentials.tsx index c238e05d05c0..a1176e13c9d1 100644 --- a/packages/twenty-front/src/modules/auth/sign-in-up/components/SignInUpWithCredentials.tsx +++ b/packages/twenty-front/src/modules/auth/sign-in-up/components/SignInUpWithCredentials.tsx @@ -14,7 +14,7 @@ import { useState, useMemo } from 'react'; import { captchaProviderState } from '@/client-config/states/captchaProviderState'; import { isRequestingCaptchaTokenState } from '@/captcha/states/isRequestingCaptchaTokenState'; import { FormProvider } from 'react-hook-form'; -import { SignInUpMode } from '@/auth/types/signInUpMode.type'; +import { SignInUpMode } from '@/auth/types/signInUpMode'; const StyledForm = styled.form` align-items: center; diff --git a/packages/twenty-front/src/modules/auth/sign-in-up/components/SignInUpWorkspaceScopeForm.tsx b/packages/twenty-front/src/modules/auth/sign-in-up/components/SignInUpWorkspaceScopeForm.tsx index b53c593d51fa..877598a8bcdd 100644 --- a/packages/twenty-front/src/modules/auth/sign-in-up/components/SignInUpWorkspaceScopeForm.tsx +++ b/packages/twenty-front/src/modules/auth/sign-in-up/components/SignInUpWorkspaceScopeForm.tsx @@ -4,15 +4,12 @@ import { useSignInUpForm } from '@/auth/sign-in-up/hooks/useSignInUpForm'; import { SignInUpStep } from '@/auth/states/signInUpStepState'; import { authProvidersState } from '@/client-config/states/authProvidersState'; import styled from '@emotion/styled'; -import { useCallback, useEffect } from 'react'; import { useRecoilState } 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'; -import { useLocation } from 'react-router-dom'; -import { isDefined } from '~/utils/isDefined'; const StyledContentContainer = styled.div` margin-bottom: ${({ theme }) => theme.spacing(8)}; @@ -25,38 +22,7 @@ export const SignInUpWorkspaceScopeForm = () => { const { form } = useSignInUpForm(); const { handleResetPassword } = useHandleResetPassword(); - const { signInUpStep, continueWithEmail, continueWithCredentials } = - useSignInUp(form); - const location = useLocation(); - - const checkAuthProviders = useCallback(() => { - if ( - signInUpStep === SignInUpStep.Init && - !authProviders.google && - !authProviders.microsoft && - !authProviders.sso - ) { - return continueWithEmail(); - } - const searchParams = new URLSearchParams(location.search); - const email = searchParams.get('email'); - if (isDefined(email) && authProviders.password) { - return continueWithCredentials(); - } - }, [ - continueWithCredentials, - location.search, - authProviders.google, - authProviders.microsoft, - authProviders.password, - authProviders.sso, - continueWithEmail, - signInUpStep, - ]); - - useEffect(() => { - checkAuthProviders(); - }, [checkAuthProviders]); + const { signInUpStep } = useSignInUp(form); return ( <> diff --git a/packages/twenty-front/src/modules/auth/sign-in-up/components/SignInUpWorkspaceScopeFormEffect.tsx b/packages/twenty-front/src/modules/auth/sign-in-up/components/SignInUpWorkspaceScopeFormEffect.tsx new file mode 100644 index 000000000000..c3be61dfd8a2 --- /dev/null +++ b/packages/twenty-front/src/modules/auth/sign-in-up/components/SignInUpWorkspaceScopeFormEffect.tsx @@ -0,0 +1,48 @@ +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 { useCallback, useEffect } from 'react'; + +const searchParams = new URLSearchParams(window.location.search); +const email = searchParams.get('email'); + +export const SignInUpWorkspaceScopeFormEffect = () => { + const [authProviders] = useRecoilState(authProvidersState); + + const { form } = useSignInUpForm(); + + const { signInUpStep, continueWithEmail, continueWithCredentials } = + useSignInUp(form); + + const checkAuthProviders = useCallback(() => { + if ( + signInUpStep === SignInUpStep.Init && + !authProviders.google && + !authProviders.microsoft && + !authProviders.sso + ) { + return continueWithEmail(); + } + + if (isDefined(email) && authProviders.password) { + return continueWithCredentials(); + } + }, [ + signInUpStep, + authProviders.google, + authProviders.microsoft, + authProviders.sso, + authProviders.password, + continueWithEmail, + continueWithCredentials, + ]); + + useEffect(() => { + checkAuthProviders(); + }, [checkAuthProviders]); + + return <>; +}; diff --git a/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useSignInUp.tsx b/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useSignInUp.tsx index d76e06419123..4eec9f55f958 100644 --- a/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useSignInUp.tsx +++ b/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useSignInUp.tsx @@ -16,7 +16,7 @@ import { import { AppPath } from '@/types/AppPath'; import { useAuth } from '../../hooks/useAuth'; import { signInUpModeState } from '@/auth/states/signInUpModeState'; -import { SignInUpMode } from '@/auth/types/signInUpMode.type'; +import { SignInUpMode } from '@/auth/types/signInUpMode'; export const useSignInUp = (form: UseFormReturn
) => { const { enqueueSnackBar } = useSnackBar(); diff --git a/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useSignInUpForm.ts b/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useSignInUpForm.ts index 26cf789de9b9..93a6e16c2750 100644 --- a/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useSignInUpForm.ts +++ b/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useSignInUpForm.ts @@ -58,8 +58,10 @@ export const useSignInUpForm = () => { useEffect(() => { if (isDefined(prefilledEmail)) { form.setValue('email', prefilledEmail); - } else if (isDeveloperDefaultSignInPrefilled === true) { - form.setValue('email', 'tim@apple.dev'); + } + + if (isDeveloperDefaultSignInPrefilled === true) { + form.setValue('email', prefilledEmail ?? 'tim@apple.dev'); form.setValue('password', 'Applecar2025'); } }, [ @@ -68,5 +70,5 @@ export const useSignInUpForm = () => { prefilledEmail, location.search, ]); - return { form, validationSchema }; + return { form: form, validationSchema }; }; diff --git a/packages/twenty-front/src/modules/auth/states/signInUpModeState.ts b/packages/twenty-front/src/modules/auth/states/signInUpModeState.ts index b96a5451e211..98da3b608dfc 100644 --- a/packages/twenty-front/src/modules/auth/states/signInUpModeState.ts +++ b/packages/twenty-front/src/modules/auth/states/signInUpModeState.ts @@ -1,5 +1,5 @@ import { createState } from 'twenty-ui'; -import { SignInUpMode } from '@/auth/types/signInUpMode.type'; +import { SignInUpMode } from '@/auth/types/signInUpMode'; export const signInUpModeState = createState({ key: 'signInUpModeState', diff --git a/packages/twenty-front/src/modules/auth/types/signInUpMode.type.ts b/packages/twenty-front/src/modules/auth/types/signInUpMode.ts similarity index 100% rename from packages/twenty-front/src/modules/auth/types/signInUpMode.type.ts rename to packages/twenty-front/src/modules/auth/types/signInUpMode.ts diff --git a/packages/twenty-front/src/modules/settings/security/components/SettingsSSOIdentitiesProvidersListCard.tsx b/packages/twenty-front/src/modules/settings/security/components/SettingsSSOIdentitiesProvidersListCard.tsx index bcf0abef9732..13d748e851ee 100644 --- a/packages/twenty-front/src/modules/settings/security/components/SettingsSSOIdentitiesProvidersListCard.tsx +++ b/packages/twenty-front/src/modules/settings/security/components/SettingsSSOIdentitiesProvidersListCard.tsx @@ -14,7 +14,7 @@ import { useRecoilValue, useRecoilState } from 'recoil'; import { IconKey } from 'twenty-ui'; import { useListSsoIdentityProvidersByWorkspaceIdQuery } from '~/generated/graphql'; import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar'; -import { SSOIdentitiesProvidersState } from '@/settings/security/states/SSOIdentitiesProviders.state'; +import { SSOIdentitiesProvidersState } from '@/settings/security/states/SSOIdentitiesProvidersState'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; const StyledLink = styled(Link, { diff --git a/packages/twenty-front/src/modules/settings/security/components/SettingsSSOIdentitiesProvidersListCardWrapper.tsx b/packages/twenty-front/src/modules/settings/security/components/SettingsSSOIdentitiesProvidersListCardWrapper.tsx index f75f41f92e94..018a6fb92f10 100644 --- a/packages/twenty-front/src/modules/settings/security/components/SettingsSSOIdentitiesProvidersListCardWrapper.tsx +++ b/packages/twenty-front/src/modules/settings/security/components/SettingsSSOIdentitiesProvidersListCardWrapper.tsx @@ -6,7 +6,7 @@ import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath'; import { SettingsPath } from '@/types/SettingsPath'; import { SettingsListCard } from '@/settings/components/SettingsListCard'; import { useNavigate } from 'react-router-dom'; -import { SSOIdentitiesProvidersState } from '@/settings/security/states/SSOIdentitiesProviders.state'; +import { SSOIdentitiesProvidersState } from '@/settings/security/states/SSOIdentitiesProvidersState'; import { useRecoilValue } from 'recoil'; export const SettingsSSOIdentitiesProvidersListCardWrapper = () => { diff --git a/packages/twenty-front/src/modules/settings/security/components/SettingsSSOIdentityProviderRowRightContainer.tsx b/packages/twenty-front/src/modules/settings/security/components/SettingsSSOIdentityProviderRowRightContainer.tsx index eab5f84d6267..fb55040682c3 100644 --- a/packages/twenty-front/src/modules/settings/security/components/SettingsSSOIdentityProviderRowRightContainer.tsx +++ b/packages/twenty-front/src/modules/settings/security/components/SettingsSSOIdentityProviderRowRightContainer.tsx @@ -1,7 +1,7 @@ /* @license Enterprise */ import { SettingsSecuritySSORowDropdownMenu } from '@/settings/security/components/SettingsSecuritySSORowDropdownMenu'; -import { SSOIdentitiesProvidersState } from '@/settings/security/states/SSOIdentitiesProviders.state'; +import { SSOIdentitiesProvidersState } from '@/settings/security/states/SSOIdentitiesProvidersState'; import { getColorBySSOIdentityProviderStatus } from '@/settings/security/utils/getColorBySSOIdentityProviderStatus'; import { Status } from 'twenty-ui'; import styled from '@emotion/styled'; diff --git a/packages/twenty-front/src/modules/settings/security/components/SettingsSecurityOptionsList.tsx b/packages/twenty-front/src/modules/settings/security/components/SettingsSecurityOptionsList.tsx index dcdd5056f605..396cb7f72686 100644 --- a/packages/twenty-front/src/modules/settings/security/components/SettingsSecurityOptionsList.tsx +++ b/packages/twenty-front/src/modules/settings/security/components/SettingsSecurityOptionsList.tsx @@ -14,7 +14,7 @@ import { import { useUpdateWorkspaceMutation } from '~/generated/graphql'; import { AuthProviders } from '~/generated-metadata/graphql'; import { capitalize } from '~/utils/string/capitalize'; -import { SSOIdentitiesProvidersState } from '@/settings/security/states/SSOIdentitiesProviders.state'; +import { SSOIdentitiesProvidersState } from '@/settings/security/states/SSOIdentitiesProvidersState'; const StyledSettingsSecurityOptionsList = styled.div` display: flex; diff --git a/packages/twenty-front/src/modules/settings/security/components/SettingsSecuritySSORowDropdownMenu.tsx b/packages/twenty-front/src/modules/settings/security/components/SettingsSecuritySSORowDropdownMenu.tsx index d7de1d3b2e3f..01561fb2f405 100644 --- a/packages/twenty-front/src/modules/settings/security/components/SettingsSecuritySSORowDropdownMenu.tsx +++ b/packages/twenty-front/src/modules/settings/security/components/SettingsSecuritySSORowDropdownMenu.tsx @@ -10,7 +10,7 @@ import { import { useDeleteSSOIdentityProvider } from '@/settings/security/hooks/useDeleteSSOIdentityProvider'; import { useUpdateSSOIdentityProvider } from '@/settings/security/hooks/useUpdateSSOIdentityProvider'; -import { SSOIdentitiesProvidersState } from '@/settings/security/states/SSOIdentitiesProviders.state'; +import { SSOIdentitiesProvidersState } from '@/settings/security/states/SSOIdentitiesProvidersState'; import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; diff --git a/packages/twenty-front/src/modules/settings/security/hooks/useCreateSSOIdentityProvider.ts b/packages/twenty-front/src/modules/settings/security/hooks/useCreateSSOIdentityProvider.ts index b7dd56f1b13e..85736f96a693 100644 --- a/packages/twenty-front/src/modules/settings/security/hooks/useCreateSSOIdentityProvider.ts +++ b/packages/twenty-front/src/modules/settings/security/hooks/useCreateSSOIdentityProvider.ts @@ -1,6 +1,6 @@ /* @license Enterprise */ -import { SSOIdentitiesProvidersState } from '@/settings/security/states/SSOIdentitiesProviders.state'; +import { SSOIdentitiesProvidersState } from '@/settings/security/states/SSOIdentitiesProvidersState'; import { useSetRecoilState } from 'recoil'; import { CreateOidcIdentityProviderMutationVariables, diff --git a/packages/twenty-front/src/modules/settings/security/hooks/useDeleteSSOIdentityProvider.ts b/packages/twenty-front/src/modules/settings/security/hooks/useDeleteSSOIdentityProvider.ts index a140444631cb..71f72164d1ae 100644 --- a/packages/twenty-front/src/modules/settings/security/hooks/useDeleteSSOIdentityProvider.ts +++ b/packages/twenty-front/src/modules/settings/security/hooks/useDeleteSSOIdentityProvider.ts @@ -1,6 +1,6 @@ /* @license Enterprise */ -import { SSOIdentitiesProvidersState } from '@/settings/security/states/SSOIdentitiesProviders.state'; +import { SSOIdentitiesProvidersState } from '@/settings/security/states/SSOIdentitiesProvidersState'; import { useSetRecoilState } from 'recoil'; import { DeleteSsoIdentityProviderMutationVariables, diff --git a/packages/twenty-front/src/modules/settings/security/hooks/useUpdateSSOIdentityProvider.ts b/packages/twenty-front/src/modules/settings/security/hooks/useUpdateSSOIdentityProvider.ts index 07baaaae6a7a..5a3058e55e93 100644 --- a/packages/twenty-front/src/modules/settings/security/hooks/useUpdateSSOIdentityProvider.ts +++ b/packages/twenty-front/src/modules/settings/security/hooks/useUpdateSSOIdentityProvider.ts @@ -1,6 +1,6 @@ /* @license Enterprise */ -import { SSOIdentitiesProvidersState } from '@/settings/security/states/SSOIdentitiesProviders.state'; +import { SSOIdentitiesProvidersState } from '@/settings/security/states/SSOIdentitiesProvidersState'; import { useSetRecoilState } from 'recoil'; import { EditSsoIdentityProviderMutationVariables, diff --git a/packages/twenty-front/src/modules/settings/security/states/SSOIdentitiesProviders.state.ts b/packages/twenty-front/src/modules/settings/security/states/SSOIdentitiesProvidersState.ts similarity index 100% rename from packages/twenty-front/src/modules/settings/security/states/SSOIdentitiesProviders.state.ts rename to packages/twenty-front/src/modules/settings/security/states/SSOIdentitiesProvidersState.ts diff --git a/packages/twenty-front/src/modules/settings/security/types/AuthProviders.type.ts b/packages/twenty-front/src/modules/settings/security/types/AuthProviders.type.ts deleted file mode 100644 index 496229ef5d39..000000000000 --- a/packages/twenty-front/src/modules/settings/security/types/AuthProviders.type.ts +++ /dev/null @@ -1,4 +0,0 @@ -export type AuthProvidersKeys = - | 'isGoogleAuthEnabled' - | 'isMicrosoftAuthEnabled' - | 'isPasswordAuthEnabled'; diff --git a/packages/twenty-front/src/modules/ui/utilities/page-favicon/components/PageFavicon.tsx b/packages/twenty-front/src/modules/ui/utilities/page-favicon/components/PageFavicon.tsx new file mode 100644 index 000000000000..f9a2089046ae --- /dev/null +++ b/packages/twenty-front/src/modules/ui/utilities/page-favicon/components/PageFavicon.tsx @@ -0,0 +1,20 @@ +import { Helmet } from 'react-helmet-async'; +import { workspacePublicDataState } from '@/auth/states/workspacePublicDataState'; +import { useRecoilValue } from 'recoil'; +import { getImageAbsoluteURI } from '~/utils/image/getImageAbsoluteURI'; + +export const PageFavicon = () => { + const workspacePublicData = useRecoilValue(workspacePublicDataState); + + return ( + + {workspacePublicData?.logo && ( + + )} + + ); +}; diff --git a/packages/twenty-front/src/modules/workspace/components/WorkspaceProviderEffect.tsx b/packages/twenty-front/src/modules/workspace/components/WorkspaceProviderEffect.tsx index fe8d4f508a3e..271c68a66159 100644 --- a/packages/twenty-front/src/modules/workspace/components/WorkspaceProviderEffect.tsx +++ b/packages/twenty-front/src/modules/workspace/components/WorkspaceProviderEffect.tsx @@ -76,21 +76,5 @@ export const WorkspaceProviderEffect = () => { redirectToWorkspace, ]); - useEffect(() => { - try { - if (isDefined(workspacePublicData?.logo)) { - const link: HTMLLinkElement = - document.querySelector("link[rel*='icon']") || - document.createElement('link'); - link.rel = 'icon'; - link.href = workspacePublicData.logo; - document.getElementsByTagName('head')[0].appendChild(link); - } - } catch (err) { - // eslint-disable-next-line no-console - console.error(err); - } - }, [workspacePublicData]); - return <>; }; diff --git a/packages/twenty-front/src/pages/auth/Invite.tsx b/packages/twenty-front/src/pages/auth/Invite.tsx index 4687fee7b479..b0a9ce14ebdc 100644 --- a/packages/twenty-front/src/pages/auth/Invite.tsx +++ b/packages/twenty-front/src/pages/auth/Invite.tsx @@ -17,6 +17,7 @@ import { } from '~/generated/graphql'; import { isDefined } from '~/utils/isDefined'; import { currentUserState } from '@/auth/states/currentUserState'; +import { SignInUpWorkspaceScopeFormEffect } from '@/auth/sign-in-up/components/SignInUpWorkspaceScopeFormEffect'; const StyledContentContainer = styled.div` margin-bottom: ${({ theme }) => theme.spacing(8)}; @@ -93,7 +94,10 @@ export const Invite = () => { ) : ( - + <> + + + )} ); diff --git a/packages/twenty-front/src/pages/auth/SignInUp.tsx b/packages/twenty-front/src/pages/auth/SignInUp.tsx index bc3346fa2226..106c5425be98 100644 --- a/packages/twenty-front/src/pages/auth/SignInUp.tsx +++ b/packages/twenty-front/src/pages/auth/SignInUp.tsx @@ -17,6 +17,7 @@ import { isMultiWorkspaceEnabledState } from '@/client-config/states/isMultiWork import { useUrlManager } from '@/url-manager/hooks/useUrlManager'; import { useMemo } from 'react'; import { isDefined } from '~/utils/isDefined'; +import { SignInUpWorkspaceScopeFormEffect } from '@/auth/sign-in-up/components/SignInUpWorkspaceScopeFormEffect'; export const SignInUp = () => { const { form } = useSignInUpForm(); @@ -43,7 +44,12 @@ export const SignInUp = () => { isDefined(workspacePublicData) && (!isMultiWorkspaceEnabled || isTwentyWorkspaceSubdomain) ) { - return ; + return ( + <> + + + + ); } return ; diff --git a/packages/twenty-server/src/engine/core-modules/auth/auth.resolver.ts b/packages/twenty-server/src/engine/core-modules/auth/auth.resolver.ts index 78fd63ea312a..830f8ff5a089 100644 --- a/packages/twenty-server/src/engine/core-modules/auth/auth.resolver.ts +++ b/packages/twenty-server/src/engine/core-modules/auth/auth.resolver.ts @@ -38,7 +38,6 @@ import { } from 'src/engine/core-modules/auth/auth.exception'; import { OriginHeader } from 'src/engine/decorators/auth/origin-header.decorator'; import { AvailableWorkspaceOutput } from 'src/engine/core-modules/auth/dto/available-workspaces.output'; -import { workspaceValidator } from 'src/engine/core-modules/workspace/workspace.validate'; import { DomainManagerService } from 'src/engine/core-modules/domain-manager/service/domain-manager.service'; import { ChallengeInput } from './dto/challenge.input'; @@ -129,13 +128,7 @@ export class AuthResolver { targetWorkspaceSubdomain: this.domainManagerService.getWorkspaceSubdomainByOrigin(origin), fromSSO: false, - isAuthEnabled: workspaceValidator.isAuthEnabled( - 'password', - new AuthException( - 'Password auth is not enabled for this workspace', - AuthExceptionCode.OAUTH_ACCESS_DENIED, - ), - ), + authProvider: 'password', }); const loginToken = await this.loginTokenService.generateLoginToken( diff --git a/packages/twenty-server/src/engine/core-modules/auth/controllers/google-auth.controller.ts b/packages/twenty-server/src/engine/core-modules/auth/controllers/google-auth.controller.ts index df89aa55bedb..58bd8f2eaf1d 100644 --- a/packages/twenty-server/src/engine/core-modules/auth/controllers/google-auth.controller.ts +++ b/packages/twenty-server/src/engine/core-modules/auth/controllers/google-auth.controller.ts @@ -18,13 +18,9 @@ import { GoogleProviderEnabledGuard } from 'src/engine/core-modules/auth/guards/ import { AuthService } from 'src/engine/core-modules/auth/services/auth.service'; import { GoogleRequest } from 'src/engine/core-modules/auth/strategies/google.auth.strategy'; import { LoginTokenService } from 'src/engine/core-modules/auth/token/services/login-token.service'; -import { - AuthException, - AuthExceptionCode, -} from 'src/engine/core-modules/auth/auth.exception'; +import { AuthException } from 'src/engine/core-modules/auth/auth.exception'; import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service'; import { DomainManagerService } from 'src/engine/core-modules/domain-manager/service/domain-manager.service'; -import { workspaceValidator } from 'src/engine/core-modules/workspace/workspace.validate'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; @Controller('auth/google') @@ -70,13 +66,7 @@ export class GoogleAuthController { workspacePersonalInviteToken, targetWorkspaceSubdomain, fromSSO: true, - isAuthEnabled: workspaceValidator.isAuthEnabled( - 'google', - new AuthException( - 'Google auth is not enabled for this workspace', - AuthExceptionCode.OAUTH_ACCESS_DENIED, - ), - ), + isAuthEnabled: 'google', }; if ( diff --git a/packages/twenty-server/src/engine/core-modules/auth/controllers/microsoft-auth.controller.ts b/packages/twenty-server/src/engine/core-modules/auth/controllers/microsoft-auth.controller.ts index ecdecbd59928..5575e41f4165 100644 --- a/packages/twenty-server/src/engine/core-modules/auth/controllers/microsoft-auth.controller.ts +++ b/packages/twenty-server/src/engine/core-modules/auth/controllers/microsoft-auth.controller.ts @@ -15,12 +15,8 @@ import { MicrosoftProviderEnabledGuard } from 'src/engine/core-modules/auth/guar import { AuthService } from 'src/engine/core-modules/auth/services/auth.service'; import { MicrosoftRequest } from 'src/engine/core-modules/auth/strategies/microsoft.auth.strategy'; import { LoginTokenService } from 'src/engine/core-modules/auth/token/services/login-token.service'; -import { - AuthException, - AuthExceptionCode, -} from 'src/engine/core-modules/auth/auth.exception'; +import { AuthException } from 'src/engine/core-modules/auth/auth.exception'; import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service'; -import { workspaceValidator } from 'src/engine/core-modules/workspace/workspace.validate'; import { DomainManagerService } from 'src/engine/core-modules/domain-manager/service/domain-manager.service'; @Controller('auth/microsoft') @@ -66,13 +62,7 @@ export class MicrosoftAuthController { workspacePersonalInviteToken, targetWorkspaceSubdomain, fromSSO: true, - isAuthEnabled: workspaceValidator.isAuthEnabled( - 'microsoft', - new AuthException( - 'Microsoft auth is not enabled for this workspace', - AuthExceptionCode.OAUTH_ACCESS_DENIED, - ), - ), + authProvider: 'microsoft', }); const loginToken = await this.loginTokenService.generateLoginToken( diff --git a/packages/twenty-server/src/engine/core-modules/auth/services/auth.service.ts b/packages/twenty-server/src/engine/core-modules/auth/services/auth.service.ts index ea445d23314d..ec0788ac622a 100644 --- a/packages/twenty-server/src/engine/core-modules/auth/services/auth.service.ts +++ b/packages/twenty-server/src/engine/core-modules/auth/services/auth.service.ts @@ -45,9 +45,9 @@ import { UserWorkspaceService } from 'src/engine/core-modules/user-workspace/use import { WorkspaceInvitationService } from 'src/engine/core-modules/workspace-invitation/services/workspace-invitation.service'; import { AvailableWorkspaceOutput } from 'src/engine/core-modules/auth/dto/available-workspaces.output'; import { UserService } from 'src/engine/core-modules/user/services/user.service'; -import { workspaceValidator } from 'src/engine/core-modules/workspace/workspace.validate'; import { userValidator } from 'src/engine/core-modules/user/user.validate'; import { DomainManagerService } from 'src/engine/core-modules/domain-manager/service/domain-manager.service'; +import { WorkspaceAuthProvider } from 'src/engine/core-modules/workspace/types/workspace.type'; @Injectable() // eslint-disable-next-line @nx/workspace-inject-workspace-repository @@ -161,7 +161,7 @@ export class AuthService { lastName, picture, fromSSO, - isAuthEnabled, + authProvider, }: { email: string; password?: string; @@ -172,7 +172,7 @@ export class AuthService { picture?: string | null; fromSSO: boolean; targetWorkspaceSubdomain?: string; - isAuthEnabled?: ReturnType<(typeof workspaceValidator)['isAuthEnabled']>; + authProvider?: WorkspaceAuthProvider; }) { return await this.signInUpService.signInUp({ email, @@ -184,7 +184,7 @@ export class AuthService { targetWorkspaceSubdomain, picture, fromSSO, - isAuthEnabled, + authProvider, }); } @@ -201,7 +201,7 @@ export class AuthService { where: { email }, }); - userValidator.assertIsExist( + userValidator.assertIsDefinedOrThrow( userWithIdAndDefaultWorkspaceId, new AuthException('User not found', AuthExceptionCode.USER_NOT_FOUND), ); @@ -210,7 +210,7 @@ export class AuthService { workspaceId && userWithIdAndDefaultWorkspaceId.defaultWorkspaceId !== workspaceId ) { - await this.userService.saveDefaultWorkspace( + await this.userService.saveDefaultWorkspaceIfUserHasAccessOrThrow( userWithIdAndDefaultWorkspaceId.id, workspaceId, ); @@ -223,7 +223,7 @@ export class AuthService { relations: ['defaultWorkspace', 'workspaces', 'workspaces.workspace'], }); - userValidator.assertIsExist( + userValidator.assertIsDefinedOrThrow( user, new AuthException('User not found', AuthExceptionCode.USER_NOT_FOUND), ); @@ -254,7 +254,7 @@ export class AuthService { email, }); - if (userValidator.isExist(user)) { + if (userValidator.isDefined(user)) { return { exists: true, availableWorkspaces: await this.findAvailableWorkspacesByEmail(email), @@ -460,7 +460,7 @@ export class AuthService { ], }); - userValidator.assertIsExist( + userValidator.assertIsDefinedOrThrow( user, new AuthException('User not found', AuthExceptionCode.USER_NOT_FOUND), ); diff --git a/packages/twenty-server/src/engine/core-modules/auth/services/sign-in-up.service.ts b/packages/twenty-server/src/engine/core-modules/auth/services/sign-in-up.service.ts index 5e6eff5e4cec..8ca5c2329caa 100644 --- a/packages/twenty-server/src/engine/core-modules/auth/services/sign-in-up.service.ts +++ b/packages/twenty-server/src/engine/core-modules/auth/services/sign-in-up.service.ts @@ -32,6 +32,7 @@ import { } from 'src/engine/core-modules/workspace/workspace.entity'; import { workspaceValidator } from 'src/engine/core-modules/workspace/workspace.validate'; import { getImageBufferFromUrl } from 'src/utils/image'; +import { WorkspaceAuthProvider } from 'src/engine/core-modules/workspace/types/workspace.type'; export type SignInUpServiceInput = { email: string; @@ -43,7 +44,7 @@ export type SignInUpServiceInput = { picture?: string | null; fromSSO: boolean; targetWorkspaceSubdomain?: string; - isAuthEnabled?: ReturnType<(typeof workspaceValidator)['isAuthEnabled']>; + authProvider?: WorkspaceAuthProvider; }; @Injectable() @@ -73,7 +74,7 @@ export class SignInUpService { picture, fromSSO, targetWorkspaceSubdomain, - isAuthEnabled, + authProvider, }: SignInUpServiceInput) { if (!firstName) firstName = ''; if (!lastName) lastName = ''; @@ -154,7 +155,7 @@ export class SignInUpService { lastName, picture, existingUser, - isAuthEnabled, + authProvider, }); await this.workspaceInvitationService.invalidateWorkspaceInvitation( @@ -187,7 +188,7 @@ export class SignInUpService { lastName, picture, existingUser, - isAuthEnabled, + authProvider, }: { email: string; passwordHash: string | undefined; @@ -196,7 +197,7 @@ export class SignInUpService { lastName: string; picture: SignInUpServiceInput['picture']; existingUser: User | null; - isAuthEnabled?: ReturnType<(typeof workspaceValidator)['isAuthEnabled']>; + authProvider?: WorkspaceAuthProvider; }) { const isNewUser = !isDefined(existingUser); let user = existingUser; @@ -217,8 +218,16 @@ export class SignInUpService { ), ); - if (isAuthEnabled) - workspaceValidator.validateAuth(isAuthEnabled, workspace); + if (authProvider) { + workspaceValidator.isAuthEnabledOrThrow( + authProvider, + workspace, + new AuthException( + `${authProvider} auth is not enabled for this workspace`, + AuthExceptionCode.OAUTH_ACCESS_DENIED, + ), + ); + } if (isNewUser) { const imagePath = await this.uploadPicture(picture, workspace.id); @@ -236,7 +245,7 @@ export class SignInUpService { user = await this.userRepository.save(userToCreate); } - userValidator.assertIsExist( + userValidator.assertIsDefinedOrThrow( user, new AuthException( 'User not found', diff --git a/packages/twenty-server/src/engine/core-modules/auth/services/switch-workspace.service.spec.ts b/packages/twenty-server/src/engine/core-modules/auth/services/switch-workspace.service.spec.ts index 16ba3e80f1f4..56cf23c0d134 100644 --- a/packages/twenty-server/src/engine/core-modules/auth/services/switch-workspace.service.spec.ts +++ b/packages/twenty-server/src/engine/core-modules/auth/services/switch-workspace.service.spec.ts @@ -47,7 +47,7 @@ describe('SwitchWorkspaceService', () => { { provide: UserService, useValue: { - saveDefaultWorkspace: jest.fn(), + saveDefaultWorkspaceIfUserHasAccessOrThrow: jest.fn(), }, }, ], @@ -211,10 +211,9 @@ describe('SwitchWorkspaceService', () => { refreshToken: mockRefreshToken, }, }); - expect(userService.saveDefaultWorkspace).toHaveBeenCalledWith( - mockUser.id, - mockWorkspace.id, - ); + expect( + userService.saveDefaultWorkspaceIfUserHasAccessOrThrow, + ).toHaveBeenCalledWith(mockUser.id, mockWorkspace.id); expect(accessTokenService.generateAccessToken).toHaveBeenCalledWith( mockUser.id, mockWorkspace.id, diff --git a/packages/twenty-server/src/engine/core-modules/auth/services/switch-workspace.service.ts b/packages/twenty-server/src/engine/core-modules/auth/services/switch-workspace.service.ts index edbef71b31b1..6a88dfb9a87e 100644 --- a/packages/twenty-server/src/engine/core-modules/auth/services/switch-workspace.service.ts +++ b/packages/twenty-server/src/engine/core-modules/auth/services/switch-workspace.service.ts @@ -78,7 +78,10 @@ export class SwitchWorkspaceService { user: User, workspace: Workspace, ): Promise { - await this.userService.saveDefaultWorkspace(user.id, workspace.id); + await this.userService.saveDefaultWorkspaceIfUserHasAccessOrThrow( + user.id, + workspace.id, + ); const token = await this.accessTokenService.generateAccessToken( user.id, diff --git a/packages/twenty-server/src/engine/core-modules/user/services/user.service.ts b/packages/twenty-server/src/engine/core-modules/user/services/user.service.ts index ec110a2b7421..eef63bcd4033 100644 --- a/packages/twenty-server/src/engine/core-modules/user/services/user.service.ts +++ b/packages/twenty-server/src/engine/core-modules/user/services/user.service.ts @@ -135,7 +135,7 @@ export class UserService extends TypeOrmQueryService { return user; } - async saveDefaultWorkspace(userId: string, workspaceId: string) { + async hasUserAccessToWorkspaceOrThrow(userId: string, workspaceId: string) { const user = await this.userRepository.findOne({ where: { id: userId, @@ -146,13 +146,20 @@ export class UserService extends TypeOrmQueryService { relations: ['workspaces'], }); - userValidator.assertIsExist( + userValidator.assertIsDefinedOrThrow( user, new AuthException( 'User does not have access to this workspace', AuthExceptionCode.FORBIDDEN_EXCEPTION, ), ); + } + + async saveDefaultWorkspaceIfUserHasAccessOrThrow( + userId: string, + workspaceId: string, + ) { + await this.hasUserAccessToWorkspaceOrThrow(userId, workspaceId); return await this.userRepository.save({ id: userId, diff --git a/packages/twenty-server/src/engine/core-modules/user/user.resolver.ts b/packages/twenty-server/src/engine/core-modules/user/user.resolver.ts index 1aa3bf5f9e5a..6594293d864b 100644 --- a/packages/twenty-server/src/engine/core-modules/user/user.resolver.ts +++ b/packages/twenty-server/src/engine/core-modules/user/user.resolver.ts @@ -77,7 +77,10 @@ export class UserResolver { this.environmentService.get('IS_MULTIWORKSPACE_ENABLED') && workspaceId ) { - await this.userService.saveDefaultWorkspace(userId, workspaceId); + await this.userService.saveDefaultWorkspaceIfUserHasAccessOrThrow( + userId, + workspaceId, + ); } const user = await this.userRepository.findOne({ @@ -87,7 +90,7 @@ export class UserResolver { relations: ['defaultWorkspace', 'workspaces', 'workspaces.workspace'], }); - userValidator.assertIsExist( + userValidator.assertIsDefinedOrThrow( user, new AuthException('User not found', AuthExceptionCode.USER_NOT_FOUND), ); diff --git a/packages/twenty-server/src/engine/core-modules/user/user.validate.ts b/packages/twenty-server/src/engine/core-modules/user/user.validate.ts index 4fb4cec3b553..42a4b682d0fb 100644 --- a/packages/twenty-server/src/engine/core-modules/user/user.validate.ts +++ b/packages/twenty-server/src/engine/core-modules/user/user.validate.ts @@ -1,34 +1,24 @@ import { User } from 'src/engine/core-modules/user/user.entity'; import { CustomException } from 'src/utils/custom-exception'; +import { isDefined } from 'src/utils/is-defined'; -const assertIsExist = ( +const assertIsDefinedOrThrow = ( user: User | undefined | null, exceptionToThrow: CustomException, ): asserts user is User => { - if (!user) { + if (!isDefined(user)) { throw exceptionToThrow; } }; -const isExist = (user: User | undefined | null): user is User => { - return !!user; -}; - -const assertHasDefaultWorkspace = ( - user: User, - exceptionToThrow?: CustomException, -): asserts user is User & { defaultWorkspaceId: string } => { - if (!user.defaultWorkspaceId) { - throw exceptionToThrow; - } +const isUserDefined = (user: User | undefined | null): user is User => { + return isDefined(user); }; export const userValidator: { - assertIsExist: typeof assertIsExist; - assertHasDefaultWorkspace: typeof assertHasDefaultWorkspace; - isExist: typeof isExist; + assertIsDefinedOrThrow: typeof assertIsDefinedOrThrow; + isDefined: typeof isUserDefined; } = { - assertIsExist, - assertHasDefaultWorkspace, - isExist, + assertIsDefinedOrThrow, + isDefined: isUserDefined, }; diff --git a/packages/twenty-server/src/engine/core-modules/workspace/types/workspace.type.ts b/packages/twenty-server/src/engine/core-modules/workspace/types/workspace.type.ts new file mode 100644 index 000000000000..259fc76f2c43 --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/workspace/types/workspace.type.ts @@ -0,0 +1 @@ +export type WorkspaceAuthProvider = 'google' | 'microsoft' | 'password'; diff --git a/packages/twenty-server/src/engine/core-modules/workspace/workspace.validate.ts b/packages/twenty-server/src/engine/core-modules/workspace/workspace.validate.ts index 72bebb48ecb2..33e666a000ec 100644 --- a/packages/twenty-server/src/engine/core-modules/workspace/workspace.validate.ts +++ b/packages/twenty-server/src/engine/core-modules/workspace/workspace.validate.ts @@ -3,8 +3,8 @@ import { WorkspaceActivationStatus, } from 'src/engine/core-modules/workspace/workspace.entity'; import { CustomException } from 'src/utils/custom-exception'; - -type WorkspaceAuthProvider = 'google' | 'microsoft' | 'password'; +import { AuthException } from 'src/engine/core-modules/auth/auth.exception'; +import { WorkspaceAuthProvider } from 'src/engine/core-modules/workspace/types/workspace.type'; const assertIsExist = ( workspace: Workspace | undefined | null, @@ -25,40 +25,24 @@ const assertIsActive = ( throw exceptionToThrow; }; -type IsAuthEnabled =

( - provider: P, - exceptionToThrow: CustomException, -) => ( +const isAuthEnabledOrThrow = ( + provider: WorkspaceAuthProvider, workspace: Workspace, - exceptionToThrowCustom?: CustomException, -) => boolean; - -const isAuthEnabled: IsAuthEnabled = (provider, exceptionToThrow) => { - return (workspace, exceptionToThrowCustom = exceptionToThrow) => { - if (provider === 'google' && workspace.isGoogleAuthEnabled) return true; - if (provider === 'microsoft' && workspace.isMicrosoftAuthEnabled) - return true; - if (provider === 'password' && workspace.isPasswordAuthEnabled) return true; + exceptionToThrowCustom: AuthException, +) => { + if (provider === 'google' && workspace.isGoogleAuthEnabled) return true; + if (provider === 'microsoft' && workspace.isMicrosoftAuthEnabled) return true; + if (provider === 'password' && workspace.isPasswordAuthEnabled) return true; - if (exceptionToThrowCustom) { - throw exceptionToThrowCustom; - } - - return false; - }; + throw exceptionToThrowCustom; }; -const validateAuth = (fn: ReturnType, workspace: Workspace) => - fn(workspace); - export const workspaceValidator: { assertIsExist: typeof assertIsExist; assertIsActive: typeof assertIsActive; - isAuthEnabled: IsAuthEnabled; - validateAuth: typeof validateAuth; + isAuthEnabledOrThrow: typeof isAuthEnabledOrThrow; } = { assertIsExist: assertIsExist, assertIsActive: assertIsActive, - isAuthEnabled, - validateAuth, + isAuthEnabledOrThrow: isAuthEnabledOrThrow, };