import uuid from hashlib import md5 from typing import TYPE_CHECKING, List from barker.models.login_history import LoginHistory from barker.models.user_role import UserRole from sqlalchemy import Boolean, Index, Unicode, desc, func, select, text from sqlalchemy.dialects.postgresql import UUID from sqlalchemy.ext.hybrid import hybrid_property from sqlalchemy.orm import Mapped, Session, mapped_column, relationship from ..db.base_class import reg if TYPE_CHECKING: from .role import Role @reg.mapped_as_dataclass(unsafe_hash=True) class User: __tablename__ = "users" id: Mapped[uuid.UUID] = mapped_column( "id", UUID(as_uuid=True), primary_key=True, server_default=text("gen_random_uuid()"), insert_default=uuid.uuid4 ) name: Mapped[str] = mapped_column("name", Unicode(255), nullable=False) _password: Mapped[str] = mapped_column("password", Unicode(60), nullable=False) locked_out: Mapped[bool] = mapped_column("locked_out", Boolean, nullable=False) roles: Mapped[List["Role"]] = relationship( "Role", secondary=UserRole.__table__, order_by="Role.name" # type: ignore[attr-defined] ) login_history: Mapped[List["LoginHistory"]] = relationship(order_by=desc(LoginHistory.date), back_populates="user") Index("uq_users_name", func.lower(name), unique=True) @hybrid_property def password(self): return self._password @password.inplace.setter def _password_setter(self, value: str) -> None: self._password = encrypt(value) def __init__(self, name=None, password=None, locked_out=None, id_=None): self.name = name self.password = password self.locked_out = locked_out self.id = id_ @classmethod def auth(cls, name: str, password: str, db: Session): if password is None: return None user = db.execute(select(User).where(User.name.ilike(name))).scalars().one_or_none() if not user: return None if user.password != encrypt(password) or user.locked_out: return None else: return user def encrypt(val): return md5(val.encode("utf-8") + "v2".encode("utf-8")).hexdigest()