refresh_token.py 1.5 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152
  1. """
  2. RefreshToken model - stores refresh tokens for users.
  3. """
  4. from datetime import datetime
  5. from sqlalchemy import DateTime, ForeignKey, String
  6. from sqlalchemy.dialects.postgresql import JSONB
  7. from sqlalchemy.orm import Mapped, mapped_column, relationship
  8. from app.models.base import Base
  9. class RefreshToken(Base):
  10. """
  11. Refresh token model for JWT token rotation.
  12. Stores hashed refresh tokens and device info.
  13. Tokens can be revoked by setting revoked_at.
  14. """
  15. __tablename__ = "refresh_tokens"
  16. id: Mapped[int] = mapped_column(primary_key=True)
  17. user_id: Mapped[int] = mapped_column(
  18. ForeignKey("users.id", ondelete="CASCADE"), nullable=False
  19. )
  20. # Token (should be hashed in production, but for MVP we'll store plain)
  21. token: Mapped[str] = mapped_column(String(512), unique=True, nullable=False)
  22. expires_at: Mapped[datetime] = mapped_column(
  23. DateTime(timezone=True), nullable=False
  24. )
  25. # Device tracking (user agent, IP, etc.)
  26. device_info: Mapped[dict | None] = mapped_column(JSONB)
  27. # Revocation
  28. revoked_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
  29. # Relationships
  30. user: Mapped["User"] = relationship("User", back_populates="refresh_tokens")
  31. @property
  32. def is_valid(self) -> bool:
  33. """Check if token is valid (not expired and not revoked)."""
  34. now = datetime.now()
  35. return self.expires_at > now and self.revoked_at is None
  36. def __repr__(self) -> str:
  37. return f"<RefreshToken {self.id} for user {self.user_id}>"