diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 9f1a8028..6777afcb 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -182,6 +182,9 @@ jobs: sentry: dsn: ${{ secrets.SENTRY_DSN }} jsLoaderUrl: ${{ secrets.SENTRY_JS_LOADER_URL }} + recaptcha: + siteKey: ${{ secrets.RECAPTCHA_SITE_KEY }} + secretKey: ${{ secrets.RECAPTCHA_SECRET_KEY }} mailer: primary: host: ${{ secrets.DJANGO_MAILER_PRIMARY_HOST }} diff --git a/charts/templates/web-secret.yaml b/charts/templates/web-secret.yaml index bc4d6ed0..160b0c0b 100644 --- a/charts/templates/web-secret.yaml +++ b/charts/templates/web-secret.yaml @@ -17,6 +17,9 @@ "DJANGO_MAILER_PRIMARY_HOST_PORT" (.Values.secrets.mailer.primary.port | b64enc) "DJANGO_MAILER_PRIMARY_HOST_USER" (.Values.secrets.mailer.primary.user | b64enc) "DJANGO_MAILER_PRIMARY_HOST_PASSWORD" (.Values.secrets.mailer.primary.password | b64enc) + + "DJANGO_RECAPTCHA_SITE_KEY" (.Values.secrets.recaptcha.siteKey | b64enc) + "DJANGO_RECAPTCHA_SECRET_KEY" (.Values.secrets.recaptcha.secretKey | b64enc) ) }} {{- include "fiesta.secret" (merge (dict "Args" $data) . ) -}} diff --git a/charts/values.yaml b/charts/values.yaml index 14868138..80b01eea 100644 --- a/charts/values.yaml +++ b/charts/values.yaml @@ -39,6 +39,9 @@ secrets: sentry: dsn: dsn jsLoaderUrl: url + recaptcha: + siteKey: key + secretKey: key mailer: primary: host: host diff --git a/fiesta/.env.base b/fiesta/.env.base index d73b9203..16308cf5 100644 --- a/fiesta/.env.base +++ b/fiesta/.env.base @@ -1,3 +1,6 @@ DJANGO_BUILD_DIR=/usr/src/build DJANGO_STATIC_ROOT=/usr/src/static DJANGO_MEDIA_ROOT=/usr/src/media +# test keys are NOT suitable for v2 recaptcha +DJANGO_RECAPTCHA_PUBLIC_KEY=" " +DJANGO_RECAPTCHA_PRIVATE_KEY=" " diff --git a/fiesta/apps/accounts/forms/sign_up.py b/fiesta/apps/accounts/forms/sign_up.py new file mode 100644 index 00000000..a95ecaab --- /dev/null +++ b/fiesta/apps/accounts/forms/sign_up.py @@ -0,0 +1,14 @@ +from __future__ import annotations + +from allauth.account.forms import SignupForm as AllauthSignupForm +from django_recaptcha.fields import ReCaptchaField +from django_recaptcha.widgets import ReCaptchaV3 + + +class SignupForm(AllauthSignupForm): + recaptcha = ReCaptchaField( + widget=ReCaptchaV3( + action="signup", + attrs={"theme": "clean"}, + ) + ) diff --git a/fiesta/apps/accounts/templates/account/signup_form.html b/fiesta/apps/accounts/templates/account/signup_form.html index 070dafd4..6966d159 100644 --- a/fiesta/apps/accounts/templates/account/signup_form.html +++ b/fiesta/apps/accounts/templates/account/signup_form.html @@ -23,6 +23,11 @@

{{ card_title|default:"Create Account" }}

