Skip to content
This repository has been archived by the owner on Jun 24, 2024. It is now read-only.

Commit

Permalink
Merge branch 'release/3.3.1'
Browse files Browse the repository at this point in the history
  • Loading branch information
AlexandreJunod committed Dec 20, 2023
2 parents 88360d5 + 7319e5d commit 8cc5864
Show file tree
Hide file tree
Showing 28 changed files with 589 additions and 362 deletions.
4 changes: 3 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,10 @@ SECURE_PROXY_SSL_HEADER=HTTP_X_FORWARDED_PROTO,https
# LIMIT MAX CONNEXIONS ATTEMPTS
AXES_FAILURE_LIMIT=5
# Lock out by combination of ip AND User
AXES_LOCK_OUT_BY_COMBINATION_USER_AND_IP=True
# Please read https://django-axes.readthedocs.io/en/latest/5_customization.html#customizing-lockout-parameters
AXES_LOCKOUT_PARAMETERS=username
AXES_COOLOFF_TIME=2
AXES_IPWARE_PROXY_COUNT=1
#Limit requests on DRF. Note that this might impact the print service!
DRF_THROTTLE_RATE_PERMITS_API='1000/day'
DRF_THROTTLE_RATE_PERMITS_DETAILS_API='1000/day'
Expand Down
3 changes: 2 additions & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,9 @@ services:
SESSION_COOKIE_SAMESITE:
SECURE_PROXY_SSL_HEADER:
AXES_FAILURE_LIMIT:
AXES_LOCK_OUT_BY_COMBINATION_USER_AND_IP:
AXES_LOCKOUT_PARAMETERS:
AXES_COOLOFF_TIME:
AXES_IPWARE_PROXY_COUNT:
DRF_THROTTLE_RATE_PERMITS_API:
DRF_THROTTLE_RATE_PERMITS_DETAILS_API:
DRF_THROTTLE_RATE_EVENTS_API:
Expand Down
19 changes: 19 additions & 0 deletions geocity/apps/accounts/auth_backends.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from django.contrib.auth import get_user_model
from django.contrib.auth.backends import ModelBackend


class EmailAuthenticationBackend(ModelBackend):
def authenticate(self, request, email, password, **kwargs):
User = get_user_model()

if not email or not password:
return None

user = User.objects.filter(email=email).first()
if not user:
return None

if user.check_password(password):
return user

return None
Empty file.
150 changes: 104 additions & 46 deletions geocity/apps/accounts/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
from captcha.fields import CaptchaField
from django import forms
from django.conf import settings
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth import authenticate
from django.contrib.auth.forms import AuthenticationForm, BaseUserCreationForm
from django.core.validators import MaxValueValidator, MinValueValidator, RegexValidator
from django.db.models import Q
from django.utils.translation import gettext_lazy as _

from geocity.fields import AddressWidget
Expand All @@ -17,18 +17,96 @@
from .geomapfish.provider import GeomapfishProvider


def check_existing_email(email, user):
if (
models.User.objects.filter(email=email)
.exclude(Q(id=user.id) if user else Q())
.exists()
):
raise forms.ValidationError(_("Cet email est déjà utilisé."))
class EmailAuthenticationForm(forms.Form):
"""Largely copied over from django's AuthenticationForm."""

return email
email_or_username = forms.CharField(
label=_("Email/Username"),
widget=forms.TextInput(attrs={"autofocus": True, "maxlength": 254}),
max_length=254,
)
password = forms.CharField(
label=_("Password"),
widget=forms.PasswordInput(attrs={"autocomplete": "current-password"}),
)

def __init__(self, request, *args, **kwargs):
super().__init__(*args, **kwargs)
self.user_cache = None
self.request = request

def get_user(self):
return self.user_cache

def clean(self):
email_or_username = self.cleaned_data.get("email_or_username")
password = self.cleaned_data.get("password")

if email_or_username is not None and password:
# Try with email first
self.user_cache = authenticate(
self.request, email=email_or_username, password=password
)
if self.user_cache:
if self.user_cache.is_active:
return self.cleaned_data

raise forms.ValidationError(
AuthenticationForm.error_messages["inactive"], code="inactive"
)

# Then try with username
self.user_cache = authenticate(
self.request, username=email_or_username, password=password
)
if self.user_cache:
if self.user_cache.is_active:
return self.cleaned_data

raise forms.ValidationError(
AuthenticationForm.error_messages["inactive"], code="inactive"
)

# Nothing matched :(
raise forms.ValidationError(
AuthenticationForm.error_messages["invalid_login"],
code="invalid_login",
params={"username": _("Email/Username")},
)


class EmailUserCreationForm(BaseUserCreationForm):
class Meta(BaseUserCreationForm.Meta):
fields = ("email",)

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields["email"].required = True
self.email_already_known = False

def clean_email(self):
"""Reject emails that differ only in case."""
email = self.cleaned_data.get("email")

