51 lines
1.9 KiB
Python
51 lines
1.9 KiB
Python
import uuid
|
|
|
|
from sqlalchemy import Column, DateTime, ForeignKey, String, func
|
|
from sqlalchemy.dialects.postgresql import INET, UUID
|
|
|
|
from app.config.database import Base
|
|
|
|
|
|
class UserSession(Base):
|
|
"""One signed-in session, and the refresh token currently standing for it.
|
|
|
|
Revocation used to live only in a Redis blacklist. That failed open — a
|
|
Redis outage made `verify_refresh_token` log a warning and accept the token
|
|
anyway — and it did not survive a flush, so every revoked token quietly came
|
|
back. It also meant nobody could be shown where they were signed in, because
|
|
nothing recorded it.
|
|
|
|
A session row is the durable fact. Redis stays useful as a fast negative
|
|
cache, but it is no longer the only thing standing between a stolen token
|
|
and an account.
|
|
"""
|
|
|
|
__tablename__ = "user_sessions"
|
|
|
|
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
|
user_id = Column(
|
|
UUID(as_uuid=True), ForeignKey("users.id", ondelete="CASCADE"),
|
|
nullable=False, index=True,
|
|
)
|
|
tenant_id = Column(UUID(as_uuid=True), nullable=True, index=True)
|
|
|
|
current_jti = Column(UUID(as_uuid=True), nullable=False, unique=True, index=True)
|
|
previous_jti = Column(UUID(as_uuid=True), nullable=True, index=True)
|
|
|
|
user_agent = Column(String(512), nullable=True)
|
|
ip_address = Column(INET, nullable=True)
|
|
|
|
created_at = Column(DateTime(timezone=True), server_default=func.now(), nullable=False)
|
|
last_used_at = Column(DateTime(timezone=True), server_default=func.now(), nullable=False)
|
|
expires_at = Column(DateTime(timezone=True), nullable=False)
|
|
|
|
revoked_at = Column(DateTime(timezone=True), nullable=True)
|
|
revoked_reason = Column(String(40), nullable=True)
|
|
|
|
@property
|
|
def is_active(self) -> bool:
|
|
return self.revoked_at is None
|
|
|
|
def __repr__(self) -> str:
|
|
return f"<UserSession {self.user_id} {'active' if self.is_active else 'revoked'}>"
|