from tortoise import fields, models from passlib.context import CryptContext import datetime import uuid import random import string pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") def generate_short_uuid() -> str: return "".join(random.choices(string.ascii_letters + string.digits, k=5)) class User(models.Model): id = fields.CharField(primary_key=True, max_length=5, default=generate_short_uuid) name = fields.CharField(max_length=100) password = fields.CharField(max_length=100) phoneNumber = fields.CharField(max_length=15, unique=True) balance = fields.DecimalField(max_digits=10, decimal_places=2, default=0.00) mac_address = fields.CharField(max_length=17) createdAt = fields.DatetimeField(auto_now_add=True) updatedAt = fields.DatetimeField(auto_now=True) lastLogin = fields.DatetimeField(default=datetime.datetime.now) failed_attempts = fields.IntField(default=0) account_locked = fields.BooleanField(default=False) reset_token = fields.CharField(max_length=6, null=True, unique=True) reset_token_expiration = fields.DatetimeField(null=True) class Meta: table = "users" def hash_password(self, plain_password: str) -> str: return pwd_context.hash(plain_password) def verify_password(self, plain_password: str) -> bool: if ( self.account_locked and datetime.datetime.now() < self.lastLogin + datetime.timedelta(minutes=15) ): print("Account is locked due to too many failed attempts. Try again later.") return False if pwd_context.verify(plain_password, self.password): self.failed_attempts = 0 self.account_locked = False self.lastLogin = datetime.datetime.now() self.save() return True else: self.failed_attempts += 1 if self.failed_attempts >= 5: self.account_locked = True self.save() return False async def initiate_password_reset(self): self.reset_token = f"{random.randint(100000, 999999)}" self.reset_token_expiration = datetime.datetime.now() + datetime.timedelta( minutes=15 ) await self.save() async def reset_password(self, reset_token: str, new_password: str): if ( self.reset_token != reset_token or datetime.datetime.now() > self.reset_token_expiration ): return False self.password = self.hash_password(new_password) self.reset_token = None self.reset_token_expiration = None await self.save() return True