diff --git a/api/funkwhale_api/common/validators.py b/api/funkwhale_api/common/validators.py
index b5f26cac5..78a4b4c7c 100644
--- a/api/funkwhale_api/common/validators.py
+++ b/api/funkwhale_api/common/validators.py
@@ -1,6 +1,7 @@
import mimetypes
from os.path import splitext
+from django.core import validators
from django.core.exceptions import ValidationError
from django.core.files.images import get_image_dimensions
from django.template.defaultfilters import filesizeformat
@@ -150,3 +151,17 @@ class FileValidator(object):
}
raise ValidationError(message)
+
+
+class DomainValidator(validators.URLValidator):
+ message = "Enter a valid domain name."
+
+ def __call__(self, value):
+ """
+ This is a bit hackish but since we don't have any built-in domain validator,
+ we use the url one, and prepend http:// in front of it.
+
+ If it fails, we know the domain is not valid.
+ """
+ super().__call__("http://{}".format(value))
+ return value
diff --git a/api/funkwhale_api/federation/models.py b/api/funkwhale_api/federation/models.py
index 2fdeaaa76..59360aea1 100644
--- a/api/funkwhale_api/federation/models.py
+++ b/api/funkwhale_api/federation/models.py
@@ -13,6 +13,7 @@ from django.urls import reverse
from funkwhale_api.common import session
from funkwhale_api.common import utils as common_utils
+from funkwhale_api.common import validators as common_validators
from funkwhale_api.music import utils as music_utils
from . import utils as federation_utils
@@ -83,7 +84,11 @@ class DomainQuerySet(models.QuerySet):
class Domain(models.Model):
- name = models.CharField(primary_key=True, max_length=255)
+ name = models.CharField(
+ primary_key=True,
+ max_length=255,
+ validators=[common_validators.DomainValidator()],
+ )
creation_date = models.DateTimeField(default=timezone.now)
nodeinfo_fetch_date = models.DateTimeField(default=None, null=True, blank=True)
nodeinfo = JSONField(default=empty_dict, max_length=50000, blank=True)
diff --git a/api/funkwhale_api/manage/views.py b/api/funkwhale_api/manage/views.py
index 0697c6c14..763b37497 100644
--- a/api/funkwhale_api/manage/views.py
+++ b/api/funkwhale_api/manage/views.py
@@ -98,7 +98,10 @@ class ManageInvitationViewSet(
class ManageDomainViewSet(
- mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet
+ mixins.CreateModelMixin,
+ mixins.ListModelMixin,
+ mixins.RetrieveModelMixin,
+ viewsets.GenericViewSet,
):
lookup_value_regex = r"[a-zA-Z0-9\-\.]+"
queryset = (
diff --git a/api/tests/manage/test_serializers.py b/api/tests/manage/test_serializers.py
index 803820b48..74ba96ba8 100644
--- a/api/tests/manage/test_serializers.py
+++ b/api/tests/manage/test_serializers.py
@@ -1,3 +1,5 @@
+import pytest
+
from funkwhale_api.manage import serializers
@@ -53,6 +55,13 @@ def test_manage_domain_serializer(factories, now):
assert s.data == expected
+def test_manage_domain_serializer_validates_hostname(db):
+ s = serializers.ManageDomainSerializer(data={"name": "hello world"})
+
+ with pytest.raises(serializers.serializers.ValidationError):
+ s.is_valid(raise_exception=True)
+
+
def test_manage_actor_serializer(factories, now):
actor = factories["federation.Actor"]()
setattr(actor, "uploads_count", 66)
diff --git a/api/tests/manage/test_views.py b/api/tests/manage/test_views.py
index 72e945bca..4591f7b1b 100644
--- a/api/tests/manage/test_views.py
+++ b/api/tests/manage/test_views.py
@@ -1,6 +1,7 @@
import pytest
from django.urls import reverse
+from funkwhale_api.federation import models as federation_models
from funkwhale_api.federation import tasks as federation_tasks
from funkwhale_api.manage import serializers, views
@@ -90,6 +91,14 @@ def test_domain_detail(factories, superuser_api_client):
assert response.data["name"] == d.pk
+def test_domain_create(superuser_api_client):
+ url = reverse("api:v1:manage:federation:domains-list")
+ response = superuser_api_client.post(url, {"name": "test.federation"})
+
+ assert response.status_code == 201
+ assert federation_models.Domain.objects.filter(pk="test.federation").exists()
+
+
def test_domain_nodeinfo(factories, superuser_api_client, mocker):
domain = factories["federation.Domain"]()
url = reverse(
diff --git a/front/src/views/admin/moderation/DomainsList.vue b/front/src/views/admin/moderation/DomainsList.vue
index 84fb1df43..259d05f0c 100644
--- a/front/src/views/admin/moderation/DomainsList.vue
+++ b/front/src/views/admin/moderation/DomainsList.vue
@@ -1,26 +1,70 @@