5b2950c43a
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>
68 lines
2.7 KiB
TypeScript
68 lines
2.7 KiB
TypeScript
import { workspacePublicDataState } from '@/auth/states/workspacePublicDataState';
|
|
import { clientConfigApiStatusState } from '@/client-config/states/clientConfigApiStatusState';
|
|
import { isMultiWorkspaceEnabledState } from '@/client-config/states/isMultiWorkspaceEnabledState';
|
|
import { useIsCurrentLocationOnDefaultDomain } from '@/domain-manager/hooks/useIsCurrentLocationOnDefaultDomain';
|
|
import { useOrigin } from '@/domain-manager/hooks/useOrigin';
|
|
import { useRedirectToDefaultDomain } from '@/domain-manager/hooks/useRedirectToDefaultDomain';
|
|
import { workspaceAuthProvidersState } from '@/workspace/states/workspaceAuthProvidersState';
|
|
import { workspaceAuthBypassProvidersState } from '@/workspace/states/workspaceAuthBypassProvidersState';
|
|
import { useRecoilValue, useSetRecoilState } from 'recoil';
|
|
import { isDefined } from 'twenty-shared/utils';
|
|
import { useGetPublicWorkspaceDataByDomainQuery } from '~/generated-metadata/graphql';
|
|
|
|
export const useGetPublicWorkspaceDataByDomain = () => {
|
|
const { isDefaultDomain } = useIsCurrentLocationOnDefaultDomain();
|
|
const isMultiWorkspaceEnabled = useRecoilValue(isMultiWorkspaceEnabledState);
|
|
const { origin } = useOrigin();
|
|
const setWorkspaceAuthProviders = useSetRecoilState(
|
|
workspaceAuthProvidersState,
|
|
);
|
|
const setWorkspaceAuthBypassProviders = useSetRecoilState(
|
|
workspaceAuthBypassProvidersState,
|
|
);
|
|
const workspacePublicData = useRecoilValue(workspacePublicDataState);
|
|
const { redirectToDefaultDomain } = useRedirectToDefaultDomain();
|
|
const setWorkspacePublicDataState = useSetRecoilState(
|
|
workspacePublicDataState,
|
|
);
|
|
const clientConfigApiStatus = useRecoilValue(clientConfigApiStatusState);
|
|
|
|
const { loading, data, error } = useGetPublicWorkspaceDataByDomainQuery({
|
|
variables: {
|
|
origin,
|
|
},
|
|
skip:
|
|
!clientConfigApiStatus.isSaved ||
|
|
(isMultiWorkspaceEnabled && isDefaultDomain) ||
|
|
isDefined(workspacePublicData),
|
|
onCompleted: (data) => {
|
|
setWorkspaceAuthProviders(
|
|
data.getPublicWorkspaceDataByDomain.authProviders,
|
|
);
|
|
setWorkspaceAuthBypassProviders(
|
|
data.getPublicWorkspaceDataByDomain.authBypassProviders ?? null,
|
|
);
|
|
setWorkspacePublicDataState(data.getPublicWorkspaceDataByDomain);
|
|
},
|
|
onError: (error) => {
|
|
// Only redirect to default domain if it's a workspace not found error
|
|
const isWorkspaceNotFoundError = error.graphQLErrors?.some(
|
|
(graphQLError) => graphQLError.extensions?.code === 'NOT_FOUND',
|
|
);
|
|
|
|
if (isWorkspaceNotFoundError) {
|
|
redirectToDefaultDomain();
|
|
} else {
|
|
// eslint-disable-next-line no-console
|
|
console.error(error);
|
|
}
|
|
},
|
|
});
|
|
|
|
return {
|
|
loading,
|
|
data: data?.getPublicWorkspaceDataByDomain,
|
|
error,
|
|
};
|
|
};
|