Skip to content

Commit

Permalink
feat(api): add User.email_verified, set during auth action authentica…
Browse files Browse the repository at this point in the history
…tion
  • Loading branch information
peterthomassen committed Jan 17, 2022
1 parent e1f4030 commit b801018
Show file tree
Hide file tree
Showing 4 changed files with 45 additions and 2 deletions.
8 changes: 6 additions & 2 deletions api/desecapi/authentication.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import base64
from datetime import datetime, timezone
from ipaddress import ip_address

from django.contrib.auth.hashers import PBKDF2PasswordHasher
Expand Down Expand Up @@ -161,11 +162,14 @@ def authenticate(self, request):
def authenticate_credentials(self, context):
serializer = AuthenticatedBasicUserActionSerializer(data={}, context=context)
serializer.is_valid(raise_exception=True)

user = serializer.validated_data['user']

email_verified = datetime.fromtimestamp(serializer.timestamp, timezone.utc)
user.email_verified = max(user.email_verified or email_verified, email_verified)
user.save()

if user.is_active == False: # don't catch None here!
raise exceptions.AuthenticationFailed('User inactive.')

return user, None


Expand Down
30 changes: 30 additions & 0 deletions api/desecapi/migrations/0020_user_email_verified.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Generated by Django 4.0.1 on 2022-01-14 13:39

import datetime

from django.db import migrations, models
from django.db.models import F, Q


def forwards_func(apps, schema_editor):
User = apps.get_model("desecapi", "User")
db_alias = schema_editor.connection.alias
filter = Q(is_active=True) | ~Q(last_login__isnull=True)
filter_kwargs = dict(created__date__gte=datetime.date(2019, 10, 23))
User.objects.using(db_alias).filter(filter, **filter_kwargs).update(email_verified=F('created'))


class Migration(migrations.Migration):

dependencies = [
('desecapi', '0019_alter_user_is_active'),
]

operations = [
migrations.AddField(
model_name='user',
name='email_verified',
field=models.DateTimeField(blank=True, null=True),
),
migrations.RunPython(forwards_func, migrations.RunPython.noop),
]
1 change: 1 addition & 0 deletions api/desecapi/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ def _limit_domains_default():
verbose_name='email address',
unique=True,
)
email_verified = models.DateTimeField(null=True, blank=True)
is_active = models.BooleanField(default=True, null=True)
is_admin = models.BooleanField(default=False)
created = models.DateTimeField(auto_now_add=True)
Expand Down
8 changes: 8 additions & 0 deletions api/desecapi/tests/test_user_management.py
Original file line number Diff line number Diff line change
Expand Up @@ -926,6 +926,14 @@ def test_action_code_confusion(self):
self.assertVerificationFailureInvalidCodeResponse(self.client.verify(reset_password_link,
data={'new_password': 'dummy'}))

def test_action_code_updates_email_verified(self):
email_verified = User.objects.get(email=self.email).email_verified
with mock.patch('time.time', return_value=time.time() + 1):
self.assertResetPasswordSuccessResponse(self.reset_password(self.email))
confirmation_link = self.assertResetPasswordEmail(self.email)
self.client.verify(confirmation_link) # even without payload
self.assertGreaterEqual((User.objects.get(email=self.email).email_verified - email_verified).total_seconds(), 1)


class RenewTestCase(UserManagementTestCase, DomainOwnerTestCase):
DYN = False
Expand Down

0 comments on commit b801018

Please sign in to comment.