feat(): enable custom domain usage (#9911)
# Content - Introduce the `workspaceUrls` property. It contains two sub-properties: `customUrl, subdomainUrl`. These endpoints are used to access the workspace. Even if the `workspaceUrls` is invalid for multiple reasons, the `subdomainUrl` remains valid. - Introduce `ResolveField` workspaceEndpoints to avoid unnecessary URL computation on the frontend part. - Add a `forceSubdomainUrl` to avoid custom URL using a query parameter
This commit is contained in:
@ -5,7 +5,10 @@ export const IMPERSONATE = gql`
|
||||
mutation Impersonate($userId: String!, $workspaceId: String!) {
|
||||
impersonate(userId: $userId, workspaceId: $workspaceId) {
|
||||
workspace {
|
||||
subdomain
|
||||
workspaceUrls {
|
||||
subdomainUrl
|
||||
customUrl
|
||||
}
|
||||
id
|
||||
}
|
||||
loginToken {
|
||||
|
||||
@ -22,7 +22,10 @@ export const SIGN_UP = gql`
|
||||
}
|
||||
workspace {
|
||||
id
|
||||
subdomain
|
||||
workspaceUrls {
|
||||
subdomainUrl
|
||||
customUrl
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,8 +9,10 @@ export const CHECK_USER_EXISTS = gql`
|
||||
availableWorkspaces {
|
||||
id
|
||||
displayName
|
||||
subdomain
|
||||
hostname
|
||||
workspaceUrls {
|
||||
subdomainUrl
|
||||
customUrl
|
||||
}
|
||||
logo
|
||||
sso {
|
||||
type
|
||||
|
||||
@ -1,13 +1,15 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
export const GET_PUBLIC_WORKSPACE_DATA_BY_SUBDOMAIN = gql`
|
||||
query GetPublicWorkspaceDataBySubdomain {
|
||||
getPublicWorkspaceDataBySubdomain {
|
||||
export const GET_PUBLIC_WORKSPACE_DATA_BY_DOMAIN = gql`
|
||||
query GetPublicWorkspaceDataByDomain {
|
||||
getPublicWorkspaceDataByDomain {
|
||||
id
|
||||
logo
|
||||
displayName
|
||||
subdomain
|
||||
hostname
|
||||
workspaceUrls {
|
||||
subdomainUrl
|
||||
customUrl
|
||||
}
|
||||
authProviders {
|
||||
sso {
|
||||
id
|
||||
@ -53,7 +53,7 @@ import { BillingCheckoutSession } from '@/auth/types/billingCheckoutSession.type
|
||||
import { captchaState } from '@/client-config/states/captchaState';
|
||||
import { isEmailVerificationRequiredState } from '@/client-config/states/isEmailVerificationRequiredState';
|
||||
import { isMultiWorkspaceEnabledState } from '@/client-config/states/isMultiWorkspaceEnabledState';
|
||||
import { useIsCurrentLocationOnAWorkspaceSubdomain } from '@/domain-manager/hooks/useIsCurrentLocationOnAWorkspaceSubdomain';
|
||||
import { useIsCurrentLocationOnAWorkspace } from '@/domain-manager/hooks/useIsCurrentLocationOnAWorkspace';
|
||||
import { useLastAuthenticatedWorkspaceDomain } from '@/domain-manager/hooks/useLastAuthenticatedWorkspaceDomain';
|
||||
import { useRedirect } from '@/domain-manager/hooks/useRedirect';
|
||||
import { useRedirectToWorkspaceDomain } from '@/domain-manager/hooks/useRedirectToWorkspaceDomain';
|
||||
@ -62,6 +62,7 @@ import { isAppWaitingForFreshObjectMetadataState } from '@/object-metadata/state
|
||||
import { workspaceAuthProvidersState } from '@/workspace/states/workspaceAuthProvidersState';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
import { dynamicActivate } from '~/utils/i18n/dynamicActivate';
|
||||
import { getWorkspaceUrl } from '~/utils/getWorkspaceUrl';
|
||||
|
||||
export const useAuth = () => {
|
||||
const setTokenPair = useSetRecoilState(tokenPairState);
|
||||
@ -96,8 +97,7 @@ export const useAuth = () => {
|
||||
useGetLoginTokenFromEmailVerificationTokenMutation();
|
||||
const [getCurrentUser] = useGetCurrentUserLazyQuery();
|
||||
|
||||
const { isOnAWorkspaceSubdomain } =
|
||||
useIsCurrentLocationOnAWorkspaceSubdomain();
|
||||
const { isOnAWorkspace } = useIsCurrentLocationOnAWorkspace();
|
||||
|
||||
const workspacePublicData = useRecoilValue(workspacePublicDataState);
|
||||
|
||||
@ -289,10 +289,10 @@ export const useAuth = () => {
|
||||
|
||||
setCurrentWorkspace(workspace);
|
||||
|
||||
if (isDefined(workspace) && isOnAWorkspaceSubdomain) {
|
||||
if (isDefined(workspace) && isOnAWorkspace) {
|
||||
setLastAuthenticateWorkspaceDomain({
|
||||
workspaceId: workspace.id,
|
||||
subdomain: workspace.subdomain,
|
||||
workspaceUrl: getWorkspaceUrl(workspace.workspaceUrls),
|
||||
});
|
||||
}
|
||||
|
||||
@ -315,7 +315,7 @@ export const useAuth = () => {
|
||||
};
|
||||
}, [
|
||||
getCurrentUser,
|
||||
isOnAWorkspaceSubdomain,
|
||||
isOnAWorkspace,
|
||||
setCurrentUser,
|
||||
setCurrentWorkspace,
|
||||
setCurrentWorkspaceMember,
|
||||
@ -413,7 +413,8 @@ export const useAuth = () => {
|
||||
|
||||
if (isMultiWorkspaceEnabled) {
|
||||
return redirectToWorkspaceDomain(
|
||||
signUpResult.data.signUp.workspace.subdomain,
|
||||
getWorkspaceUrl(signUpResult.data.signUp.workspace.workspaceUrls),
|
||||
|
||||
isEmailVerificationRequired ? AppPath.SignInUp : AppPath.Verify,
|
||||
{
|
||||
...(!isEmailVerificationRequired && {
|
||||
|
||||
@ -27,6 +27,7 @@ import { useRedirectToWorkspaceDomain } from '@/domain-manager/hooks/useRedirect
|
||||
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { isDefined } from 'twenty-shared';
|
||||
import { getWorkspaceUrl } from '~/utils/getWorkspaceUrl';
|
||||
|
||||
const StyledContentContainer = styled(motion.div)`
|
||||
margin-bottom: ${({ theme }) => theme.spacing(8)};
|
||||
@ -92,9 +93,13 @@ export const SignInUpGlobalScopeForm = () => {
|
||||
if (response.__typename === 'UserExists') {
|
||||
if (response.availableWorkspaces.length >= 1) {
|
||||
const workspace = response.availableWorkspaces[0];
|
||||
return redirectToWorkspaceDomain(workspace.subdomain, pathname, {
|
||||
email: form.getValues('email'),
|
||||
});
|
||||
return redirectToWorkspaceDomain(
|
||||
getWorkspaceUrl(workspace.workspaceUrls),
|
||||
pathname,
|
||||
{
|
||||
email: form.getValues('email'),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
if (response.__typename === 'UserNotExists') {
|
||||
|
||||
@ -3,6 +3,7 @@ import { useSSO } from '@/auth/sign-in-up/hooks/useSSO';
|
||||
import { useRedirect } from '@/domain-manager/hooks/useRedirect';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { renderHook } from '@testing-library/react';
|
||||
|
||||
jest.mock('@/ui/feedback/snack-bar-manager/hooks/useSnackBar');
|
||||
@ -52,9 +53,11 @@ const apolloMocks = [
|
||||
];
|
||||
|
||||
const Wrapper = ({ children }: { children: React.ReactNode }) => (
|
||||
<MockedProvider mocks={apolloMocks} addTypename={false}>
|
||||
{children}
|
||||
</MockedProvider>
|
||||
<MemoryRouter>
|
||||
<MockedProvider mocks={apolloMocks} addTypename={false}>
|
||||
{children}
|
||||
</MockedProvider>
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
describe('useSSO', () => {
|
||||
|
||||
@ -13,14 +13,16 @@ export const useSSO = () => {
|
||||
|
||||
const { enqueueSnackBar } = useSnackBar();
|
||||
const { redirect } = useRedirect();
|
||||
|
||||
const redirectToSSOLoginPage = async (identityProviderId: string) => {
|
||||
let authorizationUrlForSSOResult;
|
||||
try {
|
||||
authorizationUrlForSSOResult = await apolloClient.mutate({
|
||||
mutation: GET_AUTHORIZATION_URL,
|
||||
variables: {
|
||||
input: { identityProviderId, workspaceInviteHash },
|
||||
input: {
|
||||
identityProviderId,
|
||||
workspaceInviteHash,
|
||||
},
|
||||
},
|
||||
});
|
||||
} catch (error: any) {
|
||||
|
||||
@ -21,6 +21,7 @@ export type CurrentWorkspace = Pick<
|
||||
| 'hasValidEnterpriseKey'
|
||||
| 'subdomain'
|
||||
| 'hostname'
|
||||
| 'workspaceUrls'
|
||||
| 'metadataVersion'
|
||||
>;
|
||||
|
||||
|
||||
@ -1,10 +1,9 @@
|
||||
import { createState } from '@ui/utilities/state/utils/createState';
|
||||
|
||||
import { Workspace } from '~/generated/graphql';
|
||||
|
||||
export type Workspaces = Pick<
|
||||
Workspace,
|
||||
'id' | 'logo' | 'displayName' | 'subdomain'
|
||||
'id' | 'logo' | 'displayName' | 'workspaceUrls'
|
||||
>[];
|
||||
|
||||
export const workspacesState = createState<Workspaces>({
|
||||
|
||||
Reference in New Issue
Block a user