fix(email-validation): validation email cross workspace (#11261)

- Finish removing captcha guard from validation  email
- Fix an issue where domain validation wasn't working on a multi-domain
setup
This commit is contained in:
Antoine Moreaux
2025-04-02 10:06:34 +02:00
committed by GitHub
parent 44d4e4efaf
commit 44bf393b06
16 changed files with 102 additions and 82 deletions

View File

@ -3,12 +3,14 @@ import { AppPath } from '@/types/AppPath';
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { useReadCaptchaToken } from '@/captcha/hooks/useReadCaptchaToken';
import { useLingui } from '@lingui/react/macro';
import { useEffect, useState } from 'react';
import { useSearchParams } from 'react-router-dom';
import { useNavigateApp } from '~/hooks/useNavigateApp';
import { EmailVerificationSent } from '../sign-in-up/components/EmailVerificationSent';
import { useRedirectToWorkspaceDomain } from '@/domain-manager/hooks/useRedirectToWorkspaceDomain';
import { useVerifyLogin } from '@/auth/hooks/useVerifyLogin';
import { getWorkspaceUrl } from '~/utils/getWorkspaceUrl';
export const VerifyEmailEffect = () => {
const { getLoginTokenFromEmailVerificationToken } = useAuth();
@ -21,7 +23,9 @@ export const VerifyEmailEffect = () => {
const emailVerificationToken = searchParams.get('emailVerificationToken');
const navigate = useNavigateApp();
const { readCaptchaToken } = useReadCaptchaToken();
const { redirectToWorkspaceDomain } = useRedirectToWorkspaceDomain();
const { verifyLoginToken } = useVerifyLogin();
const { t } = useLingui();
useEffect(() => {
const verifyEmailToken = async () => {
@ -33,23 +37,25 @@ export const VerifyEmailEffect = () => {
return navigate(AppPath.SignInUp);
}
const captchaToken = await readCaptchaToken();
try {
const { loginToken } = await getLoginTokenFromEmailVerificationToken(
emailVerificationToken,
captchaToken,
);
const { loginToken, workspaceUrls } =
await getLoginTokenFromEmailVerificationToken(emailVerificationToken);
enqueueSnackBar(t`Email verified.`, {
dedupeKey: 'email-verification-dedupe-key',
variant: SnackBarVariant.Success,
});
navigate(AppPath.Verify, undefined, { loginToken: loginToken.token });
const workspaceUrl = getWorkspaceUrl(workspaceUrls);
if (workspaceUrl.slice(0, -1) !== window.location.origin) {
return redirectToWorkspaceDomain(workspaceUrl, AppPath.Verify, {
loginToken: loginToken.token,
});
}
verifyLoginToken(loginToken.token);
} catch (error) {
enqueueSnackBar(t`Email verification failed.`, {
dedupeKey: 'email-verification-dedupe-key',
dedupeKey: 'email-verification-error-dedupe-key',
variant: SnackBarVariant.Error,
});
setIsError(true);

View File

@ -12,6 +12,10 @@ export const GET_LOGIN_TOKEN_FROM_EMAIL_VERIFICATION_TOKEN = gql`
loginToken {
...AuthTokenFragment
}
workspaceUrls {
subdomainUrl
customUrl
}
}
}
`;

View File

@ -13,7 +13,6 @@ import { iconsState } from 'twenty-ui';
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { isCurrentUserLoadedState } from '@/auth/states/isCurrentUserLoadingState';
import { isVerifyPendingState } from '@/auth/states/isVerifyPendingState';
import { workspacesState } from '@/auth/states/workspaces';
import { billingState } from '@/client-config/states/billingState';
import { clientConfigApiStatusState } from '@/client-config/states/clientConfigApiStatusState';
@ -87,7 +86,6 @@ export const useAuth = () => {
const setSignInUpStep = useSetRecoilState(signInUpStepState);
const setCurrentWorkspace = useSetRecoilState(currentWorkspaceState);
const setIsVerifyPendingState = useSetRecoilState(isVerifyPendingState);
const setWorkspaces = useSetRecoilState(workspacesState);
const { redirect } = useRedirect();
const { redirectToWorkspaceDomain } = useRedirectToWorkspaceDomain();
@ -337,8 +335,6 @@ export const useAuth = () => {
const handleGetAuthTokensFromLoginToken = useCallback(
async (loginToken: string) => {
setIsVerifyPendingState(true);
const getAuthTokensResult = await getAuthTokensFromLoginToken({
variables: { loginToken },
});
@ -356,15 +352,8 @@ export const useAuth = () => {
);
await loadCurrentUser();
setIsVerifyPendingState(false);
},
[
setIsVerifyPendingState,
getAuthTokensFromLoginToken,
setTokenPair,
loadCurrentUser,
],
[getAuthTokensFromLoginToken, setTokenPair, loadCurrentUser],
);
const handleCredentialsSignIn = useCallback(
@ -391,8 +380,6 @@ export const useAuth = () => {
workspacePersonalInviteToken?: string,
captchaToken?: string,
) => {
setIsVerifyPendingState(true);
const signUpResult = await signUp({
variables: {
email,
@ -440,7 +427,6 @@ export const useAuth = () => {
);
},
[
setIsVerifyPendingState,
signUp,
workspacePublicData,
isMultiWorkspaceEnabled,

View File

@ -1,12 +1,8 @@
import { useRecoilState, useRecoilValue } from 'recoil';
import { isVerifyPendingState } from '@/auth/states/isVerifyPendingState';
import { useRecoilState } from 'recoil';
import { tokenPairState } from '../states/tokenPairState';
export const useIsLogged = (): boolean => {
const [tokenPair] = useRecoilState(tokenPairState);
const isVerifyPending = useRecoilValue(isVerifyPendingState);
return !!tokenPair && !isVerifyPending;
return !!tokenPair;
};

View File

@ -1,6 +0,0 @@
import { createState } from '@ui/utilities/state/utils/createState';
export const isVerifyPendingState = createState<boolean>({
key: 'isVerifyPendingState',
defaultValue: false,
});