{% include "fiestaforms/parts/field.html" with bf=form.email errors=form.errors.email %} {% include "fiestaforms/parts/field.html" with bf=form.password1 errors=form.errors.password1 %} {% include "fiestaforms/parts/field.html" with bf=form.password2 errors=form.errors.password2 %} +
+ {{ form.recaptcha }} + {% if form.errors.recaptcha %}
{{ form.errors.recaptcha }}
{% endif %} +
+
Already registered? diff --git a/fiesta/fiesta/settings/__init__.py b/fiesta/fiesta/settings/__init__.py index 53abf72e..e19e1924 100644 --- a/fiesta/fiesta/settings/__init__.py +++ b/fiesta/fiesta/settings/__init__.py @@ -39,6 +39,8 @@ class Development(Base): USE_WEBPACK_INTEGRITY = False + SILENCED_SYSTEM_CHECKS = ["django_recaptcha.recaptcha_test_key_error"] + def INSTALLED_APPS(self): return super().INSTALLED_APPS + ["debug_toolbar"] diff --git a/fiesta/fiesta/settings/auth.py b/fiesta/fiesta/settings/auth.py index d58c98c1..50737b9d 100644 --- a/fiesta/fiesta/settings/auth.py +++ b/fiesta/fiesta/settings/auth.py @@ -1,5 +1,7 @@ from __future__ import annotations +from configurations.values import SecretValue + class AuthConfigMixin: AUTH_PASSWORD_VALIDATORS = [ @@ -75,6 +77,10 @@ class AuthConfigMixin: ACCOUNT_USERNAME_REQUIRED = False # email ftw ACCOUNT_DEFAULT_HTTP_PROTOCOL = "https" + ACCOUNT_FORMS = { + "signup": "apps.accounts.forms.sign_up.SignupForm", + } + ACCOUNT_EMAIL_VERIFICATION = "mandatory" SOCIALACCOUNT_EMAIL_VERIFICATION = "optional" # social account settings @@ -92,3 +98,8 @@ class AuthConfigMixin: ACCOUNT_LOGOUT_ON_PASSWORD_CHANGE = True # logout after password change ACCOUNT_USERNAME_MIN_LENGTH = 4 # a personal preference + + RECAPTCHA_PUBLIC_KEY = SecretValue() + RECAPTCHA_PRIVATE_KEY = SecretValue() + + RECAPTCHA_USE_SSL = True # Defaults to False diff --git a/fiesta/fiesta/settings/logging.py b/fiesta/fiesta/settings/logging.py index bb18fdf3..9bdb9ce5 100644 --- a/fiesta/fiesta/settings/logging.py +++ b/fiesta/fiesta/settings/logging.py @@ -58,6 +58,10 @@ def LOGGING(self): "handlers": ["console"], "level": self.LOG_LEVEL, }, + "django_recaptcha": { + "handlers": ["console"], + "level": self.LOG_LEVEL, + }, }, } diff --git a/fiesta/fiesta/settings/project.py b/fiesta/fiesta/settings/project.py index 4a241c58..9639f133 100644 --- a/fiesta/fiesta/settings/project.py +++ b/fiesta/fiesta/settings/project.py @@ -101,6 +101,8 @@ def DEFAULT_FROM_EMAIL(self): "allauth_cas", # superuser can log in as any user "loginas", + # recaptcha + "django_recaptcha", # editorjs integration "django_editorjs_fields", # location fields diff --git a/poetry.lock b/poetry.lock index 655393b2..7c8594e7 100644 --- a/poetry.lock +++ b/poetry.lock @@ -869,6 +869,20 @@ files = [ [package.dependencies] Django = ">=2.1" +[[package]] +name = "django-recaptcha" +version = "4.0.0" +description = "Django recaptcha form field/widget app." +optional = false +python-versions = "*" +files = [ + {file = "django-recaptcha-4.0.0.tar.gz", hash = "sha256:5316438f97700c431d65351470d1255047e3f2cd9af0f2f13592b637dad9213e"}, + {file = "django_recaptcha-4.0.0-py3-none-any.whl", hash = "sha256:0d912d5c7c009df4e47accd25029133d47a74342dbd2a8edc2877b6bffa971a3"}, +] + +[package.dependencies] +django = "*" + [[package]] name = "django-select2" version = "7.11.1" @@ -2132,4 +2146,4 @@ anyio = ">=3.0.0" [metadata] lock-version = "2.0" python-versions = "^3.12" -content-hash = "716d16cbcfcba887f4be4fdd30f814911acb8e539f3277f62803921c96ac6871" +content-hash = "43d00087dadb290162ad108ddf32623fd1204085dc55e24e7f45680a996ddf58" diff --git a/pyproject.toml b/pyproject.toml index be7731b4..e4b9a0d4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,6 +55,7 @@ django-admin-env-notice = "^1.0" cryptography = "^43.0.0" django-admin-relation-links = "^0.2.5" django-mailer = "^2.3.1" +django-recaptcha = "^4.0.0" [tool.poetry.dev-dependencies] @@ -96,17 +97,16 @@ exclude = ''' [tool.ruff] # see https://beta.ruff.rs/docs/rules/ -extend-select = ["UP", "DJ", "PIE", "INT", "PTH", "SIM", "RET", "G", "DTZ", "B", "I002"] line-length = 120 target-version = "py311" -ignore = ["E731"] exclude = [ "migrations", ] -[tool.ruff.isort] -required-imports = ["from __future__ import annotations"] +lint.ignore = ["E731"] +extend-select = ["UP", "DJ", "PIE", "INT", "PTH", "SIM", "RET", "G", "DTZ", "B", "I002"] +lint.isort.required-imports = ["from __future__ import annotations"] [tool.vulture]