fix(auth): add captcha auto-refresh via ApolloLink (#12758)
- Introduced `createCaptchaRefreshLink` to trigger captcha token refresh automatically. - Removed redundant manual captcha refresh calls and integrated it into Apollo Provider.
This commit is contained in:
@ -1,10 +1,17 @@
|
||||
import { ApolloProvider as ApolloProviderBase } from '@apollo/client';
|
||||
|
||||
import { useApolloFactory } from '@/apollo/hooks/useApolloFactory';
|
||||
import { useRequestFreshCaptchaToken } from '@/captcha/hooks/useRequestFreshCaptchaToken';
|
||||
import { createCaptchaRefreshLink } from '@/apollo/utils/captchaRefreshLink';
|
||||
|
||||
export const ApolloProvider = ({ children }: React.PropsWithChildren) => {
|
||||
const { requestFreshCaptchaToken } = useRequestFreshCaptchaToken();
|
||||
|
||||
const captchaRefreshLink = createCaptchaRefreshLink(requestFreshCaptchaToken);
|
||||
|
||||
const apolloClient = useApolloFactory({
|
||||
connectToDevTools: true,
|
||||
extraLinks: [captchaRefreshLink],
|
||||
});
|
||||
|
||||
// This will attach the right apollo client to Apollo Dev Tools
|
||||
|
||||
@ -0,0 +1,19 @@
|
||||
import { ApolloLink } from '@apollo/client';
|
||||
|
||||
export const createCaptchaRefreshLink = (
|
||||
requestFreshCaptchaToken: () => void,
|
||||
) => {
|
||||
return new ApolloLink((operation, forward) => {
|
||||
const { variables } = operation;
|
||||
|
||||
const hasCaptchaToken = variables && 'captchaToken' in variables;
|
||||
|
||||
return forward(operation).map((response) => {
|
||||
if (hasCaptchaToken) {
|
||||
requestFreshCaptchaToken();
|
||||
}
|
||||
|
||||
return response;
|
||||
});
|
||||
});
|
||||
};
|
||||
@ -501,7 +501,8 @@ export const useAuth = () => {
|
||||
|
||||
const handleSignOut = useCallback(async () => {
|
||||
await clearSession();
|
||||
}, [clearSession]);
|
||||
await requestFreshCaptchaToken();
|
||||
}, [clearSession, requestFreshCaptchaToken]);
|
||||
|
||||
const handleCredentialsSignUpInWorkspace = useCallback(
|
||||
async ({
|
||||
@ -519,60 +520,55 @@ export const useAuth = () => {
|
||||
captchaToken?: string;
|
||||
verifyEmailNextPath?: string;
|
||||
}) => {
|
||||
try {
|
||||
const signUpInWorkspaceResult = await signUpInWorkspace({
|
||||
variables: {
|
||||
email,
|
||||
password,
|
||||
workspaceInviteHash,
|
||||
workspacePersonalInviteToken,
|
||||
captchaToken,
|
||||
locale: i18n.locale ?? 'en',
|
||||
...(workspacePublicData?.id
|
||||
? { workspaceId: workspacePublicData.id }
|
||||
: {}),
|
||||
verifyEmailNextPath,
|
||||
},
|
||||
});
|
||||
const signUpInWorkspaceResult = await signUpInWorkspace({
|
||||
variables: {
|
||||
email,
|
||||
password,
|
||||
workspaceInviteHash,
|
||||
workspacePersonalInviteToken,
|
||||
captchaToken,
|
||||
locale: i18n.locale ?? 'en',
|
||||
...(workspacePublicData?.id
|
||||
? { workspaceId: workspacePublicData.id }
|
||||
: {}),
|
||||
verifyEmailNextPath,
|
||||
},
|
||||
});
|
||||
|
||||
if (isDefined(signUpInWorkspaceResult.errors)) {
|
||||
throw signUpInWorkspaceResult.errors;
|
||||
}
|
||||
|
||||
if (!signUpInWorkspaceResult.data?.signUpInWorkspace) {
|
||||
throw new Error('No login token');
|
||||
}
|
||||
|
||||
if (isEmailVerificationRequired) {
|
||||
setSearchParams({ email });
|
||||
setSignInUpStep(SignInUpStep.EmailVerification);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (isMultiWorkspaceEnabled) {
|
||||
return await redirectToWorkspaceDomain(
|
||||
getWorkspaceUrl(
|
||||
signUpInWorkspaceResult.data.signUpInWorkspace.workspace
|
||||
.workspaceUrls,
|
||||
),
|
||||
isEmailVerificationRequired ? AppPath.SignInUp : AppPath.Verify,
|
||||
{
|
||||
...(!isEmailVerificationRequired && {
|
||||
loginToken:
|
||||
signUpInWorkspaceResult.data.signUpInWorkspace.loginToken
|
||||
.token,
|
||||
}),
|
||||
email,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
await handleGetAuthTokensFromLoginToken(
|
||||
signUpInWorkspaceResult.data?.signUpInWorkspace.loginToken.token,
|
||||
);
|
||||
} finally {
|
||||
requestFreshCaptchaToken();
|
||||
if (isDefined(signUpInWorkspaceResult.errors)) {
|
||||
throw signUpInWorkspaceResult.errors;
|
||||
}
|
||||
|
||||
if (!signUpInWorkspaceResult.data?.signUpInWorkspace) {
|
||||
throw new Error('No login token');
|
||||
}
|
||||
|
||||
if (isEmailVerificationRequired) {
|
||||
setSearchParams({ email });
|
||||
setSignInUpStep(SignInUpStep.EmailVerification);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (isMultiWorkspaceEnabled) {
|
||||
return await redirectToWorkspaceDomain(
|
||||
getWorkspaceUrl(
|
||||
signUpInWorkspaceResult.data.signUpInWorkspace.workspace
|
||||
.workspaceUrls,
|
||||
),
|
||||
isEmailVerificationRequired ? AppPath.SignInUp : AppPath.Verify,
|
||||
{
|
||||
...(!isEmailVerificationRequired && {
|
||||
loginToken:
|
||||
signUpInWorkspaceResult.data.signUpInWorkspace.loginToken.token,
|
||||
}),
|
||||
email,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
await handleGetAuthTokensFromLoginToken(
|
||||
signUpInWorkspaceResult.data?.signUpInWorkspace.loginToken.token,
|
||||
);
|
||||
},
|
||||
[
|
||||
signUpInWorkspace,
|
||||
@ -583,7 +579,6 @@ export const useAuth = () => {
|
||||
setSearchParams,
|
||||
isEmailVerificationRequired,
|
||||
redirectToWorkspaceDomain,
|
||||
requestFreshCaptchaToken,
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
@ -10,7 +10,6 @@ import {
|
||||
} from '@/auth/states/signInUpStepState';
|
||||
import { SignInUpMode } from '@/auth/types/signInUpMode';
|
||||
import { useReadCaptchaToken } from '@/captcha/hooks/useReadCaptchaToken';
|
||||
import { useRequestFreshCaptchaToken } from '@/captcha/hooks/useRequestFreshCaptchaToken';
|
||||
import { useBuildSearchParamsFromUrlSyncedStates } from '@/domain-manager/hooks/useBuildSearchParamsFromUrlSyncedStates';
|
||||
import { AppPath } from '@/types/AppPath';
|
||||
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
|
||||
@ -47,16 +46,14 @@ export const useSignInUp = (form: UseFormReturn<Form>) => {
|
||||
checkUserExists: { checkUserExistsQuery },
|
||||
} = useAuth();
|
||||
|
||||
const { requestFreshCaptchaToken } = useRequestFreshCaptchaToken();
|
||||
const { readCaptchaToken } = useReadCaptchaToken();
|
||||
|
||||
const { buildSearchParamsFromUrlSyncedStates } =
|
||||
useBuildSearchParamsFromUrlSyncedStates();
|
||||
|
||||
const continueWithEmail = useCallback(() => {
|
||||
requestFreshCaptchaToken();
|
||||
setSignInUpStep(SignInUpStep.Email);
|
||||
}, [requestFreshCaptchaToken, setSignInUpStep]);
|
||||
}, [setSignInUpStep]);
|
||||
|
||||
const continueWithCredentials = useCallback(async () => {
|
||||
const token = await readCaptchaToken();
|
||||
@ -74,7 +71,6 @@ export const useSignInUp = (form: UseFormReturn<Form>) => {
|
||||
});
|
||||
},
|
||||
onCompleted: (data) => {
|
||||
requestFreshCaptchaToken();
|
||||
setSignInUpMode(
|
||||
data?.checkUserExists.exists
|
||||
? SignInUpMode.SignIn
|
||||
@ -88,7 +84,6 @@ export const useSignInUp = (form: UseFormReturn<Form>) => {
|
||||
form,
|
||||
checkUserExistsQuery,
|
||||
enqueueSnackBar,
|
||||
requestFreshCaptchaToken,
|
||||
setSignInUpStep,
|
||||
setSignInUpMode,
|
||||
]);
|
||||
@ -154,8 +149,6 @@ export const useSignInUp = (form: UseFormReturn<Form>) => {
|
||||
enqueueSnackBar(err?.message, {
|
||||
variant: SnackBarVariant.Error,
|
||||
});
|
||||
} finally {
|
||||
requestFreshCaptchaToken();
|
||||
}
|
||||
},
|
||||
[
|
||||
@ -169,7 +162,6 @@ export const useSignInUp = (form: UseFormReturn<Form>) => {
|
||||
workspaceInviteHash,
|
||||
workspacePersonalInviteToken,
|
||||
enqueueSnackBar,
|
||||
requestFreshCaptchaToken,
|
||||
buildSearchParamsFromUrlSyncedStates,
|
||||
isOnAWorkspace,
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user