Skip to content

[MKT_949]:feat/cloudflare turnstile integration - #394

Open
jaaaaavier wants to merge 7 commits into
masterfrom
feat/cloudflare-turnstile-integration
Open

[MKT_949]:feat/cloudflare turnstile integration#394
jaaaaavier wants to merge 7 commits into
masterfrom
feat/cloudflare-turnstile-integration

Conversation

@jaaaaavier

@jaaaaavier jaaaaavier commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Changes

The purpose of this PR is to integrate Cloudflare Turnstile into the repo. To do this, we’ve done the following:

  • Create the assertCaptcha file. Here, we basically decide whether to use the new verifyTurnstile function or the old verifyRecaptcha function. This allows us to keep what we already had and know works as a fallback
  • In the verifyTurnstile file, we’ve set up everything needed to verify with Cloudflare that the token is correct. This file was created based on the official Cloudflare Turnstile documentation.
  • I’ve also extracted the isTransientNetworkError method to the networkRetry file, since we’ll be using it in different parts of the code.
  • In the checkout controller, we’ve replaced verifyRecaptcha with the asserCaptcha described above, marked captchaToken as optional, and added turnstileToken in the same way; now, one of these two must be required
  • New variables have been added
  • Tests have been updated

Test

Cloudflare publishes a set of dummy keys: public sitekey/secret pairs that force a deterministic
outcome (always pass, always fail, force an interactive challenge) while still going through the real
siteverify API. They let us exercise the whole chain — browser widget → drive-web → payments →
Cloudflare — without creating a widget in the dashboard.

Each scenario was run through a full checkout, checking three places: the request payload in the
browser, the payments logs, and the resulting HTTP status.

Scenario Keys Result
Happy path sitekey 1x…BB (pass, invisible) + secret 1x…AA (pass) POST /checkout/customer and POST /checkout/payment-intent both carry a turnstileToken alongside the reCAPTCHA captchaToken. No fallback warning in the logs. Payment completes.
Turnstile rejects the token secret 2x…AA (always fails) Turnstile rejected the token: invalid-input-response → 403, and reCAPTCHA is never consulted. responseTime: 60ms confirms no retries were attempted, i.e. the code was correctly treated as terminal rather than transient.
Widget fails in the browser sitekey 2x…BB (always fails, invisible) Console shows Turnstile error 600010; no turnstileToken in the payload; payment completes through reCAPTCHA.
Misconfigured sitekey invalid sitekey Widget never initialises, generateTurnstileToken() returns undefined, checkout is unaffected and falls back to reCAPTCHA.

I can add some images or video in the task if needed but i think we will need some QA

Notes

The enviroment variables required are:

TURNSTILE_ENDPOINT=https://challenges.cloudflare.com/turnstile/v0/siteverify
TURNSTILE_SECRET=the one used on ai-server

@sg-gs

sg-gs commented Jul 28, 2026

Copy link
Copy Markdown
Member

The markdown table format seems to not be correct @jaaaaavier

sg-gs
sg-gs previously approved these changes Jul 28, 2026

@sg-gs sg-gs left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add the list of new environment variables in the PR's description @jaaaaavier

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if turnstileToken is now the first option, maybe it should be required, not optional. WDYT?

Comment thread src/utils/verifyTurnstile.ts Outdated
Comment on lines +7 to +9
const MAX_TURNSTILE_ATTEMPTS = 3;
const TURNSTILE_RETRY_BASE_DELAY_MS = 300;
const RETRYABLE_TURNSTILE_ERROR_CODE = 'internal-error';

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we can also extract the shared constants between this file and the captcha one to a constants.ts.

Comment thread src/utils/verifyTurnstile.ts Outdated
hostname?: string;
};

export class TurnstileUnavailableError extends Error {}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add the necessary info here with:

super(message = '...') {
this.message = message;
this.name = 'TurnstileUnavailableError';
Object.setPrototypeOf(this, TurnstileUnavailableError.prototype);
}

Context: https://github.com/internxt/payments-server/blob/master/src/errors/UsersErrors.ts#L12

Comment thread tests/src/utils/assertCaptcha.test.ts Outdated
Comment on lines +20 to +27
jest.mock('../../../src/Logger', () => ({
__esModule: true,
default: {
warn: jest.fn(),
info: jest.fn(),
error: jest.fn(),
},
}));

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you can use spyOn for Logger instead of mocking it.

Comment thread src/utils/verifyTurnstile.ts Outdated
return await requestTurnstile(turnstileToken);
} catch (err) {
const isLastAttempt = attempt === MAX_TURNSTILE_ATTEMPTS - 1;
const isRetryable = isTransientNetworkError(err) || err instanceof TurnstileUnavailableError;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extract this to a function. Something like:

function isRetryableTurnstileError(err: unknown): boolean {
  return isTransientNetworkError(err) || err instanceof TurnstileUnavailableError;
}

@sonarqubecloud

Copy link
Copy Markdown

@jaaaaavier
jaaaaavier requested a review from xabg2 July 29, 2026 08:51
@xabg2
xabg2 requested a review from sg-gs July 29, 2026 09:32

@sg-gs sg-gs left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding the environment variables @jaaaaavier

@sg-gs

sg-gs commented Jul 29, 2026

Copy link
Copy Markdown
Member

Secrets updated @jaaaaavier

Turnstile Widget updated to consider drive.internxt.com also.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request ready-for-preview

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants