diff --git a/app.py b/app.py index dd16c1a..beb9739 100644 --- a/app.py +++ b/app.py @@ -61,16 +61,14 @@ class Config: log_level = logging.DEBUG if os.getenv("DEBUG", "false").lower() == "true" else logging.INFO -logging.basicConfig( - level=log_level, - format="%(message)s" -) +logging.basicConfig(level=log_level, format="%(message)s") logging.getLogger("werkzeug").setLevel(logging.ERROR) logging.getLogger("urllib3").setLevel(logging.WARNING) logging.getLogger("gunicorn.access").setLevel(logging.WARNING) logger = logging.getLogger("matrix-interceptor") +logger.propagate = False def now_iso(): return datetime.now(timezone.utc).isoformat() @@ -80,10 +78,6 @@ def log_event(event: str, **kwargs): details = " ".join(f"{k}={v}" for k, v in kwargs.items()) logger.info(f"{base} {details}") -def debug_log(title, data): - if config.debug: - logger.debug(f"{title}: {json.dumps(data, default=str)}") - # ============================================================ # INIT # ============================================================ @@ -102,7 +96,9 @@ if missing: KNOWN_EXTERNAL_USERS = {} RATE_LIMIT = defaultdict(list) METRICS = defaultdict(int) + METRICS_LOCK = Lock() +CACHE_LOCK = Lock() CACHE_FILE = "/app/cache/known_users.json" CACHE_DIRTY = False @@ -114,19 +110,19 @@ CACHE_DIRTY = False def load_cache(): global KNOWN_EXTERNAL_USERS + os.makedirs(os.path.dirname(CACHE_FILE), exist_ok=True) + if os.path.exists(CACHE_FILE): try: with open(CACHE_FILE, "r") as f: KNOWN_EXTERNAL_USERS = json.load(f) logger.info(f"Loaded cache with {len(KNOWN_EXTERNAL_USERS)} users") - except Exception as e: - logger.error(f"Cache load failed: {e}") + except Exception: KNOWN_EXTERNAL_USERS = {} else: - os.makedirs(os.path.dirname(CACHE_FILE), exist_ok=True) with open(CACHE_FILE, "w") as f: json.dump({}, f) - logger.info("Initialized empty cache") + logger.info("Initialized empty cache file") def save_cache(): global CACHE_DIRTY @@ -135,10 +131,11 @@ def save_cache(): return try: - os.makedirs(os.path.dirname(CACHE_FILE), exist_ok=True) - with open(CACHE_FILE, "w") as f: - json.dump(KNOWN_EXTERNAL_USERS, f) - CACHE_DIRTY = False + with CACHE_LOCK: + os.makedirs(os.path.dirname(CACHE_FILE), exist_ok=True) + with open(CACHE_FILE, "w") as f: + json.dump(KNOWN_EXTERNAL_USERS, f) + CACHE_DIRTY = False except Exception as e: logger.error(f"Failed to save cache: {e}") @@ -167,6 +164,8 @@ def is_known_user(user_id): if time.time() - ts > config.cache_ttl: del KNOWN_EXTERNAL_USERS[user_id] + global CACHE_DIRTY + CACHE_DIRTY = True return False return True @@ -175,7 +174,7 @@ def remember_user(user_id): global CACHE_DIRTY KNOWN_EXTERNAL_USERS[user_id] = time.time() CACHE_DIRTY = True - save_cache() # 🔥 critical safety write + save_cache() def is_local_room(room_id): try: @@ -187,20 +186,19 @@ def get_role(user_id): return "admin" if user_id in config.admin_users else "user" # ============================================================ -# RATE LIMIT +# RATE LIMIT (FIXED) # ============================================================ -def is_rate_limited(domain): +def is_rate_limited(domain, sender): + key = f"{domain}:{sender}" now = time.time() - RATE_LIMIT[domain] = [ - t for t in RATE_LIMIT[domain] if now - t < 60 - ] + RATE_LIMIT[key] = [t for t in RATE_LIMIT[key] if now - t < 60] - if len(RATE_LIMIT[domain]) >= config.rate_limit_per_minute: + if len(RATE_LIMIT[key]) >= config.rate_limit_per_minute: return True - RATE_LIMIT[domain].append(now) + RATE_LIMIT[key].append(now) return False # ============================================================ @@ -393,11 +391,13 @@ def invite(room_id, event_id): domain = extract_domain(sender) - if is_rate_limited(domain): + if is_rate_limited(domain, sender): return Response(status=429) if domain in config.domain_whitelist: remember_user(sender) + with METRICS_LOCK: + METRICS["invite_allowed"] += 1 return forward_request( "PUT", f"{config.tuwunel_url}/_matrix/federation/v2/invite/{room_id}/{event_id}", @@ -413,6 +413,8 @@ def invite(room_id, event_id): if is_user_in_local_rooms(sender): remember_user(sender) + with METRICS_LOCK: + METRICS["invite_allowed"] += 1 return forward_request( "PUT", f"{config.tuwunel_url}/_matrix/federation/v2/invite/{room_id}/{event_id}",