aryehraber / statamic-captcha

Statamic Addon that protects your Statamic forms using a Captcha service.
MIT License
11 stars 8 forks source link

Usage with Full Static Caching (and `nocache`) #45

Closed sjclark closed 1 week ago

sjclark commented 1 year ago

😄 Been trying to wrap my head around an intermittent issue today - but turns out it's not intermittent!

When using the Captcha addon with Full Static Caching the first page load works fine, but subsequent page loads are missing the captcha widget. I've got the entire form wrapped in {{nocache}} tags.

Where the widget should be I just get <div class="cf-turnstile" data-sitekey="0x4AAAAAAACauAHbma0FeuPz"></div>

Theres nothing logged in the console. The call to <script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script> seems to be correctly in there.

Is the addon compatible with nocache? Should I be including the javascript elsewhere somehow? 😂 any ideas at all?

EDIT: Hmmmm, maybe it requires some sort of Replacer wangjangling https://statamic.dev/static-caching#replacers

aryehraber commented 1 year ago

Hey @sjclark, I have a theory on why this is happening but don't have time to test atm. Let me explain and hopefully you can do some digging...

Since Captcha's tags are simply outputting the same static content every page load (the JS snippet & the div with the sitekey), it should work fine with Statamic's caching functionality (full measure and regular caching). However, since you've wrapped the form (including the Captcha div) with {{ nocache }}, Statamic will replace those after the page loads.

From the docs:

When using the file static cache driver (aka. "full measure") the pages will be stored as plain html files on your server. On any pages that use a nocache tag, a small snippet of JavaScript will be injected just before the closing tag. The nocache fragments will be retrieved from the server using an AJAX request. Because of this, there may be a slight delay before the fragments are replaced.

I think that this delay is the problem, since the Captcha div isn't technically available on initial page load and therefore Captcha's JS snippet will be called too early.

Your issue should be solved by removing the {{ nocache }} tags around the form but I imagine you need it for the CSRF token refresh, so perhaps an option is wrapping {{ nocache }} around {{ captcha:head }} so that the JS also has a slight delay. I'm not sure how Statamic does it's dynamic replacing, but there may still be a scenario where the JS snippet loads before the Captcha div, but I guess you'll need to test it out.

Curious to hear your findings...

sjclark commented 1 year ago

Hmmmmmm, so I've done a bit more digging. The call to Turnstile is definitely being made in either case - as I can see it correctly connects (and responds) both within and outside nocache tags. It must be that the JS can't be run from within the cached content I guess? Will keep digging, looking at https://github.com/statamic/cms/pull/6828/files as its literally referenced here using Captchas as an example: https://github.com/statamic/ideas/issues/879

sjclark commented 1 year ago

I've been trying to work through https://developers.cloudflare.com/turnstile/get-started/client-side-rendering/#disable-implicit-rendering but just can't quite get things to work - the instructions of which URL don't seem consistent either. As far as I can tell turnstile (by default) waits for DOMContentLoaded, which it won't get if it's loaded later on (post nocache). But I'm not really sure how to correctly wire up the explicit version in terms of verifying the token.

Ended up with this below the form

<script src="https://challenges.cloudflare.com/turnstile/v0/api.js?onload=onloadTurnstileCallback" async defer></script>
<script>
document.addEventListener('statamic:nocache.replaced', (event) => {
    alert('nocache elements have been replaced!');
window.onloadTurnstileCallback = function () {
    turnstile.render('.cf-turnstile', {
        sitekey: '0x4AAAAAAACauAHbma0FeuPz',
        callback: function(token) {
            console.log(`Challenge Success ${token}`);
        },
    });
};

});
</script>

Will keep exploring 😂

sjclark commented 1 year ago

Well...... this a little hacky.... but seems to work....

