feat: IAB TCF 2.2 — TC String encoder + purpose mapping + UI
- TCFEncoderService: generates base64url-encoded TC Strings per IAB spec with 12 purposes, vendor consent bitfield, CMP metadata - Category-to-purpose mapping (necessary→none, statistics→1,7,8,9,10, marketing→1,2,3,4,5,6,7,12, functional→1,11) - tcf_routes: 5 endpoints (purposes, features, mapping, encode, encode-categories) - banner_consent_service: auto-generates TC String when tcf_enabled=true - TCFSettings.tsx: enable/disable toggle, purpose grid with category mapping, TC String test generator, CMP registration info - New "TCF/IAB" tab in cookie-banner page (7 tabs total) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -69,6 +69,7 @@ _ROUTER_MODULES = [
|
||||
"banner_ab_routes",
|
||||
"compliance_report_routes",
|
||||
"whistleblower_routes",
|
||||
"tcf_routes",
|
||||
]
|
||||
|
||||
_loaded_count = 0
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
"""
|
||||
FastAPI routes for IAB TCF 2.2 (Transparency & Consent Framework).
|
||||
|
||||
Endpoints:
|
||||
GET /tcf/purposes — list 12 IAB purposes with translations
|
||||
GET /tcf/special-features — list 2 IAB special features
|
||||
GET /tcf/category-mapping — banner category → IAB purpose mapping
|
||||
POST /tcf/encode — generate TC String from consent decisions
|
||||
POST /tcf/encode-categories — generate TC String from banner categories
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import Optional, List, Dict
|
||||
|
||||
from fastapi import APIRouter, Depends
|
||||
from pydantic import BaseModel
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from classroom_engine.database import get_db
|
||||
from .tenant_utils import get_tenant_id as _get_tenant_id
|
||||
from compliance.services.tcf_encoder_service import TCFEncoderService
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
router = APIRouter(prefix="/tcf", tags=["tcf"])
|
||||
|
||||
|
||||
class TCFEncodeRequest(BaseModel):
|
||||
purpose_consents: Dict[int, bool] = {}
|
||||
vendor_consents: Dict[int, bool] = {}
|
||||
purpose_li: Optional[Dict[int, bool]] = None
|
||||
special_features: Optional[Dict[int, bool]] = None
|
||||
cmp_id: int = 1
|
||||
cmp_version: int = 1
|
||||
consent_language: str = "DE"
|
||||
|
||||
|
||||
class TCFCategoryEncodeRequest(BaseModel):
|
||||
categories: List[str] = []
|
||||
vendor_consents: Optional[Dict[int, bool]] = None
|
||||
cmp_id: int = 1
|
||||
consent_language: str = "DE"
|
||||
|
||||
|
||||
@router.get("/purposes")
|
||||
def list_purposes():
|
||||
return TCFEncoderService.get_purposes()
|
||||
|
||||
|
||||
@router.get("/special-features")
|
||||
def list_special_features():
|
||||
return TCFEncoderService.get_special_features()
|
||||
|
||||
|
||||
@router.get("/category-mapping")
|
||||
def get_category_mapping():
|
||||
return TCFEncoderService.get_category_purpose_map()
|
||||
|
||||
|
||||
@router.post("/encode")
|
||||
def encode_tc_string(body: TCFEncodeRequest):
|
||||
encoder = TCFEncoderService(
|
||||
cmp_id=body.cmp_id,
|
||||
cmp_version=body.cmp_version,
|
||||
consent_language=body.consent_language,
|
||||
)
|
||||
tc_string = encoder.encode(
|
||||
purpose_consents=body.purpose_consents,
|
||||
vendor_consents=body.vendor_consents,
|
||||
purpose_li=body.purpose_li,
|
||||
special_features=body.special_features,
|
||||
)
|
||||
return {"tc_string": tc_string, "version": 2}
|
||||
|
||||
|
||||
@router.post("/encode-categories")
|
||||
def encode_from_categories(body: TCFCategoryEncodeRequest):
|
||||
encoder = TCFEncoderService(
|
||||
cmp_id=body.cmp_id,
|
||||
consent_language=body.consent_language,
|
||||
)
|
||||
tc_string = encoder.encode_from_categories(
|
||||
categories=body.categories,
|
||||
vendor_consents=body.vendor_consents,
|
||||
)
|
||||
# Also return which purposes were set
|
||||
from compliance.services.tcf_encoder_service import CATEGORY_PURPOSE_MAP
|
||||
purpose_ids = set()
|
||||
for cat in body.categories:
|
||||
purpose_ids.update(CATEGORY_PURPOSE_MAP.get(cat, []))
|
||||
return {
|
||||
"tc_string": tc_string,
|
||||
"version": 2,
|
||||
"purposes_consented": sorted(purpose_ids),
|
||||
"categories": body.categories,
|
||||
}
|
||||
Reference in New Issue
Block a user