"""User database model.""" import json import uuid from sqlalchemy import Column, String, Boolean, Text from sqlalchemy.sql import func from sqlalchemy.types import DateTime from app.db.base import Base class User(Base): """User model for authentication and authorization.""" __tablename__ = "users" # Using String(36) for UUID to support SQLite id = Column(String(36), primary_key=True, default=lambda: str(uuid.uuid4())) username = Column(String(100), unique=True, nullable=False, index=True) email = Column(String(255), unique=True, nullable=False, index=True) hashed_password = Column(String(255), nullable=False) is_active = Column(Boolean, default=True, nullable=False) is_superuser = Column(Boolean, default=False, nullable=False) # User permissions for modules (JSON stored as text for SQLite compatibility) # Format: {"playlists": true, "downloads": false, "chromecast": true} # null means inherit from global settings (all enabled by default) _permissions = Column("permissions", Text, nullable=True) # 2FA fields totp_secret = Column(String(32), nullable=True) # Base32 encoded TOTP secret totp_enabled = Column(Boolean, default=False, nullable=False) totp_backup_codes = Column(Text, nullable=True) # JSON array of backup codes created_at = Column(DateTime, server_default=func.now(), nullable=False) updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now(), nullable=False) last_login = Column(DateTime, nullable=True) @property def permissions(self) -> dict: """Get permissions as a dictionary.""" if self._permissions: try: return json.loads(self._permissions) except json.JSONDecodeError: return {} return {} @permissions.setter def permissions(self, value: dict): """Set permissions from a dictionary.""" if value is None: self._permissions = None else: self._permissions = json.dumps(value) @property def backup_codes(self) -> list: """Get backup codes as a list.""" if self.totp_backup_codes: try: return json.loads(self.totp_backup_codes) except json.JSONDecodeError: return [] return [] @backup_codes.setter def backup_codes(self, value: list): """Set backup codes from a list.""" if value is None: self.totp_backup_codes = None else: self.totp_backup_codes = json.dumps(value) def __repr__(self): return f""