bdebon / choiceof.dev

Developers have to cope with pretty hard choices every day. This is a collection of the most complex and funny choices. What will be yours?
https://choiceof.dev
GNU General Public License v3.0
473 stars 93 forks source link

feat: secure API using Cloudflare's Turnstile #158

Open quentint opened 1 year ago

quentint commented 1 year ago

Fighting h@ckers with Cloudflare's Turnstile 😉
See https://github.com/bdebon/choiceof.dev/issues/100 for details and discussion.

quentint commented 1 year ago

@bdebon: I pushed this yesterday evening but didn't take time to explain the feature and how to set it up 🥱

That's all very simple but let me do it before considering merging 😉

bdebon commented 1 year ago

I read the pr, superb refactorings here and there and implementation of this turnstile thing that I have to confess I don't understand 100% for now (I have never used cloudflare so I'm missing some things here). Waiting for your explanation when you have time but super hyped about the content of this PR! Thank you so much!

quentint commented 1 year ago

Here's my proper MR message ⚡

Why?

We need a way to prevent spam 💀

How?

Use Cloudflare's Turnstile feature (pretty much like reCAPTCHA).

What steps do we need?

On page load:

  1. Load Turnstile JS API
  2. Initialize an invisible widget
  3. Request a token

When submitting a choice:

  1. [Front] Send the token with the chosen position
  2. [Back] Check if it exists and if Cloudflare validates it (it can only be used once)
  3. [Front] Request a new token for the next choice

What's required?

We need to set up a Turnstile site (any Cloudflare account can do this). This will give us a site and a secret keys.

I created a "localhost" pair and stored the site key in .env as a default value. It can easily be overwritten with .env.local when working locally. We also need to store the secret key, and I chose to do it alongside the root config.php, in $config['CLOUDFLARE_TURNSTILE_SECRET'].

@bdebon: I can share the secret key privately, or I can let you create a Turnstile config yourself and use your own values (that's what I'd recommend).

What could be improved?

  1. Of course, Turnstile is in beta, so it could break. But Cloudflare being huge as it is, I think we can safely use it and update it quickly if any change is announced.
  2. I don't know which version of PHP is used, so I used features available in 7.0.
  3. I introduced HTTP response codes and content (in api.php); which might not be needed, but cannot harm 😉
  4. For now the turnstile values (turnstileWidgetId and turnstileToken) are stored on the window object. That may be a bit rough but I'm not familiar enough with React's best practices.
  5. Also, the tokens are only valid for 300 seconds, so any user waiting more than that between choices will trigger a server-side error (invalid-token). Not sure if that's something we need to handle? We could refresh the token before posting the choice, but that would add delay that I'd like to avoid. We could also retry in the background with a refreshed token when needed, but that sounds a bit too much IMHO.
  6. Some GitHub checks are skipped or cancelled, but I can figure out why. Any pointers would help 🥇

Thanks 🙏

quentint commented 1 year ago

Just found an issue with my token validation test. Will fix ASAP.

quentint commented 1 year ago

All good now 😉

bdebon commented 1 year ago

Hey @quentint, you still have a problem during the build, you can check the ci log for more detail. I'm gonna read the rest of your explanation !

bdebon commented 1 year ago

Perfect explanations thank you so much! I will create a cloudflare account for the sake of seeing how all of this work. I think I understand everything about this PR and I could be able to merge and finish the implementation. Right now the CI is not passing because with next, the window object can be undefined sometimes as it can be rendered server side. There is probably a better place to store this key, I have not checked yet.

quentint commented 1 year ago

Yup, I saw that error. Will try to find a better way to store this.