diff --git a/api/desecapi/migrations/0021_authenticatednoopuseraction.py b/api/desecapi/migrations/0021_authenticatednoopuseraction.py new file mode 100644 index 000000000..9a5a2b086 --- /dev/null +++ b/api/desecapi/migrations/0021_authenticatednoopuseraction.py @@ -0,0 +1,24 @@ +# Generated by Django 4.0.1 on 2022-01-19 14:41 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('desecapi', '0020_user_email_verified'), + ] + + operations = [ + migrations.CreateModel( + name='AuthenticatedNoopUserAction', + fields=[ + ('authenticateduseraction_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='desecapi.authenticateduseraction')), + ], + options={ + 'managed': False, + }, + bases=('desecapi.authenticateduseraction',), + ), + ] diff --git a/api/desecapi/models.py b/api/desecapi/models.py index 67d52b932..905766f39 100644 --- a/api/desecapi/models.py +++ b/api/desecapi/models.py @@ -169,6 +169,7 @@ def send_email(self, reason, context=None, recipient=None): 'activate-account': slow_lane, 'change-email': slow_lane, 'change-email-confirmation-old-email': fast_lane, + 'confirm-account': slow_lane, 'password-change-confirmation': fast_lane, 'reset-password': fast_lane, 'delete-account': fast_lane, @@ -1006,6 +1007,15 @@ def _act(self): self.user.change_email(self.new_email) +class AuthenticatedNoopUserAction(AuthenticatedUserAction): + + class Meta: + managed = False + + def _act(self): + pass + + class AuthenticatedResetPasswordUserAction(AuthenticatedUserAction): new_password = models.CharField(max_length=128) diff --git a/api/desecapi/serializers.py b/api/desecapi/serializers.py index 9c78f6d87..d7587f04c 100644 --- a/api/desecapi/serializers.py +++ b/api/desecapi/serializers.py @@ -3,6 +3,7 @@ import json import re from base64 import b64encode +from datetime import timedelta import django.core.exceptions from captcha.audio import AudioCaptcha @@ -839,6 +840,13 @@ class Meta(AuthenticatedBasicUserActionSerializer.Meta): fields = AuthenticatedBasicUserActionSerializer.Meta.fields + ('new_email',) +class AuthenticatedConfirmAccountUserActionSerializer(AuthenticatedBasicUserActionSerializer): + validity_period = timedelta(days=14) + + class Meta(AuthenticatedBasicUserActionSerializer.Meta): + model = models.AuthenticatedNoopUserAction # confirmation happens during authentication, so nothing left to do + + class AuthenticatedResetPasswordUserActionSerializer(AuthenticatedBasicUserActionSerializer): new_password = serializers.CharField(write_only=True) diff --git a/api/desecapi/templates/emails/confirm-account/content.txt b/api/desecapi/templates/emails/confirm-account/content.txt new file mode 100644 index 000000000..0c70467c7 --- /dev/null +++ b/api/desecapi/templates/emails/confirm-account/content.txt @@ -0,0 +1,25 @@ +Hi there, + +You have registered with deSEC a long time ago (on {{ created|date:"Y-m-d" }}), probably to +set up a DNS domain (e.g. under dedyn.io). At the time when you registered +your account, we did not verify your email address. + +Our terms now require that deSEC account holders provide an up-to-date contact +email address, in case we need to contact you (e.g. when there is a problem +with your domain). We are currently cleaning our database and making sure +that all addresses are verified. + +Therefore, if you would like to continue using your deSEC account, you now +need to verify your email address. To do so, please use the following link +(valid for {% widthratio link_expiration_hours 24 1 %} days): + +{{ confirmation_link.0 }} + +If you do not want to continue using deSEC, we will delete your account once +the link has expired, without contacting you again. In case you miss the +deadline, we invite you to sign up again with the same email address. + +We apologize for the slight inconvenience, and hope you enjoy deSEC! + +Stay secure, +Nils diff --git a/api/desecapi/templates/emails/confirm-account/subject.txt b/api/desecapi/templates/emails/confirm-account/subject.txt new file mode 100644 index 000000000..a6bb3ab58 --- /dev/null +++ b/api/desecapi/templates/emails/confirm-account/subject.txt @@ -0,0 +1 @@ +[deSEC] Important: Please confirm your deSEC DNS account diff --git a/api/desecapi/urls/version_1.py b/api/desecapi/urls/version_1.py index e2bdbd572..6c77c5dd9 100644 --- a/api/desecapi/urls/version_1.py +++ b/api/desecapi/urls/version_1.py @@ -55,6 +55,7 @@ # Authenticated Actions path('v/activate-account//', views.AuthenticatedActivateUserActionView.as_view(), name='confirm-activate-account'), path('v/change-email//', views.AuthenticatedChangeEmailUserActionView.as_view(), name='confirm-change-email'), + path('v/confirm-account//', views.AuthenticatedConfirmAccountUserAction.as_view(), name='confirm-confirm-account'), path('v/reset-password//', views.AuthenticatedResetPasswordUserActionView.as_view(), name='confirm-reset-password'), path('v/delete-account//', views.AuthenticatedDeleteUserActionView.as_view(), name='confirm-delete-account'), path('v/renew-domain//', views.AuthenticatedRenewDomainBasicUserActionView.as_view(), name='confirm-renew-domain'), diff --git a/api/desecapi/views.py b/api/desecapi/views.py index 7ec95e29e..2e401b207 100644 --- a/api/desecapi/views.py +++ b/api/desecapi/views.py @@ -750,6 +750,15 @@ def post(self, request, *args, **kwargs): }) +class AuthenticatedConfirmAccountUserAction(AuthenticatedActionView): + html_url = '/confirm/confirm-account/{code}' + serializer_class = serializers.AuthenticatedConfirmAccountUserActionSerializer + + def post(self, request, *args, **kwargs): + super().post(request, *args, **kwargs) + return Response({'detail': 'Success! Your account status has been confirmed.'}) + + class AuthenticatedResetPasswordUserActionView(AuthenticatedActionView): html_url = '/confirm/reset-password/{code}/' serializer_class = serializers.AuthenticatedResetPasswordUserActionSerializer