Introduce SSO bypass permission. (#15417)

Closes [Core Issue
#1772](https://github.com/twentyhq/core-team-issues/issues/1772).

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> Introduces SSO bypass with a new permission flag and workspace-level
provider toggles, enabling permitted users to log in via
Google/Microsoft/Password when SSO-only, with backend enforcement and
frontend UI/hooks/queries.
> 
> - **Backend**:
> - **Permission & Enforcement**: Add `PermissionFlagType.SSO_BYPASS`;
update `AuthService` to allow login via non-SSO providers when workspace
bypass is enabled and user has `SSO_BYPASS`.
> - **Workspace Model**: Add `isGoogleAuthBypassEnabled`,
`isMicrosoftAuthBypassEnabled`, `isPasswordAuthBypassEnabled`
(migration, entity, update input, service validation).
> - **Public API**: Extend `PublicWorkspaceDataOutput` with
`authBypassProviders`; resolver computes it; permissions defaults
include `SSO_BYPASS`.
> - **Frontend**:
> - **GraphQL/State**: Generate new types/fields; add
`authBypassProviders` to `GetPublicWorkspaceDataByDomain`; new states
`workspaceAuthBypassProvidersState`, `workspaceBypassModeState`.
> - **Auth UI/Logic**: Add `useWorkspaceBypass`; update sign-in form and
footer to offer "Bypass SSO" and use merged providers when enabled;
remove auto-redirect when single SSO.
> - **Settings**: Add Security section to toggle bypass methods per
provider; conditionally show Change Password via `useCanChangePassword`.
> - **Tests/Mocks**: Update mocks and tests to include bypass
flags/providers.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
8c393b2bad387fb6e8b8f40027f8637dd6e85723. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
This commit is contained in:
Abdullah.
2025-11-03 15:40:09 +05:00
committed by GitHub
parent 604b3e50de
commit 5b2950c43a
34 changed files with 803 additions and 58 deletions
@@ -4,12 +4,14 @@ import { currentUserWorkspaceState } from '@/auth/states/currentUserWorkspaceSta
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
import { currentWorkspaceMembersState } from '@/auth/states/currentWorkspaceMembersState';
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { authProvidersState } from '@/client-config/states/authProvidersState';
import { useIsCurrentLocationOnAWorkspace } from '@/domain-manager/hooks/useIsCurrentLocationOnAWorkspace';
import { useLastAuthenticatedWorkspaceDomain } from '@/domain-manager/hooks/useLastAuthenticatedWorkspaceDomain';
import { useInitializeFormatPreferences } from '@/localization/hooks/useInitializeFormatPreferences';
import { coreViewsState } from '@/views/states/coreViewState';
import { workspaceAuthBypassProvidersState } from '@/workspace/states/workspaceAuthBypassProvidersState';
import { useCallback } from 'react';
import { useSetRecoilState } from 'recoil';
import { useRecoilValue, useSetRecoilState } from 'recoil';
import { SOURCE_LOCALE, type APP_LOCALES } from 'twenty-shared/translations';
import { type ObjectPermissions } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
@@ -36,6 +38,10 @@ export const useLoadCurrentUser = () => {
const setCurrentWorkspace = useSetRecoilState(currentWorkspaceState);
const { initializeFormatPreferences } = useInitializeFormatPreferences();
const setCoreViews = useSetRecoilState(coreViewsState);
const setWorkspaceAuthBypassProviders = useSetRecoilState(
workspaceAuthBypassProvidersState,
);
const authProviders = useRecoilValue(authProvidersState);
const { isOnAWorkspace } = useIsCurrentLocationOnAWorkspace();
@@ -103,6 +109,16 @@ export const useLoadCurrentUser = () => {
setCurrentWorkspace(workspace);
if (isDefined(workspace)) {
setWorkspaceAuthBypassProviders({
google: authProviders.google && workspace.isGoogleAuthBypassEnabled,
microsoft:
authProviders.microsoft && workspace.isMicrosoftAuthBypassEnabled,
password:
authProviders.password && workspace.isPasswordAuthBypassEnabled,
});
}
if (isDefined(workspace) && isOnAWorkspace) {
setLastAuthenticateWorkspaceDomain({
workspaceId: workspace.id,
@@ -132,6 +148,8 @@ export const useLoadCurrentUser = () => {
initializeFormatPreferences,
setLastAuthenticateWorkspaceDomain,
setCoreViews,
authProviders,
setWorkspaceAuthBypassProviders,
]);
return {