Skip to content

Commit

Permalink
Add recaptcha v3 to Signup and Recover pass views
Browse files Browse the repository at this point in the history
  • Loading branch information
pablodiegoss committed Oct 30, 2024
1 parent d0aabf3 commit e9569e5
Show file tree
Hide file tree
Showing 6 changed files with 159 additions and 12 deletions.
8 changes: 8 additions & 0 deletions .envs/.example
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,11 @@ SMTP_PORT=1025
SMTP_USER=
SMTP_PASSWORD=
SMTP_SENDER_MAIL="[email protected]"

# Recaptcha
RECAPTCHA_ENABLED=False
RECAPTCHA_PROJECT_ID=
RECAPTCHA_GCLOUD_API_KEY=
RECAPTCHA_SITE_KEY=
RECAPTCHA_SECRET_KEY=

4 changes: 4 additions & 0 deletions src/core/jinja2/core/arviewer.jinja2
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@
<link href="https://fonts.googleapis.com/css?family=Istok+Web:400,400i,700,700i&display=swap" rel="stylesheet">

<link rel="shortcut icon" href="{{ static('images/icons/favicon.ico') }}" type="image/x-icon" />
{% block extra_css %}
{% endblock %}
{% block extra_js %}
{% endblock %}
</head>

<script src="https://code.jquery.com/jquery-3.4.1.slim.min.js" integrity="sha256-pasqAKBDmFT4eHoN2ndd6lN370kFiGUFyTiUHWhU7k8=" crossorigin="anonymous"></script>
Expand Down
23 changes: 19 additions & 4 deletions src/users/jinja2/users/recover-password.jinja2
Original file line number Diff line number Diff line change
@@ -1,29 +1,44 @@
{% extends '/core/arviewer.jinja2' %}

{% block content %}
{% block extra_css%}
<link rel="stylesheet" href="{{ static ('css/signup.css') }}">
{% endblock %}

{% block extra_js%}
<!-- Google Recaptcha -->
{% if recaptcha_enabled %}
<script src="https://www.google.com/recaptcha/enterprise.js?render={{recaptcha_site_key}}"></script>
<script>
function onSubmit(token) {
console.log(token)
document.getElementById("recover-password-form").submit();
}
</script>
{% endif %}
{% endblock %}

{% block content %}
<div class="connect-modal container">
<div class="logo">
<a href="{{ url('home') }}">
<img src="{{ static ('images/icons/header_icon.png') }}">
</a>
</div>

<div class="modal-container">
<div class="modalMenu flex">
<span>{{ _('Type your username or e-mail') }}</span>
</div>

<div class="recover-password-form">
<form name="recover-password-form" action="{{url('recover')}}" method="post" enctype="multipart/form-data">
<form id="recover-password-form" action="{{url('recover')}}" method="post" enctype="multipart/form-data">
{{ csrf_input }}
{% for field in form.visible_fields() %}
<p class="recover-password-field {{field.name}}">
{{ field }}
{{ field.errors }}
</p>
{% endfor%}
<input class="submit-btn" type="submit" value="{{ _('Submit') }}"/>
<button class="submit-btn g-recaptcha" data-sitekey="{{recaptcha_site_key}}" data-callback='onSubmit' data-action='recover_password'>{{ _('Submit') }}</button>
</form>
</div>
</div>
Expand Down
23 changes: 19 additions & 4 deletions src/users/jinja2/users/signup.jinja2
Original file line number Diff line number Diff line change
@@ -1,8 +1,23 @@
{% extends '/core/arviewer.jinja2' %}

