fix: correct audit event names, attachment deletion, redirect safety, and add account deactivation check

- Fix audit event names in CLI prune/clear commands from `cli.seo.*` to `cli.seo_meta.*`
- Refactor `_delete_attachment_file` to accept full attachment dict instead of storage_path string, using directory and stored_name fields with ATTACHMENTS_DIR
- Add `safe_next` validation for referer header in validation error redirect and media redirect
- Add `is_active` check in login router to reject deactivated accounts with "Account is deactivated" error
- Replace raw `request.headers.get("Referer")` with `redirect_back()` utility in bookmarks, polls, reactions, and votes routers
- Move `mark_conversation_read` call from `get_conversation_messages` to `messages_page` to avoid side effects during message retrieval
- Fix poll audit link to use `option.get("label")` instead of `option.get("text")`
- Add `VOTABLE` set validation in votes router to reject invalid target types with 400 response
- Strip control characters (0x00-0x20) from URLs in `_safe_url` instead of simple strip
- Add `__getattr__` fallback in services `__init__.py` for dynamic attribute access
This commit is contained in:
2026-06-21 16:46:27 +00:00
parent a3dd747d07
commit 19cc85f409
31 changed files with 202 additions and 344 deletions
+7 -1
View File
@@ -126,6 +126,9 @@ class TelegramBridge:
def _chat_lock(self, chat_id: int) -> asyncio.Lock:
lock = self._chat_locks.get(chat_id)
if lock is None:
if len(self._chat_locks) > 1024:
for key in [k for k, v in self._chat_locks.items() if not v.locked()]:
del self._chat_locks[key]
lock = asyncio.Lock()
self._chat_locks[chat_id] = lock
return lock
@@ -186,7 +189,10 @@ class TelegramBridge:
def _too_many_attempts(self, chat_id: int) -> bool:
now = time.monotonic()
recent = [t for t in self._attempts.get(chat_id, []) if now - t < ATTEMPT_WINDOW_SECONDS]
self._attempts[chat_id] = recent
if recent:
self._attempts[chat_id] = recent
else:
self._attempts.pop(chat_id, None)
return len(recent) >= ATTEMPT_LIMIT
def _register_attempt(self, chat_id: int) -> None: