"""SOC analyst sessions (bcrypt-hashed opaque tokens). Decision D-006: bcrypt hash stored; the clear token is generated server-side at session creation, returned **once** in the API response and delivered out-of-band. Never re-displayable. """ from __future__ import annotations from datetime import datetime from typing import TYPE_CHECKING from uuid import UUID from sqlalchemy import DateTime, ForeignKey, String from sqlalchemy.orm import Mapped, mapped_column, relationship from mimic.db.base import Base, TimestampsMixin, UuidPkMixin if TYPE_CHECKING: from mimic.db.models.engagement import Engagement from mimic.db.models.user import User class SocSession(UuidPkMixin, TimestampsMixin, Base): __tablename__ = "soc_session" user_id: Mapped[UUID] = mapped_column( ForeignKey("user.id", ondelete="CASCADE"), nullable=False, ) engagement_id: Mapped[UUID] = mapped_column( ForeignKey("engagement.id", ondelete="CASCADE"), nullable=False, ) token_hash: Mapped[str] = mapped_column(String(255), nullable=False, unique=True) # bcrypt hash. Plain token returned once at creation. expires_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), nullable=False ) revoked_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True)) last_ip: Mapped[str | None] = mapped_column(String(64)) last_user_agent: Mapped[str | None] = mapped_column(String(512)) last_used_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True)) user: Mapped[User] = relationship(back_populates="soc_sessions") engagement: Mapped[Engagement] = relationship()