diff --git a/cloud_captchas/MANIFEST.in b/cloud_captchas/MANIFEST.in
new file mode 100644
index 0000000..fc89d1d
--- /dev/null
+++ b/cloud_captchas/MANIFEST.in
@@ -0,0 +1,4 @@
+graft indico_cloud_captchas/static
+graft indico_cloud_captchas/translations
+
+global-exclude *.pyc __pycache__ .keep
diff --git a/cloud_captchas/README.md b/cloud_captchas/README.md
new file mode 100644
index 0000000..e6856f3
--- /dev/null
+++ b/cloud_captchas/README.md
@@ -0,0 +1,27 @@
+# Cloud CAPTCHAs Plugin
+
+This plugin replaces the built-in CAPTCHA with Google's reCAPTCHA v2 or hCaptcha.
+
+This plugin also serves as an example for developers who want to use a different
+CAPTCHA for their Indico instance.
+
+
+
+
+## Setup
+
+The plugin requires you to set the site key and secret key on the plugin settings page.
+
+When using reCaptcha these keys can be created on the [reCAPTCHA admin dashboard][recaptcha-create].
+Choose **reCAPTCHA v2** and **"I'm not a robot" Checkbox**.
+
+When using hCaptcha the keys can be created on the [hCaptcha dashboard][hcaptcha-dashboard].
+
+## Changelog
+
+### 3.2
+
+- Initial release for Indico 3.2
+
+[recaptcha-create]: https://www.google.com/recaptcha/admin/create
+[hcaptcha-dashboard]: https://dashboard.hcaptcha.com/overview
diff --git a/recaptcha/indico_recaptcha/__init__.py b/cloud_captchas/indico_cloud_captchas/__init__.py
similarity index 87%
rename from recaptcha/indico_recaptcha/__init__.py
rename to cloud_captchas/indico_cloud_captchas/__init__.py
index 1e789e6..31a28af 100644
--- a/recaptcha/indico_recaptcha/__init__.py
+++ b/cloud_captchas/indico_cloud_captchas/__init__.py
@@ -8,4 +8,4 @@
from indico.util.i18n import make_bound_gettext
-_ = make_bound_gettext('recaptcha')
+_ = make_bound_gettext('cloud_captchas')
diff --git a/recaptcha/indico_recaptcha/client/Captcha.jsx b/cloud_captchas/indico_cloud_captchas/client/CloudCaptcha.jsx
similarity index 71%
rename from recaptcha/indico_recaptcha/client/Captcha.jsx
rename to cloud_captchas/indico_cloud_captchas/client/CloudCaptcha.jsx
index 6896325..110b41f 100644
--- a/recaptcha/indico_recaptcha/client/Captcha.jsx
+++ b/cloud_captchas/indico_cloud_captchas/client/CloudCaptcha.jsx
@@ -5,6 +5,7 @@
// them and/or modify them under the terms of the MIT License;
// see the LICENSE file for more details.
+import HCaptcha from '@hcaptcha/react-hcaptcha';
import PropTypes from 'prop-types';
import React, {useState, useEffect, useRef, useCallback} from 'react';
import {useFormState, useForm} from 'react-final-form';
@@ -14,44 +15,55 @@ import {Message, Form} from 'semantic-ui-react';
import {FinalField} from 'indico/react/forms';
import {Translate} from 'indico/react/i18n';
-import './Captcha.module.scss';
+import './CloudCaptcha.module.scss';
-export default function Captcha({name, settings: {siteKey}, wtf}) {
+export default function CloudCaptcha({name, settings: {siteKey, hCaptcha}, wtf}) {
return wtf ? (
-
+
) : (
-
+
);
}
-Captcha.propTypes = {
+CloudCaptcha.propTypes = {
name: PropTypes.string,
wtf: PropTypes.bool,
settings: PropTypes.shape({
siteKey: PropTypes.string.isRequired,
+ hCaptcha: PropTypes.bool.isRequired,
}).isRequired,
};
-Captcha.defaultProps = {
+CloudCaptcha.defaultProps = {
name: 'captcha',
wtf: false,
};
-function CaptchaField({onChange, siteKey, reCaptchaRef}) {
- return ;
+function CloudCaptchaField({onChange, siteKey, hCaptcha, reCaptchaRef}) {
+ return hCaptcha ? (
+ onChange(null)}
+ ref={reCaptchaRef}
+ />
+ ) : (
+
+ );
}
-CaptchaField.propTypes = {
+CloudCaptchaField.propTypes = {
onChange: PropTypes.func.isRequired,
siteKey: PropTypes.string.isRequired,
+ hCaptcha: PropTypes.bool.isRequired,
reCaptchaRef: PropTypes.object,
};
-CaptchaField.defaultProps = {
+CloudCaptchaField.defaultProps = {
reCaptchaRef: undefined,
};
-function WTFCaptcha({name, siteKey}) {
+function WTFCloudCaptcha({name, siteKey, hCaptcha}) {
const fieldRef = useRef(null);
const [response, setResponse] = useState('');
const [hasError, setError] = useState(false);
@@ -88,7 +100,7 @@ function WTFCaptcha({name, siteKey}) {
-
+
@@ -96,12 +108,13 @@ function WTFCaptcha({name, siteKey}) {
);
}
-WTFCaptcha.propTypes = {
+WTFCloudCaptcha.propTypes = {
name: PropTypes.string.isRequired,
siteKey: PropTypes.string.isRequired,
+ hCaptcha: PropTypes.bool.isRequired,
};
-function FinalCaptcha({name, siteKey}) {
+function FinalCloudCaptcha({name, siteKey, hCaptcha}) {
const reCaptchaRef = useRef(null);
const form = useForm();
const {submitErrors} = useFormState({
@@ -132,8 +145,9 @@ function FinalCaptcha({name, siteKey}) {
@@ -142,7 +156,8 @@ function FinalCaptcha({name, siteKey}) {
);
}
-FinalCaptcha.propTypes = {
+FinalCloudCaptcha.propTypes = {
name: PropTypes.string.isRequired,
siteKey: PropTypes.string.isRequired,
+ hCaptcha: PropTypes.bool.isRequired,
};
diff --git a/recaptcha/indico_recaptcha/client/Captcha.module.scss b/cloud_captchas/indico_cloud_captchas/client/CloudCaptcha.module.scss
similarity index 100%
rename from recaptcha/indico_recaptcha/client/Captcha.module.scss
rename to cloud_captchas/indico_cloud_captchas/client/CloudCaptcha.module.scss
diff --git a/recaptcha/indico_recaptcha/client/i18n.js b/cloud_captchas/indico_cloud_captchas/client/i18n.js
similarity index 83%
rename from recaptcha/indico_recaptcha/client/i18n.js
rename to cloud_captchas/indico_cloud_captchas/client/i18n.js
index c4abf17..dd00712 100644
--- a/recaptcha/indico_recaptcha/client/i18n.js
+++ b/cloud_captchas/indico_cloud_captchas/client/i18n.js
@@ -7,7 +7,7 @@
import {bindTranslateComponents} from 'indico/react/i18n';
-const {Translate, PluralTranslate} = bindTranslateComponents('cern_access');
+const {Translate, PluralTranslate} = bindTranslateComponents('cloud_captchas');
export {Translate, PluralTranslate};
export {Singular, Plural, Param} from 'react-jsx-i18n';
diff --git a/recaptcha/indico_recaptcha/client/index.js b/cloud_captchas/indico_cloud_captchas/client/index.js
similarity index 73%
rename from recaptcha/indico_recaptcha/client/index.js
rename to cloud_captchas/indico_cloud_captchas/client/index.js
index 5531e50..8c86ec6 100644
--- a/recaptcha/indico_recaptcha/client/index.js
+++ b/cloud_captchas/indico_cloud_captchas/client/index.js
@@ -7,6 +7,6 @@
import {registerPluginComponent} from 'indico/utils/plugins';
-import Captcha from './Captcha';
+import CloudCaptcha from './CloudCaptcha';
-registerPluginComponent('recaptcha', 'captcha', Captcha);
+registerPluginComponent('cloud_captchas', 'captcha', CloudCaptcha);
diff --git a/cloud_captchas/indico_cloud_captchas/plugin.py b/cloud_captchas/indico_cloud_captchas/plugin.py
new file mode 100644
index 0000000..b378236
--- /dev/null
+++ b/cloud_captchas/indico_cloud_captchas/plugin.py
@@ -0,0 +1,143 @@
+# This file is part of the Indico plugins.
+# Copyright (C) 2002 - 2022 CERN
+#
+# The Indico plugins are free software; you can redistribute
+# them and/or modify them under the terms of the MIT License;
+# see the LICENSE file for more details.
+
+import requests
+from requests.exceptions import HTTPError, RequestException
+from wtforms.fields import StringField
+from wtforms.validators import DataRequired
+
+from indico.core.plugins import IndicoPlugin
+from indico.modules.api.forms import IndicoEnumSelectField
+from indico.modules.core.plugins import CaptchaPluginMixin
+from indico.modules.users import EnumConverter
+from indico.util.enum import RichIntEnum
+from indico.web.forms.base import IndicoForm
+from indico.web.forms.validators import HiddenUnless
+from indico.web.views import WPBase
+
+from indico_cloud_captchas import _
+
+
+class CaptchaProvider(RichIntEnum):
+ __titles__ = [_("None (use Indico's built-in CAPTCHA)"), 'reCAPTCHA', 'hCaptcha']
+ none = 0
+ recaptcha = 1
+ hcaptcha = 2
+
+
+class CloudCaptchasSettingsForm(IndicoForm):
+ provider = IndicoEnumSelectField(
+ _('Type'), enum=CaptchaProvider,
+ description=_('Select which CAPTCHA provider you want to use')
+ )
+ # recaptcha
+ recaptcha_site_key = StringField(
+ _('reCAPTCHA site key'),
+ [HiddenUnless('provider', CaptchaProvider.recaptcha, preserve_data=True), DataRequired()],
+ description=_('The site key available in the reCAPTCHA admin dashboard')
+ )
+ recaptcha_secret_key = StringField(
+ _('reCAPTCHA secret key'),
+ [HiddenUnless('provider', CaptchaProvider.recaptcha, preserve_data=True), DataRequired()],
+ description=_('The secret key available in the reCAPTCHA admin dashboard')
+ )
+ # hcaptcha
+ hcaptcha_site_key = StringField(
+ _('hCaptcha site key'),
+ [HiddenUnless('provider', CaptchaProvider.hcaptcha, preserve_data=True), DataRequired()],
+ description=_('The site key available in the hCaptcha admin dashboard')
+ )
+ hcaptcha_secret_key = StringField(
+ _('reCAPTCHA secret key'),
+ [HiddenUnless('provider', CaptchaProvider.hcaptcha, preserve_data=True), DataRequired()],
+ description=_('The secret key available in the hCaptcha admin dashboard')
+ )
+
+
+class CloudCaptchasPlugin(CaptchaPluginMixin, IndicoPlugin):
+ """Cloud CAPTCHAs
+
+ Replaces Indico's default CAPTCHA with reCAPTCHA or hCaptcha.
+ """
+
+ configurable = True
+ settings_form = CloudCaptchasSettingsForm
+ default_settings = {
+ 'provider': CaptchaProvider.none,
+ 'recaptcha_site_key': '',
+ 'recaptcha_secret_key': '',
+ 'hcaptcha_site_key': '',
+ 'hcaptcha_secret_key': '',
+ }
+ settings_converters = {
+ 'provider': EnumConverter(CaptchaProvider),
+ }
+
+ def init(self):
+ super().init()
+ # TODO split hcaptcha/recaptcha (hcaptcha is pretty big since it includes react)
+ self.inject_bundle('main.js', WPBase, condition=lambda: self.settings.get('provider') != CaptchaProvider.none)
+ self.inject_bundle('main.css', WPBase, condition=lambda: self.settings.get('provider') != CaptchaProvider.none)
+
+ def is_captcha_available(self):
+ provider = self.settings.get('provider')
+ if provider == CaptchaProvider.recaptcha:
+ return bool(self.settings.get('recaptcha_site_key') and self.settings.get('recaptcha_secret_key'))
+ elif provider == CaptchaProvider.hcaptcha:
+ return bool(self.settings.get('hcaptcha_site_key') and self.settings.get('hcaptcha_secret_key'))
+ return False
+
+ def _validate_recaptcha(self, answer):
+ data = {
+ 'secret': self.settings.get('recaptcha_secret_key'),
+ 'response': answer
+ }
+ if resp := self._validate_http_post('https://www.google.com/recaptcha/api/siteverify', data):
+ return resp.json()['success']
+ return False
+
+ def _validate_hcaptcha(self, answer):
+ data = {
+ 'sitekey': self.settings.get('hcaptcha_site_key'),
+ 'secret': self.settings.get('hcaptcha_secret_key'),
+ 'response': answer
+ }
+ if resp := self._validate_http_post('https://hcaptcha.com/siteverify', data):
+ return resp.json()['success']
+ return False
+
+ def _validate_http_post(self, url, data):
+ try:
+ resp = requests.post(url, data=data)
+ resp.raise_for_status()
+ except HTTPError as exc:
+ self.logger.error('Failed to validate CAPTCHA: %s', exc.response.text)
+ return None
+ except RequestException as exc:
+ self.logger.error('Failed to validate CAPTCHA: %s', exc)
+ return None
+ return resp
+
+ def validate_captcha(self, answer):
+ if self.settings.get('provider') == CaptchaProvider.recaptcha:
+ return self._validate_recaptcha(answer)
+ elif self.settings.get('provider') == CaptchaProvider.hcaptcha:
+ return self._validate_hcaptcha(answer)
+ # should never happen
+ return False
+
+ def get_captcha_settings(self):
+ if self.settings.get('provider') == CaptchaProvider.recaptcha:
+ return {
+ 'siteKey': self.settings.get('recaptcha_site_key'),
+ 'hCaptcha': False,
+ }
+ elif self.settings.get('provider') == CaptchaProvider.hcaptcha:
+ return {
+ 'siteKey': self.settings.get('hcaptcha_site_key'),
+ 'hCaptcha': True,
+ }
diff --git a/recaptcha/package-lock.json b/cloud_captchas/package-lock.json
similarity index 63%
rename from recaptcha/package-lock.json
rename to cloud_captchas/package-lock.json
index 4ba4adb..d35fa9f 100644
--- a/recaptcha/package-lock.json
+++ b/cloud_captchas/package-lock.json
@@ -1,16 +1,38 @@
{
- "name": "recaptcha",
- "version": "1.0.0",
+ "name": "indico-plugin-cloud-captchas",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
- "name": "recaptcha",
- "version": "1.0.0",
+ "name": "indico-plugin-cloud-captchas",
"dependencies": {
+ "@hcaptcha/react-hcaptcha": "^1.4.4",
"react-google-recaptcha": "^2.1.0"
}
},
+ "node_modules/@babel/runtime": {
+ "version": "7.18.9",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.18.9.tgz",
+ "integrity": "sha512-lkqXDcvlFT5rvEjiu6+QYO+1GXrEHRo2LOtS7E4GtX5ESIZOgepqsZBVIj6Pv+a6zqsya9VCgiK1KAK4BvJDAw==",
+ "dependencies": {
+ "regenerator-runtime": "^0.13.4"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@hcaptcha/react-hcaptcha": {
+ "version": "1.4.4",
+ "resolved": "https://registry.npmjs.org/@hcaptcha/react-hcaptcha/-/react-hcaptcha-1.4.4.tgz",
+ "integrity": "sha512-Aen217LDnf5ywbPSwBG5CsoqBLIHIAS9lhj3zQjXJuO13doQ6/ubkCWNuY8jmwYLefoFt3V3MrZmCdKDaFoTuQ==",
+ "dependencies": {
+ "@babel/runtime": "^7.17.9"
+ },
+ "peerDependencies": {
+ "react": ">= 16.3.0",
+ "react-dom": ">= 16.3.0"
+ }
+ },
"node_modules/hoist-non-react-statics": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
@@ -77,6 +99,19 @@
"react": ">=16.4.1"
}
},
+ "node_modules/react-dom": {
+ "version": "18.2.0",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
+ "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==",
+ "peer": true,
+ "dependencies": {
+ "loose-envify": "^1.1.0",
+ "scheduler": "^0.23.0"
+ },
+ "peerDependencies": {
+ "react": "^18.2.0"
+ }
+ },
"node_modules/react-google-recaptcha": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/react-google-recaptcha/-/react-google-recaptcha-2.1.0.tgz",
@@ -93,9 +128,39 @@
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
+ },
+ "node_modules/regenerator-runtime": {
+ "version": "0.13.9",
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz",
+ "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA=="
+ },
+ "node_modules/scheduler": {
+ "version": "0.23.0",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz",
+ "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==",
+ "peer": true,
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ }
}
},
"dependencies": {
+ "@babel/runtime": {
+ "version": "7.18.9",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.18.9.tgz",
+ "integrity": "sha512-lkqXDcvlFT5rvEjiu6+QYO+1GXrEHRo2LOtS7E4GtX5ESIZOgepqsZBVIj6Pv+a6zqsya9VCgiK1KAK4BvJDAw==",
+ "requires": {
+ "regenerator-runtime": "^0.13.4"
+ }
+ },
+ "@hcaptcha/react-hcaptcha": {
+ "version": "1.4.4",
+ "resolved": "https://registry.npmjs.org/@hcaptcha/react-hcaptcha/-/react-hcaptcha-1.4.4.tgz",
+ "integrity": "sha512-Aen217LDnf5ywbPSwBG5CsoqBLIHIAS9lhj3zQjXJuO13doQ6/ubkCWNuY8jmwYLefoFt3V3MrZmCdKDaFoTuQ==",
+ "requires": {
+ "@babel/runtime": "^7.17.9"
+ }
+ },
"hoist-non-react-statics": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
@@ -150,6 +215,16 @@
"prop-types": "^15.5.0"
}
},
+ "react-dom": {
+ "version": "18.2.0",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
+ "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==",
+ "peer": true,
+ "requires": {
+ "loose-envify": "^1.1.0",
+ "scheduler": "^0.23.0"
+ }
+ },
"react-google-recaptcha": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/react-google-recaptcha/-/react-google-recaptcha-2.1.0.tgz",
@@ -163,6 +238,20 @@
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
+ },
+ "regenerator-runtime": {
+ "version": "0.13.9",
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz",
+ "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA=="
+ },
+ "scheduler": {
+ "version": "0.23.0",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz",
+ "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==",
+ "peer": true,
+ "requires": {
+ "loose-envify": "^1.1.0"
+ }
}
}
}
diff --git a/cloud_captchas/package.json b/cloud_captchas/package.json
new file mode 100644
index 0000000..7e2ee0d
--- /dev/null
+++ b/cloud_captchas/package.json
@@ -0,0 +1,8 @@
+{
+ "private": true,
+ "name": "indico-plugin-cloud-captchas",
+ "dependencies": {
+ "@hcaptcha/react-hcaptcha": "^1.4.4",
+ "react-google-recaptcha": "^2.1.0"
+ }
+}
diff --git a/recaptcha/recaptcha.png b/cloud_captchas/recaptcha.png
similarity index 100%
rename from recaptcha/recaptcha.png
rename to cloud_captchas/recaptcha.png
diff --git a/recaptcha/setup.cfg b/cloud_captchas/setup.cfg
similarity index 87%
rename from recaptcha/setup.cfg
rename to cloud_captchas/setup.cfg
index 428af37..5b80e78 100644
--- a/recaptcha/setup.cfg
+++ b/cloud_captchas/setup.cfg
@@ -1,5 +1,5 @@
[metadata]
-name = indico-plugin-recaptcha
+name = indico-plugin-cloud-captchas
version = 3.2-dev
description = Google reCAPTCHA plugin for Indico
long_description = file: README.md
@@ -24,7 +24,7 @@ install_requires =
[options.entry_points]
indico.plugins =
- recaptcha = indico_recaptcha.plugin:ReCaptchaPlugin
+ cloud_captchas = indico_cloud_captchas.plugin:CloudCaptchasPlugin
diff --git a/recaptcha/setup.py b/cloud_captchas/setup.py
similarity index 100%
rename from recaptcha/setup.py
rename to cloud_captchas/setup.py
diff --git a/recaptcha/webpack-bundles.json b/cloud_captchas/webpack-bundles.json
similarity index 100%
rename from recaptcha/webpack-bundles.json
rename to cloud_captchas/webpack-bundles.json
diff --git a/recaptcha/MANIFEST.in b/recaptcha/MANIFEST.in
deleted file mode 100644
index a5c60ec..0000000
--- a/recaptcha/MANIFEST.in
+++ /dev/null
@@ -1,4 +0,0 @@
-graft indico_recaptcha/static
-graft indico_recaptcha/translations
-
-global-exclude *.pyc __pycache__ .keep
diff --git a/recaptcha/README.md b/recaptcha/README.md
deleted file mode 100644
index 4f0ed09..0000000
--- a/recaptcha/README.md
+++ /dev/null
@@ -1,23 +0,0 @@
-# Google reCAPTCHA Plugin
-
-This plugin replaces the built-in CAPTCHA with Google's reCAPTCHA v2.
-
-This plugin also serves as an example for developers who want to use a
-different CAPTCHA for their Indico instance.
-
-
-
-
-## Setup
-
-The plugin requires you to set the reCAPTCHA site key and secret key on the plugin
-settings page. These keys can be created on the [reCAPTCHA admin dashboard][recaptcha-create].
-Choose **reCAPTCHA v2** and **"I'm not a robot" Checkbox**.
-
-## Changelog
-
-### 3.2
-
-- Initial release for Indico 3.2
-
-[recaptcha-create]: https://www.google.com/recaptcha/admin/create
diff --git a/recaptcha/indico_recaptcha/plugin.py b/recaptcha/indico_recaptcha/plugin.py
deleted file mode 100644
index 0250a64..0000000
--- a/recaptcha/indico_recaptcha/plugin.py
+++ /dev/null
@@ -1,64 +0,0 @@
-# This file is part of the Indico plugins.
-# Copyright (C) 2002 - 2022 CERN
-#
-# The Indico plugins are free software; you can redistribute
-# them and/or modify them under the terms of the MIT License;
-# see the LICENSE file for more details.
-
-import requests
-from requests.exceptions import RequestException
-from wtforms.fields import BooleanField, StringField
-from wtforms.validators import DataRequired
-
-from indico.core.plugins import IndicoPlugin
-from indico.modules.core.plugins import CaptchaPluginMixin
-from indico.web.forms.base import IndicoForm
-from indico.web.forms.widgets import SwitchWidget
-from indico.web.views import WPBase
-
-from indico_recaptcha import _
-
-
-class ReCaptchaSettingsForm(IndicoForm):
- enabled = BooleanField(_('Enabled'), widget=SwitchWidget(), description=_('Whether to enable the access overrides'))
- site_key = StringField(_('Site key'), [DataRequired()],
- description=_('The site key available in the reCAPTCHA admin dashboard'))
- secret_key = StringField(_('Secret key'), [DataRequired()],
- description=_('The secret key available in the reCAPTCHA admin dashboard'))
-
-
-class ReCaptchaPlugin(CaptchaPluginMixin, IndicoPlugin):
- """Google reCAPTCHA
-
- Replaces Indico's default CAPTCHA with Google reCAPTCHA.
- """
-
- configurable = True
- settings_form = ReCaptchaSettingsForm
- default_settings = {
- 'enabled': False,
- 'site_key': '',
- 'secret_key': '',
- }
-
- def init(self):
- super().init()
- self.inject_bundle('main.js', WPBase, condition=lambda: self.settings.get('enabled'))
- self.inject_bundle('main.css', WPBase, condition=lambda: self.settings.get('enabled'))
-
- def is_captcha_available(self):
- return self.settings.get('enabled') and bool(self.settings.get('site_key'))
-
- def validate_captcha(self, answer):
- secret = self.settings.get('secret_key')
- resp = requests.post('https://www.google.com/recaptcha/api/siteverify',
- data={'secret': secret, 'response': answer})
- try:
- resp.raise_for_status()
- except RequestException as exc:
- self.logger.error('Failed to validate CAPTCHA: %s', exc.response.text)
- return False
- return resp.json()['success']
-
- def get_captcha_settings(self):
- return {'siteKey': self.settings.get('site_key')}
diff --git a/recaptcha/package.json b/recaptcha/package.json
deleted file mode 100644
index 9dbd806..0000000
--- a/recaptcha/package.json
+++ /dev/null
@@ -1,10 +0,0 @@
-{
- "name": "recaptcha",
- "private": true,
- "version": "1.0.0",
- "repository": "https://github.com/indico/indico-plugins",
- "author": "Indico Team ",
- "dependencies": {
- "react-google-recaptcha": "^2.1.0"
- }
-}