From 3541ed448283dda10fa5d42c8e5b564fd3f2d58f Mon Sep 17 00:00:00 2001 From: jawad khan Date: Fri, 22 May 2026 15:41:56 +0500 Subject: [PATCH] fix(settings): hide profile 2FA and delete account when AUTH_TYPE=SSO (#15) * fix(settings): hide profile 2FA and delete account when AUTH_TYPE=SSO * fix: Added test case --- .../ProfileTwoFactorAuthenticationSection.tsx | 59 +++++++++++++++++++ ...ileTwoFactorAuthenticationSection.test.tsx | 44 ++++++++++++++ .../src/pages/settings/SettingsProfile.tsx | 48 ++++----------- 3 files changed, 113 insertions(+), 38 deletions(-) create mode 100644 packages/twenty-front/src/modules/settings/profile/components/ProfileTwoFactorAuthenticationSection.tsx create mode 100644 packages/twenty-front/src/modules/settings/profile/components/__tests__/ProfileTwoFactorAuthenticationSection.test.tsx diff --git a/packages/twenty-front/src/modules/settings/profile/components/ProfileTwoFactorAuthenticationSection.tsx b/packages/twenty-front/src/modules/settings/profile/components/ProfileTwoFactorAuthenticationSection.tsx new file mode 100644 index 0000000000000..93f5cbe346c15 --- /dev/null +++ b/packages/twenty-front/src/modules/settings/profile/components/ProfileTwoFactorAuthenticationSection.tsx @@ -0,0 +1,59 @@ +import { useIsSsoEnabled } from '@/auth/hooks/useIsSsoEnabled'; +import { SettingsCard } from '@/settings/components/SettingsCard'; +import { useCurrentUserWorkspaceTwoFactorAuthentication } from '@/settings/two-factor-authentication/hooks/useCurrentUserWorkspaceTwoFactorAuthentication'; +import { useLingui } from '@lingui/react/macro'; +import { SettingsPath } from 'twenty-shared/types'; +import { getSettingsPath } from 'twenty-shared/utils'; +import { H2Title, IconShield, Status } from 'twenty-ui/display'; +import { Section } from 'twenty-ui/layout'; +import { UndecoratedLink } from 'twenty-ui/navigation'; + +// Under SSO the IdP owns MFA — Twenty's local TOTP setup is a dead control. +// Outer component bails out before inner hooks run. +export const ProfileTwoFactorAuthenticationSection = () => { + const isSsoEnabled = useIsSsoEnabled(); + + if (isSsoEnabled) { + return null; + } + + return ; +}; + +const ProfileTwoFactorAuthenticationSectionInner = () => { + const { t } = useLingui(); + + const { currentUserWorkspaceTwoFactorAuthenticationMethods } = + useCurrentUserWorkspaceTwoFactorAuthentication(); + + const has2FAMethod = + currentUserWorkspaceTwoFactorAuthenticationMethods['TOTP']?.status === + 'VERIFIED'; + + return ( +
+ + + } + Status={ + has2FAMethod ? ( + + ) : ( + + ) + } + /> + +
+ ); +}; diff --git a/packages/twenty-front/src/modules/settings/profile/components/__tests__/ProfileTwoFactorAuthenticationSection.test.tsx b/packages/twenty-front/src/modules/settings/profile/components/__tests__/ProfileTwoFactorAuthenticationSection.test.tsx new file mode 100644 index 0000000000000..eec565b6a87b0 --- /dev/null +++ b/packages/twenty-front/src/modules/settings/profile/components/__tests__/ProfileTwoFactorAuthenticationSection.test.tsx @@ -0,0 +1,44 @@ +import { render } from '@testing-library/react'; + +import { ProfileTwoFactorAuthenticationSection } from '@/settings/profile/components/ProfileTwoFactorAuthenticationSection'; + +jest.mock('@/auth/hooks/useIsSsoEnabled', () => ({ + useIsSsoEnabled: jest.fn(), +})); + +jest.mock( + '@/settings/two-factor-authentication/hooks/useCurrentUserWorkspaceTwoFactorAuthentication', + () => ({ + useCurrentUserWorkspaceTwoFactorAuthentication: jest.fn(), + }), +); + +const useIsSsoEnabledMock: jest.Mock = jest.requireMock( + '@/auth/hooks/useIsSsoEnabled', +).useIsSsoEnabled; + +const useCurrentUserWorkspaceTwoFactorAuthenticationMock: jest.Mock = + jest.requireMock( + '@/settings/two-factor-authentication/hooks/useCurrentUserWorkspaceTwoFactorAuthentication', + ).useCurrentUserWorkspaceTwoFactorAuthentication; + +describe('ProfileTwoFactorAuthenticationSection', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + // Under SSO the IdP owns MFA — the outer component returns null before + // useCurrentUserWorkspaceTwoFactorAuthentication runs. The SSO-off path + // defers to the inner component which needs providers — out of scope here. + it('returns null when SSO is enabled without invoking inner hooks', () => { + useIsSsoEnabledMock.mockReturnValue(true); + + const { container } = render(); + + expect(container.firstChild).toBeNull(); + expect(useIsSsoEnabledMock).toHaveBeenCalled(); + expect( + useCurrentUserWorkspaceTwoFactorAuthenticationMock, + ).not.toHaveBeenCalled(); + }); +}); diff --git a/packages/twenty-front/src/pages/settings/SettingsProfile.tsx b/packages/twenty-front/src/pages/settings/SettingsProfile.tsx index 075064b719440..43da111bda325 100644 --- a/packages/twenty-front/src/pages/settings/SettingsProfile.tsx +++ b/packages/twenty-front/src/pages/settings/SettingsProfile.tsx @@ -1,32 +1,25 @@ import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; -import { SettingsCard } from '@/settings/components/SettingsCard'; +import { useIsSsoEnabled } from '@/auth/hooks/useIsSsoEnabled'; import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer'; import { SetOrChangePassword } from '@/settings/profile/components/SetOrChangePassword'; import { DeleteAccount } from '@/settings/profile/components/DeleteAccount'; import { EmailField } from '@/settings/profile/components/EmailField'; import { NameFields } from '@/settings/profile/components/NameFields'; +import { ProfileTwoFactorAuthenticationSection } from '@/settings/profile/components/ProfileTwoFactorAuthenticationSection'; import { WorkspaceMemberPictureUploader } from '@/settings/workspace-member/components/WorkspaceMemberPictureUploader'; import { useCanChangePassword } from '@/settings/profile/hooks/useCanChangePassword'; -import { useCurrentUserWorkspaceTwoFactorAuthentication } from '@/settings/two-factor-authentication/hooks/useCurrentUserWorkspaceTwoFactorAuthentication'; import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer'; import { Trans, useLingui } from '@lingui/react/macro'; import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; import { SettingsPath } from 'twenty-shared/types'; import { getSettingsPath } from 'twenty-shared/utils'; -import { H2Title, IconShield, Status } from 'twenty-ui/display'; +import { H2Title } from 'twenty-ui/display'; import { Section } from 'twenty-ui/layout'; -import { UndecoratedLink } from 'twenty-ui/navigation'; export const SettingsProfile = () => { const { t } = useLingui(); const currentWorkspaceMember = useAtomStateValue(currentWorkspaceMemberState); - - const { currentUserWorkspaceTwoFactorAuthenticationMethods } = - useCurrentUserWorkspaceTwoFactorAuthentication(); - - const has2FAMethod = - currentUserWorkspaceTwoFactorAuthenticationMethods['TOTP']?.status === - 'VERIFIED'; + const isSsoEnabled = useIsSsoEnabled(); const { canChangePassword } = useCanChangePassword(); @@ -66,38 +59,17 @@ export const SettingsProfile = () => { /> -
- - - } - Status={ - has2FAMethod ? ( - - ) : ( - - ) - } - /> - -
+ {canChangePassword && (
)} -
- -
+ {!isSsoEnabled && ( +
+ +
+ )} );