<!-- Load script (used for initial / non-static load to work -->
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js?onload=onloadTurnstileCallback"
async defer></script>

<!-- Load script (code below fires on Static Load) -->
<script>
  // Add listener for Statamic Event
  document.addEventListener('statamic:nocache.replaced', (event) => {
      // alert('nocache elements have been replaced!');
      // If fired then append script to page
      const script = document.createElement("script")
      // URL parameter `onloadTurnstileCallback` required in order for script to fire not on DomContentLoad
      script.src = "https://challenges.cloudflare.com/turnstile/v0/api.js?onload=onloadTurnstileCallback"
      script.async = false
      document.head.appendChild(script)
  });
</script>

I think for now it's probably a little more future-proof to just exclude my forms from the Static Cache, but it's been good to figure this all out. Keen to hear your thoughts 😄 and then I'll close the issue!

aryehraber commented 1 year ago

Thanks for the follow up comments as you figured things out!

I read into the links your shared earlier (regarding the Statamic changes & JS hook) and definitely feel Captcha needs to be updated a little to handle this out of the box. It will likely become some variation of what you've posted ☝️ so thanks again for sharing!

Leave this issue open, I'll try and get to it at some points when I find some time 👍

aryehraber commented 1 year ago

Hey @sjclark!

I took a look into this over the weekend and I think I solved it in a relatively seamless way. Unfortunately, I don't have much experience with the {{ nocache }} tag, and can't seem to get Statamic forms to play nice (even without Captcha), specifically validation errors don't show up. I'm pretty sure I'm just missing something, which is fine because Captcha seems to be working as expected.

I ended up writing a Replacer (just as you suggested 😊) and made usage fairly simple (I think). Would love to hear your experience/feedback; all you need to do is add \AryehRaber\Captcha\CaptchaReplacer::class to the default Statamic replacers: https://statamic.dev/static-caching#replacers

'replacers' => [
    CsrfTokenReplacer::class,
    NoCacheReplacer::class,
    \AryehRaber\Captcha\CaptchaReplacer::class, 
],

I haven't released a new version yet, so for now please update your composer.json with Captcha's dev branch: "aryehraber/statamic-captcha": "dev-dev" and run composer update.

I'll update Captcha's docs once we determine that the issue is fully solved.

sjclark commented 1 year ago

Bah, I'm trying to look into this @aryehraber but I'm beginning to think there might be some issues up with my site specifically.

Null caching everything works fine. For half, I'm getting some sort of weird Cloudflare Error; Screen Shot 2023-02-21 at 5 14 12 pm For full I'm literally getting nothing, the form doesn't even show at all 😵‍💫 no errors thrown, the nocache tags output nothing.

I can see the correct nocache javascript snippet on the page, it just doesn't seem to actually activate / do anything.

I'll keep digging 😢

EDIT: hmmm even in null caching I'm still getting inconsistent errors now; Screen Shot 2023-02-21 at 5 25 43 pm Referencing https://developer.mozilla.org/en-US/docs/Web/Privacy/Storage_Access_Policy/Errors/CookiePartitionedForeign

Not too sure whats going on there, it checks for validation for a bit, then just seems to completely delete the element (the whole checkbox vanishes), doesn't even say its failed. Sometimes it literally doesn't show the captcha whatsoever?

Blergh, websites. 😢 I'll try circle back to this in a couple of days. I'm SURE what you've done probably works great and it's something to do with my setup.

aryehraber commented 1 year ago

Hmm, that doesn't sound too good...

For full I'm literally getting nothing, the form doesn't even show at all 😵‍💫 no errors thrown, the nocache tags output nothing.

Does this also happen when Captcha is off? Or is Captcha the thing that (seems to) trigger these issues?

aryehraber commented 1 year ago

If everything works as expected when Captcha isn't installed (full-caching w/ form validation, etc.), then perhaps we can jump on a short Tuple pairing call to try and get to the bottom of it together, if you're open to that? Anyway, good luck for when you have time to get back to this!

sjclark commented 1 year ago

I'm sorry for the delay in getting back to you @aryehraber, its been a rubbish week! I ended up updating Statamic (to absolute latest), clearing out a few things, fixing some odd issues going on in my site - have a feeling something had borked itself that updating cleared out.

BUT I've got the recpatcha working, you genius! 🥳 Now works perfectly in half, and when running full it still seems to work but is just limited by your aforementioned specifically validation errors don't show up - so thats progress for sure!

Hmmm, just noticed the docs specifically state https://statamic.dev/tags/nocache#forms

However, if your form has logic in it, like validation or success messages, 
you'll need to keep the form:create dynamic by wrapping in nocache tags.

Sooooo it should work theoretically? (doesn't seem to for me, will keep digging)

Unfortunately, I don't have much experience with the {{ nocache }} tag, and can't seem to get Statamic forms to play nice (even without Captcha), specifically validation errors don't show up. I'm pretty sure I'm just missing something, which is fine because Captcha seems to be working as expected.

See https://github.com/statamic/cms/issues/7628

aryehraber commented 1 year ago

Thanks for following up @sjclark, great to hear you made progress 💪

I was about to ask whether the missing validation errors were also missing when not using Captcha, but from your Statamic Issue it sounds like you tried that 👍 I also read that line in the docs and was therefore extra confused...

I believe (hope) that the final piece of the puzzle to get Full Static Caching working is the validation errors, which should ensure that the Captcha errors are also returned correctly.

Can leave this open until it's fully resolved. Hopefully the issue is addressed soon!

bfaulkner-champions commented 1 year ago

Thanks for following up @sjclark, great to hear you made progress 💪

I was about to ask whether the missing validation errors were also missing when not using Captcha, but from your Statamic Issue it sounds like you tried that 👍 I also read that line in the docs and was therefore extra confused...

I believe (hope) that the final piece of the puzzle to get Full Static Caching working is the validation errors, which should ensure that the Captcha errors are also returned correctly.

Can leave this open until it's fully resolved. Hopefully the issue is addressed soon!

Did you have any luck with this? I'm trying to get the reCaptcha option working with half method, but it always returns "Captcha Failed".

aryehraber commented 1 year ago

@bfaulkner-champions Would you mind opening a New Issue? I'd prefer not clutter this one since it's specifically for full static caching. Please provide as much info as possible, such as versions (Statamic & Captcha), your config file and anything else that could be relevant. Will do my best to help debug your issue. Thanks!

sjclark commented 11 months ago

Hey @aryehraber just circling back to look at this on a fresh site (been overseas for a while ) 😄 would you mind tweaking to add v4 into the dev branch, just running into composer funkiness? https://github.com/aryehraber/statamic-captcha/blob/dev/composer.json

aryehraber commented 11 months ago

Hi @sjclark! Sure thing, dev should be updated now 👍