Saas-Base / apps /security /models /session.py
rsnarsna
first commit
667aacd
Raw
History Blame Contribute Delete
1.85 kB
"""
Session model — custom session management with fingerprint binding.
Maps to AGENT.md DBML: `sessions` table (Lines 190-200).
"""
import uuid
from django.conf import settings
from django.db import models
from django.utils import timezone
from core.models import BaseModel
class Session(BaseModel):
"""
Custom user session with device fingerprinting.
Provides revocable sessions bound to IP/User-Agent to prevent hijacking.
Replaces Django's default session for authenticated users.
"""
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name="security_sessions",
)
refresh_token_id = models.UUIDField(
unique=True,
null=True,
blank=True,
help_text="Link to refresh token chain.",
)
fingerprint = models.CharField(
max_length=255,
blank=True,
default="",
help_text="Device fingerprint (hashed IP+UA+headers).",
)
ip_address = models.GenericIPAddressField(null=True, blank=True)
user_agent = models.TextField(blank=True, default="")
expires_at = models.DateTimeField()
revoked_at = models.DateTimeField(null=True, blank=True)
class Meta:
db_table = "sessions"
ordering = ["-created_at"]
indexes = [
models.Index(fields=["user", "-created_at"]),
models.Index(fields=["expires_at"]),
]
def __str__(self) -> str:
return f"Session {self.pk} for {self.user_id}"
@property
def is_active(self) -> bool:
"""Check if session is still valid."""
return self.revoked_at is None and self.expires_at > timezone.now()
def revoke(self) -> None:
"""Revoke this session."""
self.revoked_at = timezone.now()
self.save(update_fields=["revoked_at"])