diff --git a/app/controllers/api/v1/accounts_controller.rb b/app/controllers/api/v1/accounts_controller.rb
index f1755b921..e75d3f852 100644
--- a/app/controllers/api/v1/accounts_controller.rb
+++ b/app/controllers/api/v1/accounts_controller.rb
@@ -4,6 +4,7 @@ class Api::V1::AccountsController < Api::BaseController
skip_before_action :authenticate_user!, :set_current_user, :handle_with_exception,
only: [:create], raise: false
before_action :check_signup_enabled, only: [:create]
+ before_action :validate_captcha, only: [:create]
before_action :fetch_account, except: [:create]
before_action :check_authorization, except: [:create]
@@ -58,6 +59,10 @@ class Api::V1::AccountsController < Api::BaseController
raise ActionController::RoutingError, 'Not Found' if GlobalConfigService.load('ENABLE_ACCOUNT_SIGNUP', 'false') == 'false'
end
+ def validate_captcha
+ raise ActionController::InvalidAuthenticityToken, 'Invalid Captcha' unless ChatwootCaptcha.new(params[:h_captcha_client_response]).valid?
+ end
+
def pundit_user
{
user: current_user,
diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb
index 52c6c87af..b76ad4015 100644
--- a/app/controllers/dashboard_controller.rb
+++ b/app/controllers/dashboard_controller.rb
@@ -26,7 +26,8 @@ class DashboardController < ActionController::Base
'API_CHANNEL_THUMBNAIL',
'ANALYTICS_TOKEN',
'ANALYTICS_HOST',
- 'DIRECT_UPLOADS_ENABLED'
+ 'DIRECT_UPLOADS_ENABLED',
+ 'HCAPTCHA_SITE_KEY'
).merge(app_config)
end
diff --git a/app/javascript/dashboard/api/auth.js b/app/javascript/dashboard/api/auth.js
index 7079614c1..18ec9e811 100644
--- a/app/javascript/dashboard/api/auth.js
+++ b/app/javascript/dashboard/api/auth.js
@@ -30,6 +30,7 @@ export default {
user_full_name: creds.fullName.trim(),
email: creds.email,
password: creds.password,
+ h_captcha_client_response: creds.hCaptchaClientResponse,
})
.then(response => {
setAuthCredentials(response);
diff --git a/app/javascript/dashboard/routes/auth/Signup.vue b/app/javascript/dashboard/routes/auth/Signup.vue
index c175e7b9e..36b63662d 100644
--- a/app/javascript/dashboard/routes/auth/Signup.vue
+++ b/app/javascript/dashboard/routes/auth/Signup.vue
@@ -75,8 +75,14 @@
"
@blur="$v.credentials.confirmPassword.$touch"
/>
+
+
+
@@ -234,5 +251,9 @@ export default {
text-align: center;
margin: var(--space-normal) 0 0 0;
}
+
+ .h-captcha--box {
+ margin-bottom: var(--space-one);
+ }
}
diff --git a/app/javascript/shared/store/globalConfig.js b/app/javascript/shared/store/globalConfig.js
index 9082db4ac..eaf88fedc 100644
--- a/app/javascript/shared/store/globalConfig.js
+++ b/app/javascript/shared/store/globalConfig.js
@@ -7,6 +7,7 @@ const {
CREATE_NEW_ACCOUNT_FROM_DASHBOARD: createNewAccountFromDashboard,
DIRECT_UPLOADS_ENABLED: directUploadsEnabled,
DISPLAY_MANIFEST: displayManifest,
+ HCAPTCHA_SITE_KEY: hCaptchaSiteKey,
INSTALLATION_NAME: installationName,
LOGO_THUMBNAIL: logoThumbnail,
LOGO: logo,
@@ -24,6 +25,7 @@ const state = {
createNewAccountFromDashboard,
directUploadsEnabled: directUploadsEnabled === 'true',
displayManifest,
+ hCaptchaSiteKey,
installationName,
logo,
logoThumbnail,
diff --git a/config/installation_config.yml b/config/installation_config.yml
index fedf4fc0e..5e22e1a4f 100644
--- a/config/installation_config.yml
+++ b/config/installation_config.yml
@@ -47,3 +47,9 @@
- name: DIRECT_UPLOADS_ENABLED
value: false
locked: false
+- name: HCAPTCHA_SITE_KEY
+ value:
+ locked: false
+- name: HCAPTCHA_SERVER_KEY
+ value:
+ locked: false
diff --git a/db/migrate/20220218120357_add_h_captcha_key.rb b/db/migrate/20220218120357_add_h_captcha_key.rb
new file mode 100644
index 000000000..02b06e5d3
--- /dev/null
+++ b/db/migrate/20220218120357_add_h_captcha_key.rb
@@ -0,0 +1,5 @@
+class AddHCaptchaKey < ActiveRecord::Migration[6.1]
+ def change
+ ConfigLoader.new.process
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index b1f6775b3..c1cd951ab 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 2022_02_15_060751) do
+ActiveRecord::Schema.define(version: 2022_02_18_120357) do
# These are extensions that must be enabled in order to support this database
enable_extension "pg_stat_statements"
diff --git a/lib/chatwoot_captcha.rb b/lib/chatwoot_captcha.rb
new file mode 100644
index 000000000..37e852868
--- /dev/null
+++ b/lib/chatwoot_captcha.rb
@@ -0,0 +1,25 @@
+class ChatwootCaptcha
+ def initialize(client_response)
+ @client_response = client_response
+ @server_key = GlobalConfigService.load('HCAPTCHA_SERVER_KEY', '')
+ end
+
+ def valid?
+ return true if @server_key.blank?
+ return false if @client_response.blank?
+
+ validate_client_response?
+ end
+
+ def validate_client_response?
+ response = HTTParty.post('https://hcaptcha.com/siteverify',
+ body: {
+ response: @client_response,
+ secret: @server_key
+ })
+
+ return unless response.success?
+
+ response.parsed_response['success']
+ end
+end
diff --git a/package.json b/package.json
index 2c36a7284..e75b12b81 100644
--- a/package.json
+++ b/package.json
@@ -20,6 +20,7 @@
"@braid/vue-formulate": "^2.5.2",
"@chatwoot/prosemirror-schema": "https://github.com/chatwoot/prosemirror-schema.git#7e8acadd10d7b932c0dc0bd0a18f804434f83517",
"@chatwoot/utils": "^0.0.3",
+ "@hcaptcha/vue-hcaptcha": "^0.3.2",
"@rails/actioncable": "6.1.3",
"@rails/webpacker": "5.3.0",
"@sentry/tracing": "^6.4.1",
diff --git a/spec/controllers/api/v1/accounts_controller_spec.rb b/spec/controllers/api/v1/accounts_controller_spec.rb
index e86834d88..a82bd515d 100644
--- a/spec/controllers/api/v1/accounts_controller_spec.rb
+++ b/spec/controllers/api/v1/accounts_controller_spec.rb
@@ -30,6 +30,25 @@ RSpec.describe 'Accounts API', type: :request do
end
end
+ it 'calls ChatwootCaptcha' do
+ with_modified_env ENABLE_ACCOUNT_SIGNUP: 'true' do
+ captcha = double
+ allow(account_builder).to receive(:perform).and_return([user, account])
+ allow(ChatwootCaptcha).to receive(:new).and_return(captcha)
+ allow(captcha).to receive(:valid?).and_return(true)
+
+ params = { account_name: 'test', email: email, user: nil, user_full_name: user_full_name, password: 'Password1!',
+ h_captcha_client_response: '123' }
+
+ post api_v1_accounts_url,
+ params: params,
+ as: :json
+
+ expect(ChatwootCaptcha).to have_received(:new).with('123')
+ expect(response.headers.keys).to include('access-token', 'token-type', 'client', 'expiry', 'uid')
+ end
+ end
+
it 'renders error response on invalid params' do
with_modified_env ENABLE_ACCOUNT_SIGNUP: 'true' do
allow(account_builder).to receive(:perform).and_return(nil)
diff --git a/spec/lib/chatwoot_captcha_spec.rb b/spec/lib/chatwoot_captcha_spec.rb
new file mode 100644
index 000000000..3b35c872e
--- /dev/null
+++ b/spec/lib/chatwoot_captcha_spec.rb
@@ -0,0 +1,25 @@
+require 'rails_helper'
+
+describe ChatwootCaptcha do
+ it 'returns true if HCAPTCHA SERVER KEY is absent' do
+ expect(described_class.new('random_key').valid?).to eq(true)
+ end
+
+ context 'when HCAPTCHA SERVER KEY is present' do
+ before do
+ create(:installation_config, { name: 'HCAPTCHA_SERVER_KEY', value: 'hcaptch_server_key' })
+ end
+
+ it 'returns false if client response is blank' do
+ expect(described_class.new('').valid?).to eq false
+ end
+
+ it 'returns true if client response is valid' do
+ captcha_request = double
+ allow(HTTParty).to receive(:post).and_return(captcha_request)
+ allow(captcha_request).to receive(:success?).and_return(true)
+ allow(captcha_request).to receive(:parsed_response).and_return({ 'success' => true })
+ expect(described_class.new('valid_response').valid?).to eq true
+ end
+ end
+end
diff --git a/yarn.lock b/yarn.lock
index f296da282..6463d91ce 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1283,6 +1283,11 @@
postcss "7.0.32"
purgecss "^2.3.0"
+"@hcaptcha/vue-hcaptcha@^0.3.2":
+ version "0.3.2"
+ resolved "https://registry.yarnpkg.com/@hcaptcha/vue-hcaptcha/-/vue-hcaptcha-0.3.2.tgz#0f77d6fc19bc47eadb6b2181eee5fc132441a942"
+ integrity sha512-JiJsAJh+fSe+uf9N3ek7CKzX/r79+hx+rMPch+e2/h9+Ei3VyJtb2Dgk1DhG/dyUdrooPIzkNMr6gfo75Cn22g==
+
"@istanbuljs/load-nyc-config@^1.0.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced"