Files
consentos/apps/api/src/schemas/scanner.py
James Cottrill fbf26453f2 feat: initial public release
ConsentOS — a privacy-first cookie consent management platform.

Self-hosted, source-available alternative to OneTrust, Cookiebot, and
CookieYes. Full standards coverage (IAB TCF v2.2, GPP v1, Google
Consent Mode v2, GPC, Shopify Customer Privacy API), multi-tenant
architecture with role-based access, configuration cascade
(system → org → group → site → region), dark-pattern detection in
the scanner, and a tamper-evident consent record audit trail.

This is the initial public release. Prior development history is
retained internally.

See README.md for the feature list, architecture overview, and
quick-start instructions. Licensed under the Elastic Licence 2.0 —
self-host freely; do not resell as a managed service.
2026-04-14 09:18:18 +00:00

143 lines
3.7 KiB
Python

"""Pydantic schemas for scanner and client-side cookie reports."""
from __future__ import annotations
import uuid
from datetime import datetime
from enum import StrEnum
from pydantic import BaseModel, Field
class ScanStatus(StrEnum):
PENDING = "pending"
RUNNING = "running"
COMPLETED = "completed"
FAILED = "failed"
class ScanTrigger(StrEnum):
MANUAL = "manual"
SCHEDULED = "scheduled"
CLIENT_REPORT = "client_report"
# ── Client-side cookie report ────────────────────────────────────────
class ReportedCookie(BaseModel):
"""A single cookie/storage item reported by the client-side reporter."""
name: str = Field(..., min_length=1, max_length=255)
domain: str = Field(..., min_length=1, max_length=255)
storage_type: str = Field(default="cookie", max_length=30)
value_length: int = Field(default=0, ge=0)
path: str | None = None
is_secure: bool | None = None
same_site: str | None = None
script_source: str | None = None
class CookieReportRequest(BaseModel):
"""Payload from the client-side cookie reporter."""
site_id: uuid.UUID
page_url: str = Field(..., max_length=2000)
cookies: list[ReportedCookie] = Field(..., max_length=500)
collected_at: datetime
user_agent: str = Field(default="", max_length=500)
class CookieReportResponse(BaseModel):
"""Acknowledgement response for a cookie report."""
accepted: bool = True
cookies_received: int
new_cookies: int = 0
# ── Scan job schemas ─────────────────────────────────────────────────
class ScanResultResponse(BaseModel):
"""A single scan result — a cookie found on a specific page."""
id: uuid.UUID
scan_job_id: uuid.UUID
page_url: str
cookie_name: str
cookie_domain: str
storage_type: str
attributes: dict | None = None
script_source: str | None = None
auto_category: str | None = None
initiator_chain: list[str] | None = None
found_at: datetime
created_at: datetime
model_config = {"from_attributes": True}
class ScanJobResponse(BaseModel):
"""Response schema for a scan job."""
id: uuid.UUID
site_id: uuid.UUID
status: str
trigger: str
pages_scanned: int
pages_total: int | None
cookies_found: int
error_message: str | None
started_at: datetime | None
completed_at: datetime | None
created_at: datetime
updated_at: datetime
model_config = {"from_attributes": True}
class ScanJobDetailResponse(ScanJobResponse):
"""Scan job with results included."""
results: list[ScanResultResponse] = []
class TriggerScanRequest(BaseModel):
"""Request to trigger a new scan."""
site_id: uuid.UUID
max_pages: int = Field(default=50, ge=1, le=500)
# ── Diff engine schemas ──────────────────────────────────────────────
class DiffStatus(StrEnum):
NEW = "new"
REMOVED = "removed"
CHANGED = "changed"
class CookieDiffItem(BaseModel):
"""A single cookie difference between two scans."""
name: str
domain: str
storage_type: str
diff_status: DiffStatus
details: str | None = None
class ScanDiffResponse(BaseModel):
"""Diff between two scans."""
current_scan_id: uuid.UUID
previous_scan_id: uuid.UUID | None
new_cookies: list[CookieDiffItem] = []
removed_cookies: list[CookieDiffItem] = []
changed_cookies: list[CookieDiffItem] = []
total_new: int = 0
total_removed: int = 0
total_changed: int = 0