@@ -97,6 +97,8 @@ def create_user(self, username: str, email: str, password: Optional[str] = None,
9797 'email_verified' : False , # Email verification status
9898 'email_verification_code' : None , # Verification code
9999 'email_verification_expires' : None , # Verification code expiry
100+ 'login_email_code' : None , # Code for Email 2FA on login
101+ 'login_email_code_expires' : None , # Expiry for login code
100102 'security_questions' : security_questions or [],
101103 'is_admin' : False , # Admin status
102104 'is_banned' : False ,
@@ -110,7 +112,8 @@ def create_user(self, username: str, email: str, password: Optional[str] = None,
110112 'preferences' : {
111113 'theme' : 'dark' ,
112114 'language' : 'pt' ,
113- 'anonymous_mode' : False
115+ 'anonymous_mode' : False ,
116+ 'email_2fa_enabled' : False # Email 2FA (One-time code on login)
114117 },
115118 'backup_codes' : [], # Will be generated during TOTP setup
116119 'recovery_code' : None , # Recovery email code (for email verification)
@@ -702,6 +705,44 @@ def is_email_verified(self, user_id: str) -> bool:
702705 user = self .collection .find_one ({'_id' : ObjectId (user_id )})
703706 return user .get ('email_verified' , False ) if user else False
704707
708+ # Login Email 2FA Methods
709+ def set_login_email_code (self , user_id : str , code : str , expires_in_minutes : int = 15 ) -> bool :
710+ """Set email code for login verification."""
711+ from datetime import timedelta
712+ expires_at = datetime .utcnow () + timedelta (minutes = expires_in_minutes )
713+
714+ result = self .collection .update_one (
715+ {'_id' : ObjectId (user_id )},
716+ {'$set' : {
717+ 'login_email_code' : code ,
718+ 'login_email_code_expires' : expires_at
719+ }}
720+ )
721+ return result .modified_count > 0
722+
723+ def verify_login_email_code (self , user_id : str , code : str ) -> bool :
724+ """Verify email code for login."""
725+ user = self .collection .find_one ({'_id' : ObjectId (user_id )})
726+ if not user :
727+ return False
728+
729+ # Check if code matches and hasn't expired
730+ if (user .get ('login_email_code' ) == code and
731+ user .get ('login_email_code_expires' ) and
732+ user ['login_email_code_expires' ] > datetime .utcnow ()):
733+
734+ # Clear verification code
735+ self .collection .update_one (
736+ {'_id' : ObjectId (user_id )},
737+ {'$set' : {
738+ 'login_email_code' : None ,
739+ 'login_email_code_expires' : None
740+ }}
741+ )
742+ return True
743+
744+ return False
745+
705746 # Passwordless Authentication Methods
706747 def verify_user_by_username_or_email (self , identifier : str ) -> Optional [Dict [str , Any ]]:
707748 """Find user by username or email for passwordless auth."""
0 commit comments