Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add support for Cloudflare Turnstile #15398

Merged
merged 9 commits into from
Aug 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions docs/system-admin-guide/authentication/recaptcha/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,15 @@ You can configure the following options:
4. Press the **Apply** button to save your changes.

![Sysadmin authentication reCAPTCHA](Sys-admin-authentication-recaptcha.png)


# Cloudflare Turnstile configuration

To use Cloudflare Turnstile with OpenProject, you need to configure the reCAPTCHA settings in the Cloudflare dashboard. Please see the following link for more details on Cloudflare Turnstile and how to configure it: [https://developers.cloudflare.com/turnstile/](https://developers.cloudflare.com/turnstile/).

Once you created a sitekey and secret key in the Cloudflare dashboard, you can configure OpenProject to use these keys.

1. Select "Turnstile" in the reCAPTCHA admin settings.
2. Insert the **website key**.
3. Insert the **secret key**.
4. Press the **Apply** button to save your changes.
32 changes: 30 additions & 2 deletions modules/recaptcha/app/controllers/recaptcha/request_controller.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
require "recaptcha"
require "net/http"

module ::Recaptcha
class RequestController < ApplicationController
Expand Down Expand Up @@ -30,13 +31,15 @@
def perform
if OpenProject::Recaptcha::Configuration.use_hcaptcha?
use_content_security_policy_named_append(:hcaptcha)
else
elsif OpenProject::Recaptcha::Configuration.use_turnstile?
use_content_security_policy_named_append(:turnstile)
elsif OpenProject::Recaptcha::Configuration.use_recaptcha?
use_content_security_policy_named_append(:recaptcha)
end
end

def verify
if valid_recaptcha?
if valid_turnstile? || valid_recaptcha?
save_recaptcha_verification_success!
complete_stage_redirect
else
Expand Down Expand Up @@ -72,6 +75,8 @@
2
when ::OpenProject::Recaptcha::TYPE_V3
3
when ::OpenProject::Recaptcha::TYPE_TURNSTILE
99 # Turnstile is not comparable/compatible with recaptcha
end
end

Expand All @@ -86,6 +91,29 @@
verify_recaptcha call_args
end

##
#
def valid_turnstile?
return false unless OpenProject::Recaptcha::Configuration.use_turnstile?

Check warning on line 97 in modules/recaptcha/app/controllers/recaptcha/request_controller.rb

View workflow job for this annotation

GitHub Actions / rubocop

[rubocop] reported by reviewdog 🐶 Add empty line after guard clause. Raw Output: modules/recaptcha/app/controllers/recaptcha/request_controller.rb:97:7: C: Layout/EmptyLineAfterGuardClause: Add empty line after guard clause.
token = params["turnstile-response"]
return false if token.blank?

data = {
"response" => token,
"remoteip" => request.remote_ip,
"secret" => recaptcha_settings["secret_key"],

Check warning on line 104 in modules/recaptcha/app/controllers/recaptcha/request_controller.rb

View workflow job for this annotation

GitHub Actions / rubocop

[rubocop] reported by reviewdog 🐶 Avoid comma after the last item of a hash. Raw Output: modules/recaptcha/app/controllers/recaptcha/request_controller.rb:104:53: C: Style/TrailingCommaInHashLiteral: Avoid comma after the last item of a hash.
}

data_encoded = URI.encode_www_form(data)

Check warning on line 107 in modules/recaptcha/app/controllers/recaptcha/request_controller.rb

View workflow job for this annotation

GitHub Actions / rubocop

[rubocop] reported by reviewdog 🐶 Useless assignment to variable - `data_encoded`. Raw Output: modules/recaptcha/app/controllers/recaptcha/request_controller.rb:107:7: W: Lint/UselessAssignment: Useless assignment to variable - `data_encoded`.

response = Net::HTTP.post_form(
URI("https://challenges.cloudflare.com/turnstile/v0/siteverify"),
data
)
response = JSON.parse(response.body)
response["success"]
end

##
# fail the recaptcha
def fail_recaptcha(msg)
Expand Down
4 changes: 3 additions & 1 deletion modules/recaptcha/app/helpers/recaptcha_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ def recaptcha_available_options
[I18n.t("recaptcha.settings.type_disabled"), ::OpenProject::Recaptcha::TYPE_DISABLED],
[I18n.t("recaptcha.settings.type_v2"), ::OpenProject::Recaptcha::TYPE_V2],
[I18n.t("recaptcha.settings.type_v3"), ::OpenProject::Recaptcha::TYPE_V3],
[I18n.t("recaptcha.settings.type_hcaptcha"), ::OpenProject::Recaptcha::TYPE_HCAPTCHA]
[I18n.t("recaptcha.settings.type_hcaptcha"), ::OpenProject::Recaptcha::TYPE_HCAPTCHA],
[I18n.t("recaptcha.settings.type_turnstile"), ::OpenProject::Recaptcha::TYPE_TURNSTILE]

]
end

