snapshot: preserve account security and profile UI cleanup
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.hashers import check_password, make_password
|
||||
from django.core.validators import FileExtensionValidator
|
||||
from django.db import models
|
||||
from django.utils.translation import get_language
|
||||
@@ -43,6 +44,7 @@ class UserProfile(models.Model):
|
||||
totp_secret = models.CharField(max_length=64, blank=True, default='')
|
||||
totp_enabled = models.BooleanField(default=False)
|
||||
totp_confirmed_at = models.DateTimeField(null=True, blank=True)
|
||||
totp_recovery_codes = models.JSONField(default=list, blank=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
class Meta:
|
||||
@@ -56,13 +58,31 @@ class UserProfile(models.Model):
|
||||
self.totp_secret = ''
|
||||
self.totp_enabled = False
|
||||
self.totp_confirmed_at = None
|
||||
self.save(update_fields=['totp_secret', 'totp_enabled', 'totp_confirmed_at', 'updated_at'])
|
||||
self.totp_recovery_codes = []
|
||||
self.save(update_fields=['totp_secret', 'totp_enabled', 'totp_confirmed_at', 'totp_recovery_codes', 'updated_at'])
|
||||
|
||||
def enable_totp(self, secret: str) -> None:
|
||||
def enable_totp(self, secret: str, recovery_codes: list[str]) -> None:
|
||||
self.totp_secret = secret
|
||||
self.totp_enabled = True
|
||||
self.totp_confirmed_at = timezone.now()
|
||||
self.save(update_fields=['totp_secret', 'totp_enabled', 'totp_confirmed_at', 'updated_at'])
|
||||
self.set_recovery_codes(recovery_codes)
|
||||
self.save(update_fields=['totp_secret', 'totp_enabled', 'totp_confirmed_at', 'totp_recovery_codes', 'updated_at'])
|
||||
|
||||
def set_recovery_codes(self, recovery_codes: list[str]) -> None:
|
||||
self.totp_recovery_codes = [make_password(code) for code in recovery_codes]
|
||||
|
||||
def consume_recovery_code(self, raw_code: str) -> bool:
|
||||
remaining_hashes = []
|
||||
matched = False
|
||||
for hashed_code in self.totp_recovery_codes or []:
|
||||
if not matched and check_password(raw_code, hashed_code):
|
||||
matched = True
|
||||
continue
|
||||
remaining_hashes.append(hashed_code)
|
||||
if matched:
|
||||
self.totp_recovery_codes = remaining_hashes
|
||||
self.save(update_fields=['totp_recovery_codes', 'updated_at'])
|
||||
return matched
|
||||
|
||||
|
||||
class PortalBranding(models.Model):
|
||||
|
||||
Reference in New Issue
Block a user