Skip to content

Commit

Permalink
Merge pull request #577 from memeLab/update-dockerfile-structure
Browse files Browse the repository at this point in the history
Update SMTP configuration and Add Google's Recaptcha V3
  • Loading branch information
pablodiegoss authored Oct 30, 2024
2 parents f878ba7 + e9569e5 commit 014dea4
Show file tree
Hide file tree
Showing 13 changed files with 200 additions and 73 deletions.
13 changes: 11 additions & 2 deletions .envs/.example
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,14 @@ POSTGRES_PASSWORD=secret
# Email server variables
SMTP_SERVER=mailpit
SMTP_PORT=1025
[email protected]
SMTP_PASSWORD=local_password
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=

30 changes: 14 additions & 16 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,31 +1,26 @@

FROM python:3.13.0-slim-bookworm
FROM python:3.13.0-slim-bookworm as base-image

RUN apt-get update && \
apt-get install -y --no-install-recommends \
gettext \
docutils-common \
curl \
pipx \
wget

COPY ./pyproject.toml /pyproject.toml
COPY ./poetry.lock /poetry.lock
gettext \
docutils-common \
curl \
wget


ENV PATH="$PATH:/root/.local/bin" \
POETRY_NO_INTERACTION=1 \
POETRY_VIRTUALENVS_CREATE=false \
POETRY_CACHE_DIR='/var/cache/pypoetry' \
TINI_VERSION=v0.19.0 \
# poetry:
POETRY_NO_INTERACTION=1 \
POETRY_VIRTUALENVS_CREATE=true \
POETRY_CACHE_DIR='/var/cache/pypoetry' \
POETRY_VERSION=1.8.4


# Installing `poetry` package manager:
# https://github.com/python-poetry/poetry
RUN pip install --upgrade pip
RUN pipx install --python python3 poetry==${POETRY_VERSION}
RUN poetry install

RUN curl -sSL https://install.python-poetry.org | python3 -

RUN dpkgArch="$(dpkg --print-architecture | awk -F- '{ print $NF }')" \
&& wget "https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini-${dpkgArch}" -O /usr/local/bin/tini \
Expand All @@ -36,6 +31,9 @@ RUN mkdir -p /jandig/src /jandig/locale /jandig/docs /jandig/static /jandig/buil

WORKDIR /jandig

COPY ./pyproject.toml /jandig/pyproject.toml
COPY ./poetry.lock /jandig/poetry.lock

COPY ./src/ /jandig/src/
COPY ./docs/ /jandig/docs/
COPY ./locale/ /jandig/locale/
Expand Down
13 changes: 4 additions & 9 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,8 @@ services:
ports:
- 8000:8000
volumes:
- ./src/:/jandig/src/
- ./docs/:/jandig/docs/
- ./etc/:/jandig/etc/
- ./locale/:/jandig/locale/
- ./run.sh:/jandig/run.sh
- ./tasks.py:/jandig/tasks.py
- ./poetry.lock:/poetry.lock
- ./pyproject.toml:/pyproject.toml
- ./:/jandig
- poetry_cache:/var/cache/pypoetry
env_file:
- .envs/.example
depends_on:
Expand Down Expand Up @@ -90,4 +84,5 @@ services:
volumes:
postgres_data:
media_data:
mailpit_data:
mailpit_data:
poetry_cache:
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ Jinja2 = "^3.1.2"
gunicorn = "^20.1.0"
Pillow = "^11.0.0"
django-cors-headers = "^3.13.0"
invoke = "^2.2.0"
django-environ = "^0.9.0"
psycopg2-binary = "^2.9.3"
Sphinx = "^8.1.3"
Expand All @@ -27,6 +26,7 @@ drf-nested-routers = "^0.93.4"
django-htmx = "^1.18.0"

[tool.poetry.group.dev.dependencies]
invoke = "^2.2.0"
playwright = "^1.41.2"
pytest = "^7.2.0"
pytest-xdist = "^3.0.2"
Expand Down
9 changes: 8 additions & 1 deletion run.sh
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
#!/bin/bash
poetry install
poetry show
poetry run inv collect db i18n --compile docs run -g
# poetry run python src/manage.py collectstatic --no-input
poetry run python src/manage.py migrate
poetry run sphinx-build docs/ build/
poetry run python etc/scripts/compilemessages.py

bash -c "cd src && poetry run gunicorn --reload --worker-connections=10000 --workers=4 --log-level debug --bind 0.0.0.0:8000 config.wsgi"
# poetry run python src/manage.py runserver 0.0.0.0:8000
7 changes: 6 additions & 1 deletion src/config/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,10 @@ def debug(request):
os.path.join(BASE_DIR, "blog", "static"),
]

STATICFILES_FINDERS = [
"django.contrib.staticfiles.finders.AppDirectoriesFinder",
]

AWS_PUBLIC_MEDIA_LOCATION = "media/public"

# Storages
Expand All @@ -237,8 +241,9 @@ def debug(request):

SMTP_SERVER = env("SMTP_SERVER", default="mailpit")
SMTP_PORT = env("SMTP_PORT", default=1025)
SMTP_EMAIL = env("SMTP_EMAIL", default="[email protected]")
SMTP_USER = env("SMTP_USER", default="[email protected]")
SMTP_PASSWORD = env("SMTP_PASSWORD", default="password")
SMTP_SENDER_MAIL = env("SMTP_SENDER_MAIL", default="[email protected]")

if len(sys.argv) > 1 and sys.argv[1] == "test":
logging.disable(logging.CRITICAL)
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
7 changes: 4 additions & 3 deletions src/users/services/email_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,15 @@ class EmailService:
def __init__(self, email_message):
self.smtp_server = settings.SMTP_SERVER
self.smtp_port = settings.SMTP_PORT
self.jandig_email = settings.SMTP_EMAIL
self.jandig_email_password = settings.SMTP_PASSWORD
self.smtp_user = settings.SMTP_USER
self.smtp_password = settings.SMTP_PASSWORD
self.jandig_email = settings.SMTP_SENDER_MAIL
self.email_message = email_message

def send_email_to_recover_password(self, multipart_message):
email_server = smtplib.SMTP(self.smtp_server, self.smtp_port)
email_server.starttls()
email_server.login(self.jandig_email, self.jandig_email_password)
email_server.login(self.smtp_user, self.smtp_password)
email_server.sendmail(
multipart_message["From"],
multipart_message["To"],
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
Loading

0 comments on commit 014dea4

Please sign in to comment.