Update app.py
This commit is contained in:
@@ -93,8 +93,14 @@ if missing:
|
|||||||
|
|
||||||
KNOWN_EXTERNAL_USERS = {}
|
KNOWN_EXTERNAL_USERS = {}
|
||||||
RATE_LIMIT = defaultdict(list)
|
RATE_LIMIT = defaultdict(list)
|
||||||
|
METRICS = defaultdict(int)
|
||||||
|
|
||||||
CACHE_FILE = "/app/cache/known_users.json"
|
CACHE_FILE = "/app/cache/known_users.json"
|
||||||
|
CACHE_DIRTY = False
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# CACHE HANDLING
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
def load_cache():
|
def load_cache():
|
||||||
global KNOWN_EXTERNAL_USERS
|
global KNOWN_EXTERNAL_USERS
|
||||||
@@ -107,13 +113,24 @@ def load_cache():
|
|||||||
KNOWN_EXTERNAL_USERS = {}
|
KNOWN_EXTERNAL_USERS = {}
|
||||||
|
|
||||||
def save_cache():
|
def save_cache():
|
||||||
|
global CACHE_DIRTY
|
||||||
|
|
||||||
|
if not CACHE_DIRTY:
|
||||||
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
os.makedirs(os.path.dirname(CACHE_FILE), exist_ok=True)
|
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(KNOWN_EXTERNAL_USERS, f)
|
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}")
|
||||||
|
|
||||||
|
def periodic_cache_save():
|
||||||
|
while True:
|
||||||
|
save_cache()
|
||||||
|
time.sleep(30)
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# HELPERS
|
# HELPERS
|
||||||
# ============================================================
|
# ============================================================
|
||||||
@@ -134,14 +151,14 @@ 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]
|
||||||
save_cache()
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def remember_user(user_id):
|
def remember_user(user_id):
|
||||||
|
global CACHE_DIRTY
|
||||||
KNOWN_EXTERNAL_USERS[user_id] = time.time()
|
KNOWN_EXTERNAL_USERS[user_id] = time.time()
|
||||||
save_cache()
|
CACHE_DIRTY = True
|
||||||
|
|
||||||
def is_local_room(room_id):
|
def is_local_room(room_id):
|
||||||
try:
|
try:
|
||||||
@@ -153,21 +170,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 (IMPROVED)
|
||||||
# ============================================================
|
# ============================================================
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
@@ -290,7 +305,15 @@ def is_likely_dm_event(event):
|
|||||||
|
|
||||||
@app.route("/healthz")
|
@app.route("/healthz")
|
||||||
def health():
|
def health():
|
||||||
return {"status": "ok"}
|
return {
|
||||||
|
"status": "ok",
|
||||||
|
"known_users": len(KNOWN_EXTERNAL_USERS),
|
||||||
|
"metrics": dict(METRICS)
|
||||||
|
}
|
||||||
|
|
||||||
|
@app.route("/metrics")
|
||||||
|
def metrics():
|
||||||
|
return dict(METRICS)
|
||||||
|
|
||||||
@app.route('/_matrix/client/v3/createRoom', methods=['POST'])
|
@app.route('/_matrix/client/v3/createRoom', methods=['POST'])
|
||||||
def create_room():
|
def create_room():
|
||||||
@@ -309,6 +332,7 @@ def create_room():
|
|||||||
)
|
)
|
||||||
|
|
||||||
if not allowed:
|
if not allowed:
|
||||||
|
METRICS["create_room_blocked"] += 1
|
||||||
log_event(
|
log_event(
|
||||||
"create_room_blocked",
|
"create_room_blocked",
|
||||||
actor=user_id,
|
actor=user_id,
|
||||||
@@ -319,6 +343,7 @@ def create_room():
|
|||||||
return Response(json.dumps({"errcode": "M_FORBIDDEN"}), status=403)
|
return Response(json.dumps({"errcode": "M_FORBIDDEN"}), status=403)
|
||||||
|
|
||||||
if is_dm:
|
if is_dm:
|
||||||
|
METRICS["create_room_allowed"] += 1
|
||||||
log_event(
|
log_event(
|
||||||
"create_room_allowed",
|
"create_room_allowed",
|
||||||
actor=user_id,
|
actor=user_id,
|
||||||
@@ -346,7 +371,7 @@ 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:
|
||||||
@@ -365,6 +390,7 @@ 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)
|
||||||
else:
|
else:
|
||||||
|
METRICS["invite_blocked"] += 1
|
||||||
log_event(
|
log_event(
|
||||||
"invite_blocked",
|
"invite_blocked",
|
||||||
actor=sender,
|
actor=sender,
|
||||||
@@ -374,6 +400,7 @@ def invite(room_id, event_id):
|
|||||||
return Response(status=403)
|
return Response(status=403)
|
||||||
|
|
||||||
remember_user(sender)
|
remember_user(sender)
|
||||||
|
METRICS["invite_allowed"] += 1
|
||||||
|
|
||||||
return forward_request(
|
return forward_request(
|
||||||
"PUT",
|
"PUT",
|
||||||
@@ -421,5 +448,6 @@ if __name__ == '__main__':
|
|||||||
seed_known_users()
|
seed_known_users()
|
||||||
|
|
||||||
threading.Thread(target=periodic_seed, daemon=True).start()
|
threading.Thread(target=periodic_seed, daemon=True).start()
|
||||||
|
threading.Thread(target=periodic_cache_save, daemon=True).start()
|
||||||
|
|
||||||
app.run(host='0.0.0.0', port=5000)
|
app.run(host='0.0.0.0', port=5000)
|
||||||
|
|||||||
Reference in New Issue
Block a user