Expand Down
3 changes: 2 additions & 1 deletion modules/recaptcha/app/views/recaptcha/admin/show.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@
<div class="form--field-instructions">
<%= I18n.t('recaptcha.settings.recaptcha_description_html',
hcaptcha_link: link_to('https://docs.hcaptcha.com/switch/', 'https://docs.hcaptcha.com/switch/', target: '_blank'),
recaptcha_link: link_to('https://www.google.com/recaptcha', 'https://www.google.com/recaptcha', target: '_blank')).html_safe %>
recaptcha_link: link_to('https://www.google.com/recaptcha', 'https://www.google.com/recaptcha', target: '_blank'),
turnstile_link: link_to('https://developers.cloudflare.com/turnstile/', 'https://developers.cloudflare.com/turnstile/', target: '_blank')).html_safe %>
</div>
</div>
<div class="form--field">
Expand Down
21 changes: 21 additions & 0 deletions modules/recaptcha/app/views/recaptcha/request/perform.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,27 @@
document.getElementById('submit_captcha').submit();
}
<% end %>
<% elsif recaptcha_settings['recaptcha_type'] == ::OpenProject::Recaptcha::TYPE_TURNSTILE %>
<% input_name = "turnstile-response" %>
<input type="hidden" name="<%= input_name %>" />
<%= nonced_javascript_include_tag "https://challenges.cloudflare.com/turnstile/v0/api.js?onload=onloadTurnstileCallback" %>

<div id="turnstile-container"></div>
<%= nonced_javascript_tag do %>
function submitTurnstileForm(token) {
var input = document.getElementsByName('<%= input_name %>')[0];

input.value = token;
document.getElementById('submit_captcha').submit();
}

window.onloadTurnstileCallback = function () {
turnstile.render('#turnstile-container', {
sitekey: '<%= recaptcha_settings['website_key'] %>',
callback: submitTurnstileForm,
});
};
<% end %>
<% end %>
<% end %>
</div>
6 changes: 5 additions & 1 deletion modules/recaptcha/config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ en:
verify_account: "Verify your account"
error_captcha: "Your account could not be verified. Please contact an administrator."
settings:
website_key: 'Website key'
website_key: 'Website key (May also be called "Site key")'
response_limit: 'Response limit for HCaptcha'
response_limit_text: 'The maximum number of characters to treat the HCaptcha response as valid.'
website_key_text: 'Enter the website key you created on the reCAPTCHA admin console for this domain.'
Expand All @@ -20,6 +20,7 @@ en:
type_v2: 'reCAPTCHA v2'
type_v3: 'reCAPTCHA v3'
type_hcaptcha: 'HCaptcha'
type_turnstile: 'Cloudflare Turnstile™'
recaptcha_description_html: >
reCAPTCHA is a free service by Google that can be enabled for your OpenProject instance.
If enabled, a captcha form will be rendered upon login for all users that have not verified a captcha yet.
Expand All @@ -29,3 +30,6 @@ en:
<br/>
HCaptcha is a Google-free alternative that you can use if you do not want to use reCAPTCHA.
See this link for more information: %{hcaptcha_link}
<br/>
Cloudflare Turnstile™ is another alternative that is more convenient for users while still providing the same level of security.
See this link for more information: %{turnstile_link}
1 change: 1 addition & 0 deletions modules/recaptcha/lib/open_project/recaptcha.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
TYPE_V2 ||= "v2"
TYPE_V3 ||= "v3"
TYPE_HCAPTCHA ||= "hcaptcha"
TYPE_TURNSTILE ||= "turnstile"

Check warning on line 7 in modules/recaptcha/lib/open_project/recaptcha.rb

View workflow job for this annotation

GitHub Actions / rubocop

[rubocop] reported by reviewdog 🐶 Avoid using or-assignment with constants. Raw Output: modules/recaptcha/lib/open_project/recaptcha.rb:7:20: W: Lint/OrAssignmentToConstant: Avoid using or-assignment with constants.

Check warning on line 7 in modules/recaptcha/lib/open_project/recaptcha.rb

View workflow job for this annotation

GitHub Actions / rubocop

[rubocop] reported by reviewdog 🐶 Freeze mutable objects assigned to constants. Raw Output: modules/recaptcha/lib/open_project/recaptcha.rb:7:24: C: Style/MutableConstant: Freeze mutable objects assigned to constants.

require "open_project/recaptcha/engine"
require "open_project/recaptcha/configuration"
Expand Down
8 changes: 8 additions & 0 deletions modules/recaptcha/lib/open_project/recaptcha/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,14 @@ def use_hcaptcha?
type == ::OpenProject::Recaptcha::TYPE_HCAPTCHA
end

def use_turnstile?
type == ::OpenProject::Recaptcha::TYPE_TURNSTILE
end

def use_recaptcha?
type == ::OpenProject::Recaptcha::TYPE_V2 || type == ::OpenProject::Recaptcha::TYPE_V3
end

def type
::Setting.plugin_openproject_recaptcha["recaptcha_type"]
end
Expand Down
7 changes: 7 additions & 0 deletions modules/recaptcha/lib/open_project/recaptcha/engine.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,13 @@ class Engine < ::Rails::Engine
keys.index_with value
end

SecureHeaders::Configuration.named_append(:turnstile) do
value = %w(https://challenges.cloudflare.com)
keys = %i(frame_src style_src connect_src)

keys.index_with value
end

OpenProject::Authentication::Stage.register(
:recaptcha,
nil,
Expand Down
Loading