diff --git a/api/api/settings.py b/api/api/settings.py index c073e3d63..5dd0d44ac 100644 --- a/api/api/settings.py +++ b/api/api/settings.py @@ -224,9 +224,7 @@ VALIDITY_PERIOD_VERIFICATION_SIGNATURE = timedelta( hours=int(os.environ.get("DESECSTACK_API_AUTHACTION_VALIDITY", "0")) ) -REGISTER_LPS_ON_SIGNUP = bool( - int(os.environ.get("DESECSTACK_API_REGISTER_LPS_ON_SIGNUP", "1")) -) +REGISTER_LPS = bool(int(os.environ.get("DESECSTACK_API_REGISTER_LPS", "1"))) # CAPTCHA CAPTCHA_VALIDITY_PERIOD = timedelta(hours=24) diff --git a/api/desecapi/serializers/users.py b/api/desecapi/serializers/users.py index b20c26526..7135a86e7 100644 --- a/api/desecapi/serializers/users.py +++ b/api/desecapi/serializers/users.py @@ -84,14 +84,24 @@ def validate_domain(self, value): serializer.default_error_messages["name_unavailable"], code="name_unavailable", ) + return value + + def validate(self, attrs): if ( - not settings.REGISTER_LPS_ON_SIGNUP - and DomainSerializer.Meta.model(name=value).is_locally_registrable + not settings.REGISTER_LPS + and attrs.get("captcha") is not None + and attrs.get("domain") is not None + and DomainSerializer.Meta.model(name=attrs["domain"]).is_locally_registrable ): raise serializers.ValidationError( - "Registration during sign-up disabled; please create account without a domain name.", + { + "domain": [ + DomainSerializer.default_error_messages["name_unavailable"] + ] + }, + code="name_unavailable", ) - return value + return super().validate(attrs) def create(self, validated_data): validated_data.pop("domain", None) diff --git a/api/desecapi/tests/test_domains.py b/api/desecapi/tests/test_domains.py index cfc101004..05e17b97e 100644 --- a/api/desecapi/tests/test_domains.py +++ b/api/desecapi/tests/test_domains.py @@ -1,6 +1,7 @@ from django.conf import settings from django.core import mail from django.core.exceptions import ValidationError +from django.test import override_settings from rest_framework import status from desecapi.models import Domain @@ -714,6 +715,13 @@ def test_create_public_suffixes(self): self.assertStatus(response, status.HTTP_400_BAD_REQUEST) self.assertEqual(response.data["name"][0].code, "name_unavailable") + @override_settings(REGISTER_LPS=False) + def test_create_local_public_suffixes_lps_disabled(self): + domain = "foo." + next(iter(self.AUTO_DELEGATION_DOMAINS)) + response = self.client.post(self.reverse("v1:domain-list"), {"name": domain}) + self.assertStatus(response, status.HTTP_400_BAD_REQUEST) + self.assertEqual(response.data["name"][0].code, "name_unavailable") + def test_create_domain_under_public_suffix_with_private_parent(self): name = "amazonaws.com" with self.assertRequests( diff --git a/api/desecapi/tests/test_user_management.py b/api/desecapi/tests/test_user_management.py index 24188a2e1..56e66958c 100644 --- a/api/desecapi/tests/test_user_management.py +++ b/api/desecapi/tests/test_user_management.py @@ -25,6 +25,7 @@ from django.conf import settings from django.core import mail from django.core.management import call_command +from django.test import override_settings from django.urls import resolve from django.utils import timezone from rest_framework import status @@ -610,6 +611,19 @@ def test_registration_with_domain(self): domain=self.random_domain_name(suffix=local_public_suffix) ) + @override_settings(REGISTER_LPS=False) + def test_registration_with_domain_lps_disabled(self): + PublicSuffixMockMixin.setUpMockPatch(self) + with self.get_psl_context_manager("."): + _, _, domain = self._test_registration_with_domain() + + local_public_suffix = random.sample(list(self.AUTO_DELEGATION_DOMAINS), 1)[0] + with self.get_psl_context_manager(local_public_suffix): + self._test_registration_with_domain( + domain=self.random_domain_name(suffix=local_public_suffix), + expect_failure_response=self.assertRegistrationFailureDomainUnavailableResponse, + ) + def test_registration_without_domain_and_password(self): email, password = self._test_registration(self.random_username(), None) confirmation_link = self.assertResetPasswordEmail(email) @@ -693,6 +707,20 @@ def test_registration_wrong_captcha(self): def test_registration_late_captcha(self): self._test_registration(password=self.random_password(), late_captcha=True) + PublicSuffixMockMixin.setUpMockPatch(self) + local_public_suffix = random.sample(list(self.AUTO_DELEGATION_DOMAINS), 1)[0] + # Late captcha sign-up allows domain registration (Nextcloud VM workflow) + for register_lps in [True, False]: + domain = self.random_domain_name(suffix=local_public_suffix) + with ( + override_settings(REGISTER_LPS=register_lps), + self.get_psl_context_manager(local_public_suffix), + self.assertRequests( + self.requests_desec_domain_creation_auto_delegation(domain) + ), + ): + self._test_registration(domain=domain, late_captcha=True) + class OtherUserAccountTestCase(UserManagementTestCase): def setUp(self): diff --git a/api/desecapi/views/domains.py b/api/desecapi/views/domains.py index 529ecbb88..6e9b6b611 100644 --- a/api/desecapi/views/domains.py +++ b/api/desecapi/views/domains.py @@ -6,6 +6,7 @@ from rest_framework.decorators import action from rest_framework.permissions import IsAuthenticated, SAFE_METHODS from rest_framework.response import Response +from rest_framework.serializers import ValidationError from rest_framework.settings import api_settings from rest_framework.views import APIView @@ -83,6 +84,14 @@ def get_serializer(self, *args, **kwargs): return super().get_serializer(*args, include_keys=include_keys, **kwargs) def perform_create(self, serializer): + if ( + not settings.REGISTER_LPS + and Domain(name=serializer.validated_data["name"]).is_locally_registrable + ): + raise ValidationError( + {"name": [DomainSerializer.default_error_messages["name_unavailable"]]}, + code="name_unavailable", + ) with PDNSChangeTracker(): domain = serializer.save(owner=self.request.user) @@ -121,5 +130,5 @@ def get(self, request, *args, **kwargs): serials = cache.get(key) if serials is None: serials = get_serials() - cache.get_or_set(key, serials, timeout=15) + cache.get_or_set(key, serials, timeout=60) return Response(serials) diff --git a/api/requirements.txt b/api/requirements.txt index 155b1d863..e7b2889ba 100644 --- a/api/requirements.txt +++ b/api/requirements.txt @@ -1,7 +1,7 @@ captcha~=0.5.0 celery~=5.3.6 -coverage~=7.4.0 -cryptography~=41.0.7 +coverage~=7.4.1 +cryptography~=42.0.1 Django~=5.0.1 django-cors-headers~=4.3.1 djangorestframework~=3.14.0 @@ -9,7 +9,7 @@ django-celery-email~=3.0.0 django-netfields~=1.3.2 django-pgtrigger~=4.11.0 django-prometheus~=2.3.1 -dnspython~=2.4.2 +dnspython~=2.5.0 httpretty~=1.0.5 # 1.1 breaks tests. Does not run in production, so stick to it. pyotp~=2.9.0 psycopg~=3.1.17 diff --git a/docker-compose.yml b/docker-compose.yml index 05576cd12..ac83c5b2a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -150,6 +150,8 @@ services: - DESECSTACK_API_PCH_API - DESECSTACK_API_PCH_API_TOKEN - DESECSTACK_API_AUTHACTION_VALIDITY + - DESECSTACK_API_REGISTER_LPS + - DESECSTACK_API_LIMIT_USER_DOMAIN_COUNT_DEFAULT - DESECSTACK_DBAPI_PASSWORD_desec - DESECSTACK_IPV4_REAR_PREFIX16 - DESECSTACK_IPV6_SUBNET @@ -284,6 +286,7 @@ services: user: memcache:memcache networks: - rearapi_celery + command: ["memcached", "-I", "2m"] logging: driver: "syslog" options: diff --git a/test/e2e2/requirements.txt b/test/e2e2/requirements.txt index d9ad10d0e..1d9988202 100644 --- a/test/e2e2/requirements.txt +++ b/test/e2e2/requirements.txt @@ -2,4 +2,4 @@ pytest pytest-schema pytest-xdist requests -dnspython~=2.4.2 +dnspython~=2.5.0 diff --git a/www/webapp/package.json b/www/webapp/package.json index 6100d9797..2fbbcf573 100644 --- a/www/webapp/package.json +++ b/www/webapp/package.json @@ -15,7 +15,7 @@ }, "dependencies": { "@fontsource/roboto": "^5.0.3", - "@mdi/js": "~7.3.67", + "@mdi/js": "~7.4.47", "axios": "^1.4.0", "date-fns": "^3.3.1", "pinia": "^2.0.30", diff --git a/www/webapp/src/views/HomePage.vue b/www/webapp/src/views/HomePage.vue index 6bc648949..9f525cb1a 100644 --- a/www/webapp/src/views/HomePage.vue +++ b/www/webapp/src/views/HomePage.vue @@ -28,7 +28,7 @@ @change="$router.push({query: {domainType: domainType}})" > - + - - + + dynDNS registrations are suspended at this time. + We do not process requests for exceptions.
+ Please do not contact support about this. You will have to register a domain elsewhere. +
+ +