1- from allauth .mfa .totp .internal .auth import TOTP
1+ from allauth .mfa .base .internal .flows import check_rate_limit
2+ from allauth .mfa .models import Authenticator
3+ from django .contrib .auth .hashers import check_password
24from rest_framework import serializers
35from rest_framework .exceptions import NotFound , ValidationError
46
@@ -21,9 +23,9 @@ class Meta:
2123 )
2224
2325
24- class TOTPCodeSerializer (serializers .Serializer ):
26+ class MfaCodeSerializer (serializers .Serializer ):
2527 """
26- Intended to be used for validating TOTP codes
28+ Intended to be used for validating TOTP and recovery codes
2729 """
2830
2931 code = serializers .CharField ()
@@ -38,10 +40,31 @@ def __init__(self, *args, **kwargs):
3840 )
3941 except MfaMethodsWrapper .DoesNotExist :
4042 raise NotFound
41- self .totp = TOTP (self .mfamethods .totp )
4243
43- def validate_code (self , value ):
44- if not self .totp .validate_code (value ):
45- raise ValidationError ('Invalid TOTP code' )
44+ def validate_code (self , code ):
45+ clear_rl = check_rate_limit (self .user )
46+ for auth in [self .mfamethods .totp , self .mfamethods .recovery_codes ]:
47+ if auth .wrap ().validate_code (code ):
48+ self .authenticator = auth
49+ clear_rl ()
50+ return code
51+ if auth .type == Authenticator .Type .RECOVERY_CODES :
52+ hashed_code = self .validate_migrated_codes (code , auth .wrap ())
53+ if hashed_code is not None :
54+ self .authenticator = auth
55+ clear_rl ()
56+ return code
4657
47- return value
58+ raise ValidationError ('Invalid code' )
59+
60+ def validate_migrated_codes (self , input_code , recovery_codes ):
61+ codes = recovery_codes ._get_migrated_codes ()
62+ if codes is None :
63+ return
64+ if not codes [0 ].startswith ('pbkdf2_sha256$' ):
65+ return
66+ # if codes are sha256 hashes do the recovery codes logic
67+ for idx , hashed_code in enumerate (codes ):
68+ if check_password (input_code , hashed_code ):
69+ recovery_codes .validate_code (hashed_code )
70+ return hashed_code
0 commit comments