""" 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"])