Files
mono/market/bp/data/routes.py
giles c015f3f02f Security audit: fix IDOR, add rate limiting, HMAC auth, token hashing, XSS sanitization
Critical: Add ownership checks to all order routes (IDOR fix).
High: Redis rate limiting on auth endpoints, HMAC-signed internal
service calls replacing header-presence-only checks, nh3 HTML
sanitization on ghost_sync and product import, internal auth on
market API endpoints, SHA-256 hashed OAuth grant/code tokens.
Medium: SECRET_KEY production guard, AP signature enforcement,
is_admin param removal, cart_sid validation, SSRF protection on
remote actor fetch.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 13:30:27 +00:00

80 lines
2.6 KiB
Python

"""Market app data endpoints.
Exposes read-only JSON queries at ``/internal/data/<query_name>`` for
cross-app callers via the internal data client.
"""
from __future__ import annotations
from quart import Blueprint, g, jsonify, request
from shared.infrastructure.data_client import DATA_HEADER
from shared.contracts.dtos import dto_to_dict
from shared.services.registry import services
def register() -> Blueprint:
bp = Blueprint("data", __name__, url_prefix="/internal/data")
@bp.before_request
async def _require_data_header():
if not request.headers.get(DATA_HEADER):
return jsonify({"error": "forbidden"}), 403
from shared.infrastructure.internal_auth import validate_internal_request
if not validate_internal_request():
return jsonify({"error": "forbidden"}), 403
_handlers: dict[str, object] = {}
@bp.get("/<query_name>")
async def handle_query(query_name: str):
handler = _handlers.get(query_name)
if handler is None:
return jsonify({"error": "unknown query"}), 404
result = await handler()
return jsonify(result)
# --- marketplaces-for-container ---
async def _marketplaces_for_container():
container_type = request.args.get("type", "")
container_id = request.args.get("id", type=int)
markets = await services.market.marketplaces_for_container(
g.s, container_type, container_id,
)
return [dto_to_dict(m) for m in markets]
_handlers["marketplaces-for-container"] = _marketplaces_for_container
# --- products-by-ids ---
async def _products_by_ids():
"""Return product details for a list of IDs (comma-separated)."""
from sqlalchemy import select
from shared.models.market import Product
ids_raw = request.args.get("ids", "")
try:
ids = [int(x) for x in ids_raw.split(",") if x.strip()]
except ValueError:
return {"error": "ids must be comma-separated integers"}, 400
if not ids:
return []
rows = (await g.s.execute(
select(Product).where(Product.id.in_(ids))
)).scalars().all()
return [
{
"id": p.id,
"title": p.title,
"slug": p.slug,
"image": p.image,
"regular_price": str(p.regular_price) if p.regular_price is not None else None,
"special_price": str(p.special_price) if p.special_price is not None else None,
}
for p in rows
]
_handlers["products-by-ids"] = _products_by_ids
return bp