Skip to content

Instantly share code, notes, and snippets.

@lenilsonjr
Created August 1, 2023 20:11
Show Gist options
  • Save lenilsonjr/0d51a12c8e5fe90abde64385eeaf943e to your computer and use it in GitHub Desktop.
Save lenilsonjr/0d51a12c8e5fe90abde64385eeaf943e to your computer and use it in GitHub Desktop.
Cloudflare Turnstile on Rails
class ApplicationController < ActionController::Base
# ...
def protect_with_cf_turnstile!
token = params['cf-turnstile-response']
ip = request.remote_ip
secret = Rails.application.credentials.dig(:cloudfare, :turnstile, :secret_key) #Ur site secret
# Prepare the form data
body = { :secret => secret, :response => token, :remoteip => ip }
# Validate the token by calling the "/siteverify" API endpoint.
url = 'https://challenges.cloudflare.com/turnstile/v0/siteverify'
conn = Faraday.new
response = conn.post do |req|
req.url url
req.body = body
end
outcome = JSON.parse(response.body)
unless outcome['success']
Rails.logger.info "CF Turnstile failed for #{ip}"
redirect_to "/403" and return
end
end
end
<%= simple_form_for(
User.new,
url: registration_path(:user),
data: {
controller: "turnstile",
turbo_frame: "_top"
}, html: { class: "" }
) do |f| %>
<div>
<input name="cf-turnstile-response" value="" data-turnstile-target="input" type="hidden" />
<label for="email" class="sr-only">Ur email</label>
<%= f.input_field :email, placeholder: "Seu e-mail de trabalho", class: "", required: true, autofocus: false, autocomplete: "email" %>
<button
type="submit"
data-turnstile-target="button"
>
Submit
</button>
</div>
<div data-turnstile-target="container"></div>
<% end %>
// Thanks @marckohlbrugge!!!
import { Controller } from "@hotwired/stimulus"
// Connects to data-controller="turnstile"
export default class extends Controller {
static values = { siteKey: String }
static targets = [ "button", "container", "input" ]
connect() {
// https://developers.cloudflare.com/turnstile/reference/testing/
this.siteKeyValue = "" // ur site public key or test key
if (!window.turnstile) {
this.loadTurnstileScript()
}
this.element.addEventListener("submit", (event) => {
if (this.inputTarget.value === "") {
event.preventDefault()
this.initTurnstile()
}
})
}
loadTurnstileScript() {
const script = document.createElement("script")
script.src = "https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit&onload=onTurnstileLoad"
script.defer = "defer"
script.async = true
window.onTurnstileLoad = () => {}
document.body.appendChild(script)
}
disconnect() {
if (this.widgetId) {
turnstile.remove(this.widgetId)
}
}
disableButton() {
this.buttonTarget.disabled = true
this.buttonTarget.classList.add("cursor-not-allowed.opacity-50")
}
enableButton() {
this.buttonTarget.disabled = false
this.buttonTarget.classList.remove("cursor-not-allowed.opacity-50")
}
initTurnstile() {
this.disableButton()
this.widgetId = turnstile.render(this.containerTarget, {
sitekey: this.siteKeyValue,
theme: "light",
"before-interactive-callback": () => {
this.disableButton()
this.containerTarget.classList.remove("hidden")
},
"after-interactive-callback": () => {
this.containerTarget.classList.add("hidden")
this.enableButton()
},
callback: (token) => {
// Success!
this.inputTarget.value = token
this.inputTarget.form.submit()
},
})
}
}
class RegistrationsController < Devise::RegistrationsController
before_action :protect_with_cf_turnstile!, only: [:create]
# ...
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment