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