{% block content %}
{# FIXME: maybe this can be improved #}
{% block extra_css%}
<link rel="stylesheet" href="{{ static ('css/signup.css') }}">
{% endblock %}

{% block extra_js%}
<!-- Google Recaptcha -->
{% if recaptcha_enabled %}
<script src="https://www.google.com/recaptcha/enterprise.js?render={{recaptcha_site_key}}"></script>
<script>
function onSubmit(token) {
console.log(token)
document.getElementById("signup-form").submit();
}
</script>
{% endif %}
{% endblock %}

{% block content %}
<div class="signup-modal container">
<div class="logo">
<a href="{{ url('home') }}">
Expand All @@ -15,7 +30,7 @@
<a href="/users/login">{{ _('Log in') }}</a>
</div>
<div class="signup-form">
<form name="signup-form" action="{{url('signup')}}" method="post" enctype="multipart/form-data">
<form id="signup-form" action="{{url('signup')}}" method="post" enctype="multipart/form-data">
{{ csrf_input }}
{% for field in form.visible_fields() %}
<p class="signup-field {{field.name}}">
Expand All @@ -29,7 +44,7 @@
<label for="remember-me-checkbox">{{ _('Remember me') }}</label>
</p>
</div>
<input class="submit-btn" type="submit" value="{{ _('Submit') }}"/>
<button class="submit-btn g-recaptcha" data-sitekey="{{recaptcha_site_key}}" data-callback='onSubmit' data-action='sign_up'>{{ _('Submit') }}</button>
</form>
</div>
</div>
Expand Down
70 changes: 70 additions & 0 deletions src/users/services/recaptcha_service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import logging

import requests
from django.conf import settings

# The minimum score threshold to consider the action as legitimate.
BOT_SCORE = 0.5

logger = logging.getLogger(__name__)


def create_assessment(token: str, recaptcha_action: str):
"""Create an assessment to analyze the risk of a UI action.
Args:
project_id: Your Google Cloud Project ID.
recaptcha_key: The reCAPTCHA key associated with the site/app
token: The generated token obtained from the client.
recaptcha_action: Action name corresponding to the token.
"""
if not token:
logger.error(
"The token is missing. Recaptcha may be enabled but not configured correctly."
)
return

payload = {
"event": {
"token": token,
"expectedAction": recaptcha_action,
"siteKey": settings.RECAPTCHA_SITE_KEY,
}
}

response = requests.post(
f"https://recaptchaenterprise.googleapis.com/v1/projects/{settings.RECAPTCHA_PROJECT_ID}/assessments?key={settings.RECAPTCHA_GCLOUD_API_KEY}",
json=payload,
)
response_data = response.json()
logger.info(response.json())

# Check if the token is valid.
if not response_data["tokenProperties"]["valid"]:
logger.info(
"The CreateAssessment call failed because the token was "
+ "invalid for the following reasons: "
+ str(response_data["tokenProperties"]["invalidReason"])
)
return

# Check if the expected action was executed.
if response_data["tokenProperties"]["action"] != recaptcha_action:
logger.info(
"The action attribute in your reCAPTCHA tag does"
+ "not match the action you are expecting to score"
)
return
else:
# Get the risk score and the reason(s).
# For more information on interpreting the assessment, see:
# https://cloud.google.com/recaptcha-enterprise/docs/interpret-assessment
for reason in response_data["riskAnalysis"]["reasons"]:
logger.info(reason)
logger.info(
"The reCAPTCHA score for this token is: "
+ str(response_data["riskAnalysis"]["score"])
)
# Get the assessment name (id). Use this to annotate the assessment.
assessment_name = response_data["name"].split("/")[-1]
logger.info(f"Assessment name: {assessment_name}")
return response_data
43 changes: 39 additions & 4 deletions src/users/views.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import json
import logging

from core.models import Artwork, Exhibit, Marker, Object
from django.conf import settings
from django.contrib.auth import (
authenticate,
get_user_model,
Expand All @@ -15,6 +15,8 @@
from django.views.decorators.cache import cache_page
from django.views.decorators.http import require_http_methods

from core.models import Artwork, Exhibit, Marker, Object

from .forms import (
ArtworkForm,
ExhibitForm,
Expand All @@ -29,13 +31,23 @@
from .models import Profile
from .services.email_service import EmailService
from .services.encrypt_service import EncryptService
from .services.recaptcha_service import BOT_SCORE, create_assessment
from .services.user_service import UserService

log = logging.getLogger("ej")
log = logging.getLogger(__file__)


def signup(request):
if request.method == "POST":
if settings.RECAPTCHA_ENABLED:
recaptcha_token = request.POST.get("g-recaptcha-response")
assessment = create_assessment(
token=recaptcha_token, recaptcha_action="sign_up"
)
score = assessment.get("riskAnalysis", {}).get("score", -1)
if score <= BOT_SCORE:
return redirect("home")

form = SignupForm(request.POST)

if form.is_valid():
Expand All @@ -49,14 +61,31 @@ def signup(request):
else:
form = SignupForm()

return render(request, "users/signup.jinja2", {"form": form})
return render(
request,
"users/signup.jinja2",
{
"form": form,
"recaptcha_enabled": settings.RECAPTCHA_ENABLED,
"recaptcha_site_key": settings.RECAPTCHA_SITE_KEY,
},
)


User = get_user_model()


def recover_password(request):
if request.method == "POST":
if settings.RECAPTCHA_ENABLED:
recaptcha_token = request.POST.get("g-recaptcha-response")
assessment = create_assessment(
token=recaptcha_token, recaptcha_action="recover_password"
)
score = assessment.get("riskAnalysis", {}).get("score", -1)
if score <= BOT_SCORE:
return redirect("home")

recover_password_form = RecoverPasswordForm(request.POST)

if recover_password_form.is_valid():
Expand Down Expand Up @@ -85,7 +114,13 @@ def recover_password(request):

recover_password_form = RecoverPasswordForm()
return render(
request, "users/recover-password.jinja2", {"form": recover_password_form}
request,
"users/recover-password.jinja2",
{
"form": recover_password_form,
"recaptcha_enabled": settings.RECAPTCHA_ENABLED,
"recaptcha_site_key": settings.RECAPTCHA_SITE_KEY,
},
)


Expand Down

0 comments on commit e9569e5

Please sign in to comment.