UMBRA/backend/app/models/session.py
Kyle Pope 89519a6dd3 Fix lock screen bypass, theme flicker, skeleton flash, and sidebar click target
Critical: Lock state was purely React useState — refreshing the page reset it.
Now persisted server-side via is_locked/locked_at columns on user_sessions.
POST /auth/lock sets the flag, /auth/verify-password clears it, and
GET /auth/status returns is_locked so the frontend initializes correctly.

UI: Cache accent color in localStorage and apply via inline script in
index.html before React hydrates to eliminate the cyan flash on load.

UI: Increase TanStack Query gcTime from 5min to 30min so page data
survives component unmount/remount across tab switches without skeleton.

UI: Move Projects nav onClick from the icon element to the full-width
container div so the entire row is clickable when the sidebar is collapsed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 19:00:55 +08:00

28 lines
1.1 KiB
Python

from sqlalchemy import String, Boolean, ForeignKey, func
from sqlalchemy.orm import Mapped, mapped_column
from datetime import datetime
from app.database import Base
class UserSession(Base):
__tablename__ = "user_sessions"
# UUID4 hex — avoids integer primary key enumeration
id: Mapped[str] = mapped_column(String(64), primary_key=True)
user_id: Mapped[int] = mapped_column(
ForeignKey("users.id", ondelete="CASCADE"),
nullable=False,
index=True,
)
created_at: Mapped[datetime] = mapped_column(default=func.now())
expires_at: Mapped[datetime] = mapped_column(nullable=False)
revoked: Mapped[bool] = mapped_column(Boolean, default=False)
# Session lock — persists across page refresh
is_locked: Mapped[bool] = mapped_column(Boolean, default=False, server_default="false")
locked_at: Mapped[datetime | None] = mapped_column(nullable=True)
# Audit fields for security logging
ip_address: Mapped[str | None] = mapped_column(String(45), nullable=True)
user_agent: Mapped[str | None] = mapped_column(String(255), nullable=True)