snapshot: preserve account security and profile UI cleanup

This commit is contained in:
Md Bayazid Bostame
2026-03-27 03:04:02 +01:00
parent c679488437
commit f2c9b3b65d
12 changed files with 699 additions and 370 deletions

View File

@@ -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):