"""Activation session model — temporary activation window for a device membership.""" from gatehouse_app.extensions import db from gatehouse_app.models.base import BaseModel from gatehouse_app.utils.constants import ActivationEndReason class ActivationSession(BaseModel): """A temporary activation window for an already-approved device membership. Created when a user re-authenticates in the portal and wants to activate an approved device on a network. Has a fixed lifetime (e.g. 8 hours). When it expires the membership is de-authorized in ZeroTier but the underlying approval record is untouched. Attributes: organization_id: FK to the organization user_id: FK to the user who owns the session device_network_membership_id: FK to the related membership authenticated_at: When the user re-authenticated to start this session expires_at: When the activation window ends ended_at: When the session was explicitly ended (null if still active) end_reason: Why the session ended (expired, logout, kill_switch, etc.) created_by: FK to the user who triggered activation (usually same as user_id) """ __tablename__ = "activation_sessions" organization_id = db.Column( db.String(36), db.ForeignKey("organizations.id"), nullable=False, index=True, ) user_id = db.Column( db.String(36), db.ForeignKey("users.id"), nullable=False, index=True, ) device_network_membership_id = db.Column( db.String(36), db.ForeignKey("device_network_memberships.id"), nullable=False, index=True, ) authenticated_at = db.Column( db.DateTime, nullable=False, ) expires_at = db.Column( db.DateTime, nullable=False, ) ended_at = db.Column(db.DateTime, nullable=True) end_reason = db.Column( db.Enum(ActivationEndReason, name="activation_end_reason"), nullable=True, ) created_by = db.Column( db.String(36), db.ForeignKey("users.id"), nullable=False, ) # Relationships organization = db.relationship("Organization", backref="activation_sessions") user = db.relationship( "User", foreign_keys=[user_id], backref="activation_sessions", ) created_by_user = db.relationship( "User", foreign_keys=[created_by], backref="created_activation_sessions", ) membership = db.relationship( "DeviceNetworkMembership", back_populates="activation_sessions", ) def __repr__(self): return ( f"" ) @property def is_expired(self) -> bool: from datetime import datetime, timezone now = datetime.now(timezone.utc) exp = self.expires_at if exp.tzinfo is None: exp = exp.replace(tzinfo=timezone.utc) return now > exp @property def is_active(self) -> bool: return self.ended_at is None and not self.is_expired def to_dict(self, exclude=None): exclude = exclude or [] data = super().to_dict(exclude=exclude) data["is_expired"] = self.is_expired data["is_active"] = self.is_active return data