if not email:
self.add_error("email", "")

if email and models.User.objects.filter(email__iexact=email).exists():
self.email_already_known = True

class NewDjangoAuthUserForm(UserCreationForm):
return email

def save(self, commit=True):
user = super().save(commit=False)
user.username = f"{settings.EMAIL_USER_PREFIX}{user.email}"

if commit:
user.save()

return user


class NewDjangoAuthUserForm(EmailUserCreationForm):
first_name = forms.CharField(
label=_("Prénom"),
max_length=30,
Expand All @@ -37,44 +115,17 @@ class NewDjangoAuthUserForm(UserCreationForm):
label=_("Nom"),
max_length=150,
)
email = forms.EmailField(
label=_("Email"),
max_length=254,
)
required_css_class = "required"

def clean_email(self):
return check_existing_email(self.cleaned_data["email"], user=None)

def clean(self):
cleaned_data = super().clean()

if not "username" in cleaned_data:
raise forms.ValidationError({"username": ""})

if not "first_name" in cleaned_data:
if "first_name" not in cleaned_data:
raise forms.ValidationError({"first_name": ""})

if not "last_name" in cleaned_data:
if "last_name" not in cleaned_data:
raise forms.ValidationError({"last_name": ""})

if not "email" in cleaned_data:
raise forms.ValidationError({"email": ""})

for reserved_usernames in (
settings.TEMPORARY_USER_PREFIX,
settings.ANONYMOUS_USER_PREFIX,
):
if cleaned_data["username"].startswith(reserved_usernames):
raise forms.ValidationError(
{
"username": _(
"Le nom d'utilisat·eur·rice ne peut pas commencer par %s"
)
% reserved_usernames
}
)

if cleaned_data["first_name"] == settings.ANONYMOUS_NAME:
raise forms.ValidationError(
{
Expand All @@ -92,10 +143,9 @@ def clean(self):

def save(self, commit=True):
user = super().save(commit=False)
user.email = self.cleaned_data["email"]
user.first_name = self.cleaned_data["first_name"]
user.last_name = self.cleaned_data["last_name"]
user.backend = "django.contrib.auth.backends.ModelBackend"
user.backend = "geocity.apps.accounts.auth_backends.EmailAuthenticationBackend"

if commit:
user.save()
Expand All @@ -106,6 +156,13 @@ def save(self, commit=True):
class DjangoAuthUserForm(forms.ModelForm):
"""User"""

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

if self.instance.username.startswith(settings.EMAIL_USER_PREFIX):
field = self.fields["username"]
field.widget = field.hidden_widget()

first_name = forms.CharField(
max_length=30,
label=_("Prénom"),
Expand All @@ -127,10 +184,11 @@ class DjangoAuthUserForm(forms.ModelForm):
attrs={"placeholder": "ex: [email protected]", "required": "required"}
),
)
required_css_class = "required"
username = forms.CharField(
max_length=150, label=_("Identifiant"), disabled=True, required=False
)

def clean_email(self):
return check_existing_email(self.cleaned_data["email"], self.instance)
required_css_class = "required"

def clean_first_name(self):
if self.cleaned_data["first_name"] == settings.ANONYMOUS_NAME:
Expand All @@ -150,7 +208,7 @@ def clean_last_name(self):

class Meta:
model = models.User
fields = ["first_name", "last_name", "email"]
fields = ["first_name", "last_name", "email", "username"]


class GenericUserProfileForm(forms.ModelForm):
Expand Down
Empty file.
10 changes: 10 additions & 0 deletions geocity/apps/accounts/templates/allauth/layouts/base.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{% extends "base_generic.html" %}
{% load static %}
{% load i18n %}
{% load bootstrap4 %}

{% block content-fluid %}
<div class="container mb-0 px-3 px-md-0">
<a href="{% url 'accounts:account_login' %}" class="btn btn-primary">Page de connexion</a>
</div>
{% endblock %}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{% load i18n %}
{% autoescape off %}
{% blocktranslate %}Bonjour {{ user }}{% endblocktranslate %},
{% blocktranslate %}Bonjour {{ user.get_full_name }}{% endblocktranslate %},

{% translate "Nous devons simplement vérifier votre adresse e-mail avant que vous puissiez accéder à Geocity." %}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{% load i18n %}
{% autoescape off %}
{% blocktranslate %}Bonjour {{ user.get_full_name }}{% endblocktranslate %},

{% translate "Pour votre information, quelqu'un a tenté de créer un compte Geocity avec votre adresse e-mail." %}

{% translate "Ceci n'a pas d'implication grave pour vous et vous pouvez sans danger l'ignorer." %}

{% translate "Avec nos meilleures salutations," %}
{{ signature }}

{% translate "Ceci est un e-mail automatique, veuillez ne pas y répondre." %}
{% endautoescape %}
Loading

0 comments on commit 8cc5864

Please sign in to comment.