from sqlalchemy import ( Column, Integer, String, DateTime, Boolean, Text, ForeignKey, Index, Enum as SQLEnum, ) from sqlalchemy.orm import relationship from datetime import datetime from enum import Enum from database.models.generic_model import Base class User(Base): __tablename__ = "users" id = Column(String(36), primary_key=True) email = Column(String(255), unique=True, nullable=False, index=True) hashed_password = Column(String(255), nullable=False) nom = Column(String(100), nullable=False) prenom = Column(String(100), nullable=False) role = Column(String(50), default="user") is_verified = Column(Boolean, default=False, index=True) verification_token = Column(String(255), nullable=True, unique=True, index=True) verification_token_expires = Column(DateTime, nullable=True) is_active = Column(Boolean, default=True, index=True) failed_login_attempts = Column(Integer, default=0) locked_until = Column(DateTime, nullable=True) reset_token = Column(String(255), nullable=True, unique=True, index=True) reset_token_expires = Column(DateTime, nullable=True) password_changed_at = Column(DateTime, nullable=True) must_change_password = Column(Boolean, default=False) created_at = Column(DateTime, default=datetime.now, nullable=False) updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now) last_login = Column(DateTime, nullable=True) last_login_ip = Column(String(45), nullable=True) refresh_tokens = relationship( "RefreshToken", back_populates="user", cascade="all, delete-orphan" ) audit_logs = relationship( "AuditLog", back_populates="user", cascade="all, delete-orphan" ) def __repr__(self): return f"" class RefreshToken(Base): __tablename__ = "refresh_tokens" id = Column(String(36), primary_key=True) user_id = Column( String(36), ForeignKey("users.id", ondelete="CASCADE"), nullable=False, index=True, ) token_hash = Column(String(64), unique=True, nullable=False, index=True) token_id = Column(String(32), unique=True, nullable=False, index=True) fingerprint_hash = Column(String(64), nullable=True) device_info = Column(String(500), nullable=True) ip_address = Column(String(45), nullable=True) is_revoked = Column(Boolean, default=False, index=True) revoked_at = Column(DateTime, nullable=True) revoked_reason = Column(String(100), nullable=True) is_used = Column(Boolean, default=False) used_at = Column(DateTime, nullable=True) replaced_by = Column(String(36), nullable=True) expires_at = Column(DateTime, nullable=False, index=True) created_at = Column(DateTime, default=datetime.now, nullable=False) last_used_at = Column(DateTime, nullable=True) user = relationship("User", back_populates="refresh_tokens") __table_args__ = ( Index("ix_refresh_tokens_user_valid", "user_id", "is_revoked", "expires_at"), ) def __repr__(self): return f"" class AuditEventType(str, Enum): LOGIN_SUCCESS = "login_success" LOGIN_FAILED = "login_failed" LOGOUT = "logout" PASSWORD_CHANGE = "password_change" PASSWORD_RESET_REQUEST = "password_reset_request" PASSWORD_RESET_COMPLETE = "password_reset_complete" EMAIL_VERIFICATION = "email_verification" ACCOUNT_LOCKED = "account_locked" ACCOUNT_UNLOCKED = "account_unlocked" TOKEN_REFRESH = "token_refresh" TOKEN_REVOKED = "token_revoked" SUSPICIOUS_ACTIVITY = "suspicious_activity" SESSION_CREATED = "session_created" SESSION_TERMINATED = "session_terminated" class AuditLog(Base): __tablename__ = "audit_logs" id = Column(String(36), primary_key=True) user_id = Column( String(36), ForeignKey("users.id", ondelete="SET NULL"), nullable=True, index=True, ) event_type = Column(SQLEnum(AuditEventType), nullable=False, index=True) event_description = Column(Text, nullable=True) ip_address = Column(String(45), nullable=True, index=True) user_agent = Column(String(500), nullable=True) fingerprint_hash = Column(String(64), nullable=True) resource_type = Column(String(50), nullable=True) resource_id = Column(String(100), nullable=True) request_method = Column(String(10), nullable=True) request_path = Column(String(500), nullable=True) meta = Column("metadata", Text, nullable=True) success = Column(Boolean, default=True) failure_reason = Column(String(255), nullable=True) created_at = Column(DateTime, default=datetime.now, nullable=False, index=True) user = relationship("User", back_populates="audit_logs") __table_args__ = ( Index("ix_audit_logs_user_event", "user_id", "event_type", "created_at"), Index("ix_audit_logs_ip_event", "ip_address", "event_type", "created_at"), ) def __repr__(self): return f"" class LoginAttempt(Base): __tablename__ = "login_attempts" id = Column(Integer, primary_key=True, autoincrement=True) email = Column(String(255), nullable=False, index=True) ip_address = Column(String(45), nullable=True, index=True) user_agent = Column(String(500), nullable=True) fingerprint_hash = Column(String(64), nullable=True) success = Column(Boolean, default=False, index=True) failure_reason = Column(String(255), nullable=True) timestamp = Column(DateTime, default=datetime.now, nullable=False, index=True) __table_args__ = ( Index("ix_login_attempts_email_time", "email", "timestamp"), Index("ix_login_attempts_ip_time", "ip_address", "timestamp"), ) def __repr__(self): return f"" class UserSession(Base): __tablename__ = "user_sessions" id = Column(String(36), primary_key=True) user_id = Column( String(36), ForeignKey("users.id", ondelete="CASCADE"), nullable=False, index=True, ) session_token_hash = Column(String(64), unique=True, nullable=False, index=True) refresh_token_id = Column(String(36), nullable=True) device_info = Column(String(500), nullable=True) ip_address = Column(String(45), nullable=True) fingerprint_hash = Column(String(64), nullable=True) location = Column(String(255), nullable=True) is_active = Column(Boolean, default=True, index=True) terminated_at = Column(DateTime, nullable=True) termination_reason = Column(String(100), nullable=True) created_at = Column(DateTime, default=datetime.now, nullable=False) last_activity = Column(DateTime, default=datetime.now, nullable=False) expires_at = Column(DateTime, nullable=False) __table_args__ = (Index("ix_user_sessions_user_active", "user_id", "is_active"),) def __repr__(self): return f""