diff --git a/config.py b/config.py index b1c0abcbf..294347119 100644 --- a/config.py +++ b/config.py @@ -20,7 +20,7 @@ CLIENT_WHITELIST = ['SERVER_HOSTNAME', 'PREFERRED_URL_SCHEME', 'MIXPANEL_KEY', 'AUTHENTICATION_TYPE', 'REGISTRY_TITLE', 'REGISTRY_TITLE_SHORT', 'CONTACT_INFO', 'AVATAR_KIND', 'LOCAL_OAUTH_HANDLER', 'DOCUMENTATION_LOCATION', 'DOCUMENTATION_METADATA', 'SETUP_COMPLETE', 'DEBUG', 'MARKETO_MUNCHKIN_ID', - 'STATIC_SITE_BUCKET'] + 'STATIC_SITE_BUCKET', 'RECAPTCHA_SITE_KEY'] def frontend_visible_config(config_dict): @@ -406,3 +406,8 @@ class DefaultConfig(object): # Example: 10 builds per minute is accomplished by setting ITEMS = 10, SECS = 60 MAX_BUILD_QUEUE_RATE_ITEMS = -1 MAX_BUILD_QUEUE_RATE_SECS = -1 + + # Site key and secret key for using recaptcha. + FEATURE_RECAPTCHA = False + RECAPTCHA_SITE_KEY = None + RECAPTCHA_SECRET_KEY = None diff --git a/endpoints/api/user.py b/endpoints/api/user.py index e26727225..c969926dd 100644 --- a/endpoints/api/user.py +++ b/endpoints/api/user.py @@ -2,6 +2,7 @@ import logging import json +import recaptcha2 from flask import request, abort from flask_login import logout_user @@ -183,6 +184,10 @@ class User(ApiResource): 'type': 'string', 'description': 'The optional invite code', }, + 'recaptcha_response': { + 'type': 'string', + 'description': 'The (may be disabled) recaptcha response code for verification', + }, } }, 'UpdateUser': { @@ -382,6 +387,19 @@ class User(ApiResource): abort(404) user_data = request.get_json() + + # If recaptcha is enabled, then verify the user is a human. + if features.RECAPTCHA: + recaptcha_response = user_data.get('recaptcha_response', '') + result = recaptcha2.verify(app.config['RECAPTCHA_SECRET_KEY'], + recaptcha_response, + request.remote_addr) + + if not result['success']: + return { + 'message': 'Are you a bot? If not, please revalidate the captcha.' + }, 400 + invite_code = user_data.get('invite_code', '') existing_user = model.user.get_nonrobot_user(user_data['username']) if existing_user: diff --git a/endpoints/common.py b/endpoints/common.py index 35adca062..b888e6bde 100644 --- a/endpoints/common.py +++ b/endpoints/common.py @@ -220,6 +220,7 @@ def render_page_template(name, route_data=None, **kwargs): enterprise_logo=app.config.get('ENTERPRISE_LOGO_URL', ''), mixpanel_key=app.config.get('MIXPANEL_KEY', ''), munchkin_key=app.config.get('MARKETO_MUNCHKIN_ID', ''), + recaptcha_key=app.config.get('RECAPTCHA_SITE_KEY', ''), google_tagmanager_key=app.config.get('GOOGLE_TAGMANAGER_KEY', ''), google_anaytics_key=app.config.get('GOOGLE_ANALYTICS_KEY', ''), sentry_public_dsn=app.config.get('SENTRY_PUBLIC_DSN', ''), diff --git a/external_libraries.py b/external_libraries.py index bf1676f78..d99431283 100644 --- a/external_libraries.py +++ b/external_libraries.py @@ -18,6 +18,7 @@ EXTERNAL_JS = [ 'cdn.jsdelivr.net/g/bootbox@4.1.0,underscorejs@1.5.2,restangular@1.2.0,d3js@3.3.3', 'cdn.ravenjs.com/3.1.0/angular/raven.min.js', 'cdn.jsdelivr.net/cal-heatmap/3.3.10/cal-heatmap.min.js', + 'cdnjs.cloudflare.com/ajax/libs/angular-recaptcha/3.2.1/angular-recaptcha.min.js', ] EXTERNAL_CSS = [ diff --git a/requirements-nover.txt b/requirements-nover.txt index 53b1659ab..b872db571 100644 --- a/requirements-nover.txt +++ b/requirements-nover.txt @@ -66,3 +66,4 @@ toposort trollius tzlocal xhtml2pdf +recaptcha2 diff --git a/requirements.txt b/requirements.txt index 2753c81b2..bc904569b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -98,6 +98,7 @@ python-swiftclient==3.1.0 pytz==2016.7 PyYAML==3.12 raven==5.29.0 +recaptcha2==0.1 redis==2.10.5 redlock==1.2.0 reportlab==2.7 diff --git a/static/css/directives/ui/signup-form.css b/static/css/directives/ui/signup-form.css index 0988e0380..273c953bc 100644 --- a/static/css/directives/ui/signup-form.css +++ b/static/css/directives/ui/signup-form.css @@ -10,4 +10,24 @@ .signup-form-element input { margin-bottom: 10px; +} + +.signup-form-element .captcha { + display: block; + text-align: center; +} + +.signup-form-element .captcha div { + display: inline-block; + margin: 2px; +} + +.signup-form-element .captcha { + height: 0px; + transition: height ease-in-out 250ms; + overflow: hidden; +} + +.user-setup-element .captcha.expanded { + height: 94px; } \ No newline at end of file diff --git a/static/directives/signup-form.html b/static/directives/signup-form.html index c62357715..44b38bfab 100644 --- a/static/directives/signup-form.html +++ b/static/directives/signup-form.html @@ -30,6 +30,10 @@ match="newUser.password" required ng-pattern="/^.{8,}$/"> +
+
+