diff --git a/api/config/settings/common.py b/api/config/settings/common.py index 4a68f80f2..013c7ae43 100644 --- a/api/config/settings/common.py +++ b/api/config/settings/common.py @@ -302,6 +302,7 @@ SESSION_COOKIE_HTTPONLY = False ACCOUNT_AUTHENTICATION_METHOD = "username_email" ACCOUNT_EMAIL_REQUIRED = True ACCOUNT_EMAIL_VERIFICATION = "mandatory" +ACCOUNT_USERNAME_VALIDATORS = "funkwhale_api.users.serializers.username_validators" # Custom user app defaults # Select the correct user model @@ -432,6 +433,7 @@ PLAYLISTS_MAX_TRACKS = env.int("PLAYLISTS_MAX_TRACKS", default=250) ACCOUNT_USERNAME_BLACKLIST = [ "funkwhale", "library", + "instance", "test", "status", "root", diff --git a/api/funkwhale_api/federation/views.py b/api/funkwhale_api/federation/views.py index 2c01292b3..29f566309 100644 --- a/api/funkwhale_api/federation/views.py +++ b/api/funkwhale_api/federation/views.py @@ -34,6 +34,7 @@ class FederationMixin(object): class ActorViewSet(FederationMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet): lookup_field = "user__username" + lookup_value_regex = ".*" authentication_classes = [authentication.SignatureAuthentication] permission_classes = [] renderer_classes = [renderers.ActivityPubRenderer] diff --git a/api/funkwhale_api/users/serializers.py b/api/funkwhale_api/users/serializers.py index 4421fa3f1..2f2271584 100644 --- a/api/funkwhale_api/users/serializers.py +++ b/api/funkwhale_api/users/serializers.py @@ -1,8 +1,13 @@ +import re + from django.conf import settings +from django.core import validators +from django.utils.deconstruct import deconstructible +from django.utils.translation import gettext_lazy as _ + from rest_auth.serializers import PasswordResetSerializer as PRS from rest_auth.registration.serializers import RegisterSerializer as RS from rest_framework import serializers - from versatileimagefield.serializers import VersatileImageFieldSerializer from funkwhale_api.activity import serializers as activity_serializers @@ -10,6 +15,19 @@ from funkwhale_api.activity import serializers as activity_serializers from . import models +@deconstructible +class ASCIIUsernameValidator(validators.RegexValidator): + regex = r"^[\w]+$" + message = _( + "Enter a valid username. This value may contain only English letters, " + "numbers, and _ characters." + ) + flags = re.ASCII + + +username_validators = [ASCIIUsernameValidator()] + + class RegisterSerializer(RS): invitation = serializers.CharField( required=False, allow_null=True, allow_blank=True diff --git a/api/tests/users/test_views.py b/api/tests/users/test_views.py index 268148c21..92e9922bf 100644 --- a/api/tests/users/test_views.py +++ b/api/tests/users/test_views.py @@ -20,6 +20,22 @@ def test_can_create_user_via_api(preferences, api_client, db): assert u.username == "test1" +@pytest.mark.parametrize("username", ["wrong.name", "wrong-name", "éaeu", "wrong name"]) +def test_username_only_accepts_letters_and_underscores( + username, preferences, api_client, db +): + url = reverse("rest_register") + data = { + "username": username, + "email": "test1@test.com", + "password1": "testtest", + "password2": "testtest", + } + preferences["users__registration_enabled"] = True + response = api_client.post(url, data) + assert response.status_code == 400 + + def test_can_restrict_usernames(settings, preferences, db, api_client): url = reverse("rest_register") preferences["users__registration_enabled"] = True diff --git a/changes/changelog.d/username.enhancement b/changes/changelog.d/username.enhancement new file mode 100644 index 000000000..bf38490f0 --- /dev/null +++ b/changes/changelog.d/username.enhancement @@ -0,0 +1 @@ +Apply restrictions to username characters during signup