MediaRouter / app /security /models.py
basyx's picture
Upload 340 files
3493993 verified
Raw
History Blame Contribute Delete
13.9 kB
from __future__ import annotations
from datetime import datetime, timezone
from uuid import uuid4
from sqlalchemy import (
JSON,
BigInteger,
DateTime,
ForeignKey,
Index,
Integer,
String,
Text,
UniqueConstraint,
)
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
def utcnow() -> datetime:
return datetime.now(timezone.utc)
class Base(DeclarativeBase):
pass
class User(Base):
"""An authoritative MediaRouter actor, independent from API credentials."""
__tablename__ = "users"
__table_args__ = (
UniqueConstraint("subject", name="uq_users_subject"),
Index("ix_users_status", "status"),
)
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid4()))
# `subject` is an immutable backend identity (for example an IdP subject),
# never an API-key secret or a user-supplied workspace selector.
subject: Mapped[str] = mapped_column(String(255), nullable=False)
display_name: Mapped[str | None] = mapped_column(String(255))
status: Mapped[str] = mapped_column(String(32), nullable=False, default="active")
metadata_json: Mapped[dict[str, object]] = mapped_column(
"metadata", JSON, nullable=False, default=dict
)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), nullable=False, default=utcnow
)
updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), nullable=False, default=utcnow, onupdate=utcnow
)
class Workspace(Base):
"""A first-class tenant. API keys must be bound through a membership."""
__tablename__ = "workspaces"
__table_args__ = (
UniqueConstraint("slug", name="uq_workspaces_slug"),
Index("ix_workspaces_status", "status"),
)
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid4()))
slug: Mapped[str] = mapped_column(String(120), nullable=False)
name: Mapped[str] = mapped_column(String(255), nullable=False)
status: Mapped[str] = mapped_column(String(32), nullable=False, default="active")
metadata_json: Mapped[dict[str, object]] = mapped_column(
"metadata", JSON, nullable=False, default=dict
)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), nullable=False, default=utcnow
)
updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), nullable=False, default=utcnow, onupdate=utcnow
)
class WorkspaceMembership(Base):
"""Authorizes a user to act inside one workspace."""
__tablename__ = "workspace_memberships"
__table_args__ = (
UniqueConstraint("workspace_id", "user_id", name="uq_workspace_membership"),
Index("ix_workspace_memberships_user", "user_id"),
Index("ix_workspace_memberships_workspace", "workspace_id"),
)
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid4()))
workspace_id: Mapped[str] = mapped_column(
String(36), ForeignKey("workspaces.id", ondelete="CASCADE"), nullable=False
)
user_id: Mapped[str] = mapped_column(
String(36), ForeignKey("users.id", ondelete="CASCADE"), nullable=False
)
role: Mapped[str] = mapped_column(String(32), nullable=False, default="member")
status: Mapped[str] = mapped_column(String(32), nullable=False, default="active")
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), nullable=False, default=utcnow
)
updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), nullable=False, default=utcnow, onupdate=utcnow
)
class APIKey(Base):
__tablename__ = "api_keys"
__table_args__ = (
Index("ix_api_keys_key_prefix", "key_prefix"),
Index("ix_api_keys_key_hash", "key_hash", unique=True),
Index("ix_api_keys_status", "status"),
Index("ix_api_keys_expires_at", "expires_at"),
)
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid4()))
name: Mapped[str] = mapped_column(String(120), nullable=False)
key_prefix: Mapped[str] = mapped_column(String(40), nullable=False)
key_hash: Mapped[str] = mapped_column(String(64), nullable=False)
environment: Mapped[str] = mapped_column(String(16), nullable=False)
status: Mapped[str] = mapped_column(String(16), nullable=False, default="active")
role: Mapped[str | None] = mapped_column(String(64), nullable=True)
scopes: Mapped[list[str]] = mapped_column(JSON, nullable=False, default=list)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), nullable=False, default=utcnow
)
last_used_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
expires_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
grace_expires_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
created_by: Mapped[str | None] = mapped_column(String(120))
notes: Mapped[str | None] = mapped_column(Text)
rotated_from_id: Mapped[str | None] = mapped_column(
String(36), ForeignKey("api_keys.id", ondelete="SET NULL")
)
requests_per_minute: Mapped[int] = mapped_column(Integer, nullable=False, default=100)
concurrent_jobs: Mapped[int] = mapped_column(Integer, nullable=False, default=10)
uploads_per_hour: Mapped[int] = mapped_column(Integer, nullable=False, default=20)
processing_bytes_per_day: Mapped[int] = mapped_column(
BigInteger, nullable=False, default=107_374_182_400
)
class APIKeyPrincipal(Base):
"""Server-side API-key-to-membership binding.
Keeping this association outside the opaque API-key record prevents a
credential identifier from accidentally becoming a tenant identifier.
"""
__tablename__ = "api_key_principals"
__table_args__ = (
UniqueConstraint("api_key_id", name="uq_api_key_principal_key"),
Index("ix_api_key_principals_workspace", "workspace_id"),
Index("ix_api_key_principals_user", "user_id"),
)
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid4()))
api_key_id: Mapped[str] = mapped_column(
String(36), ForeignKey("api_keys.id", ondelete="CASCADE"), nullable=False
)
workspace_id: Mapped[str] = mapped_column(
String(36), ForeignKey("workspaces.id", ondelete="CASCADE"), nullable=False
)
user_id: Mapped[str] = mapped_column(
String(36), ForeignKey("users.id", ondelete="CASCADE"), nullable=False
)
membership_id: Mapped[str] = mapped_column(
String(36), ForeignKey("workspace_memberships.id", ondelete="RESTRICT"), nullable=False
)
status: Mapped[str] = mapped_column(String(32), nullable=False, default="active")
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), nullable=False, default=utcnow
)
updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), nullable=False, default=utcnow, onupdate=utcnow
)
class CanonicalMediaAsset(Base):
"""Workspace-owned output locator issued only by the MediaRouter pipeline."""
__tablename__ = "media_assets"
__table_args__ = (
UniqueConstraint("request_id", "filename", name="uq_media_asset_output"),
Index("ix_media_assets_workspace_created", "workspace_id", "created_at"),
Index("ix_media_assets_workspace_request", "workspace_id", "request_id"),
Index(
"ix_media_assets_workspace_project_created", "workspace_id", "project_id", "created_at"
),
)
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid4()))
workspace_id: Mapped[str] = mapped_column(
String(36), ForeignKey("workspaces.id", ondelete="RESTRICT"), nullable=False
)
# Assets may remain workspace-level. Once attached, one canonical asset
# has one project parent; PostgreSQL also verifies matching workspaces.
project_id: Mapped[str | None] = mapped_column(
String(36), ForeignKey("projects.id", ondelete="RESTRICT")
)
request_id: Mapped[str] = mapped_column(String(36), nullable=False)
filename: Mapped[str] = mapped_column(String(255), nullable=False)
mime_type: Mapped[str] = mapped_column(String(255), nullable=False)
file_size: Mapped[int] = mapped_column(BigInteger, nullable=False)
sha256: Mapped[str] = mapped_column(String(64), nullable=False)
metadata_json: Mapped[dict[str, object]] = mapped_column(
"metadata", JSON, nullable=False, default=dict
)
created_by_user_id: Mapped[str | None] = mapped_column(
String(36), ForeignKey("users.id", ondelete="SET NULL")
)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), nullable=False, default=utcnow
)
updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), nullable=False, default=utcnow, onupdate=utcnow
)
class CanonicalMediaVariant(Base):
"""An immutable derivative of a canonical asset, owned by the same tenant."""
__tablename__ = "media_asset_variants"
__table_args__ = (
UniqueConstraint(
"asset_id", "request_id", "filename", name="uq_media_asset_variant_output"
),
Index("ix_media_asset_variants_workspace_asset", "workspace_id", "asset_id"),
)
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid4()))
workspace_id: Mapped[str] = mapped_column(
String(36), ForeignKey("workspaces.id", ondelete="RESTRICT"), nullable=False
)
asset_id: Mapped[str] = mapped_column(
String(36), ForeignKey("media_assets.id", ondelete="CASCADE"), nullable=False
)
request_id: Mapped[str] = mapped_column(String(36), nullable=False)
filename: Mapped[str] = mapped_column(String(255), nullable=False)
mime_type: Mapped[str] = mapped_column(String(255), nullable=False)
file_size: Mapped[int] = mapped_column(BigInteger, nullable=False)
sha256: Mapped[str] = mapped_column(String(64), nullable=False)
metadata_json: Mapped[dict[str, object]] = mapped_column(
"metadata", JSON, nullable=False, default=dict
)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), nullable=False, default=utcnow
)
class AuditLog(Base):
__tablename__ = "audit_logs"
__table_args__ = (
Index("ix_audit_logs_api_key_id", "api_key_id"),
Index("ix_audit_logs_created_at", "created_at"),
Index("ix_audit_logs_request_id", "request_id"),
)
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid4()))
request_id: Mapped[str] = mapped_column(String(36), nullable=False)
api_key_id: Mapped[str | None] = mapped_column(
String(36), ForeignKey("api_keys.id", ondelete="SET NULL")
)
key_name: Mapped[str | None] = mapped_column(String(120))
ip_address: Mapped[str | None] = mapped_column(String(64))
user_agent: Mapped[str | None] = mapped_column(String(512))
endpoint: Mapped[str] = mapped_column(String(1024), nullable=False)
http_method: Mapped[str] = mapped_column(String(16), nullable=False)
response_code: Mapped[int] = mapped_column(Integer, nullable=False)
processing_time_ms: Mapped[int] = mapped_column(Integer, nullable=False)
bytes_uploaded: Mapped[int] = mapped_column(BigInteger, nullable=False, default=0)
bytes_downloaded: Mapped[int] = mapped_column(BigInteger, nullable=False, default=0)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), nullable=False, default=utcnow
)
class AuditEvent(Base):
"""Safe workspace-scoped domain event recorded by the shared AuditService."""
__tablename__ = "audit_events"
__table_args__ = (
Index("ix_audit_events_workspace_created", "workspace_id", "created_at"),
Index("ix_audit_events_type", "event_type"),
Index("ix_audit_events_entity", "entity_type", "entity_id"),
)
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid4()))
workspace_id: Mapped[str] = mapped_column(
String(36), ForeignKey("workspaces.id", ondelete="RESTRICT"), nullable=False
)
actor_user_id: Mapped[str | None] = mapped_column(
String(36), ForeignKey("users.id", ondelete="SET NULL")
)
api_key_id: Mapped[str | None] = mapped_column(
String(36), ForeignKey("api_keys.id", ondelete="SET NULL")
)
event_type: Mapped[str] = mapped_column(String(100), nullable=False)
entity_type: Mapped[str] = mapped_column(String(64), nullable=False)
entity_id: Mapped[str] = mapped_column(String(36), nullable=False)
request_id: Mapped[str | None] = mapped_column(String(64))
metadata_json: Mapped[dict[str, object]] = mapped_column(
"metadata", JSON, nullable=False, default=dict
)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), nullable=False, default=utcnow
)
class RateLimit(Base):
__tablename__ = "rate_limits"
__table_args__ = (
Index("ix_rate_limits_api_key_id", "api_key_id"),
Index("ix_rate_limits_bucket_start", "bucket_start"),
)
api_key_id: Mapped[str] = mapped_column(
String(36), ForeignKey("api_keys.id", ondelete="CASCADE"), primary_key=True
)
bucket_type: Mapped[str] = mapped_column(String(32), primary_key=True)
bucket_start: Mapped[datetime] = mapped_column(DateTime(timezone=True), primary_key=True)
count: Mapped[int] = mapped_column(BigInteger, nullable=False, default=0)
units: Mapped[int] = mapped_column(BigInteger, nullable=False, default=0)
updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), nullable=False, default=utcnow, onupdate=utcnow
)