harden deploy reports and admin alerts
This commit is contained in:
@@ -41,7 +41,13 @@ async def check_rate_limit(
|
||||
if settings.redis_url:
|
||||
allowed = await check_redis_rate_limit(scope, identifiers, limit, window_seconds)
|
||||
if not allowed:
|
||||
await log_rate_limit_event(session, scope=scope, identifier="redis")
|
||||
await log_rate_limit_event(
|
||||
session,
|
||||
scope=scope,
|
||||
identifier="redis",
|
||||
user=user,
|
||||
request=request,
|
||||
)
|
||||
raise_rate_limit(scope, window_seconds)
|
||||
return
|
||||
|
||||
@@ -52,7 +58,13 @@ async def check_rate_limit(
|
||||
while bucket and now - bucket[0] > window_seconds:
|
||||
bucket.popleft()
|
||||
if len(bucket) >= limit:
|
||||
await log_rate_limit_event(session, scope=scope, identifier=str(identifier))
|
||||
await log_rate_limit_event(
|
||||
session,
|
||||
scope=scope,
|
||||
identifier=str(identifier),
|
||||
user=user,
|
||||
request=request,
|
||||
)
|
||||
raise_rate_limit(scope, window_seconds)
|
||||
for identifier in identifiers:
|
||||
_buckets[(scope, identifier)].append(now)
|
||||
@@ -107,18 +119,82 @@ async def log_rate_limit_event(
|
||||
*,
|
||||
scope: str,
|
||||
identifier: str,
|
||||
user: User | None = None,
|
||||
request: Request | None = None,
|
||||
) -> None:
|
||||
if session is None:
|
||||
return
|
||||
from app.models.car import AuditLog
|
||||
client_host = request.client.host if request and request.client else None
|
||||
user_agent = request.headers.get("user-agent") if request else None
|
||||
metadata = {
|
||||
"scope": scope,
|
||||
"identifier": identifier,
|
||||
"telegram_id": user.telegram_id if user else None,
|
||||
"user_id": user.id if user else None,
|
||||
"ip": client_host,
|
||||
}
|
||||
|
||||
session.add(
|
||||
AuditLog(
|
||||
actor_user_id=None,
|
||||
actor_role="system",
|
||||
action="rate_limit.exceeded",
|
||||
target_type=scope,
|
||||
target_id=identifier[:80],
|
||||
metadata_json={"scope": scope, "identifier": identifier},
|
||||
)
|
||||
if session is None:
|
||||
from app.db.session import async_session_factory
|
||||
|
||||
async with async_session_factory() as event_session:
|
||||
await persist_rate_limit_event(
|
||||
event_session,
|
||||
scope=scope,
|
||||
identifier=identifier,
|
||||
user=user,
|
||||
client_host=client_host,
|
||||
user_agent=user_agent,
|
||||
metadata=metadata,
|
||||
)
|
||||
return
|
||||
|
||||
await persist_rate_limit_event(
|
||||
session,
|
||||
scope=scope,
|
||||
identifier=identifier,
|
||||
user=user,
|
||||
client_host=client_host,
|
||||
user_agent=user_agent,
|
||||
metadata=metadata,
|
||||
)
|
||||
|
||||
|
||||
async def persist_rate_limit_event(
|
||||
event_session: AsyncSession,
|
||||
*,
|
||||
scope: str,
|
||||
identifier: str,
|
||||
user: User | None,
|
||||
client_host: str | None,
|
||||
user_agent: str | None,
|
||||
metadata: dict,
|
||||
) -> None:
|
||||
from app.models.car import AuditLog
|
||||
from app.services.admin_notifications import create_admin_notification
|
||||
|
||||
try:
|
||||
event_session.add(
|
||||
AuditLog(
|
||||
actor_user_id=user.id if user else None,
|
||||
actor_role=user.platform_role if user else "system",
|
||||
action="rate_limit.exceeded",
|
||||
target_type=scope,
|
||||
target_id=identifier[:80],
|
||||
metadata_json=metadata,
|
||||
ip=client_host,
|
||||
user_agent=user_agent[:256] if user_agent else None,
|
||||
)
|
||||
)
|
||||
await create_admin_notification(
|
||||
event_session,
|
||||
event_type="rate_limit_exceeded",
|
||||
title="Rate limit exceeded",
|
||||
body=f"Scope: {scope}\nIdentifier: {identifier}",
|
||||
entity_type="user" if user else "system",
|
||||
entity_id=user.id if user else scope,
|
||||
severity="warning",
|
||||
idempotency_key=f"rate_limit:{scope}:{identifier}:{int(time.time() // max(60, 1))}",
|
||||
metadata=metadata,
|
||||
)
|
||||
await event_session.commit()
|
||||
except Exception:
|
||||
await event_session.rollback()
|
||||
|
||||
Reference in New Issue
Block a user