mirror of
https://github.com/jhd3197/ServerKit.git
synced 2026-11-10 05:53:05 +09:00
refactor(agents): raise typed errors from the report endpoint
Keeps the hand-shaped error-body ratchet at its ceiling. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
a0662254ae
commit
0aab404e35
3 files changed
+17
-8
No files matched your search
@@ -13,6 +13,9 @@ Auth (no JWT/CSRF; the HMAC is the credential):
|
||||
from flask import Blueprint, jsonify, request
|
||||
|
||||
from app import limiter
|
||||
from app.exceptions import (
|
||||
AuthenticationError, PayloadTooLargeError, PermissionDeniedError, ValidationError,
|
||||
)
|
||||
from app.services import agent_health_service, permission_levels
|
||||
from app.utils.ip_utils import is_ip_allowed
|
||||
|
||||
@@ -31,23 +34,23 @@ def _rate_key():
|
||||
@limiter.limit("30 per minute", key_func=_rate_key)
|
||||
def report():
|
||||
if (request.content_length or 0) > MAX_BODY_BYTES:
|
||||
return jsonify({'error': 'payload too large'}), 413
|
||||
raise PayloadTooLargeError()
|
||||
raw = request.get_data(cache=True)
|
||||
if len(raw) > MAX_BODY_BYTES:
|
||||
return jsonify({'error': 'payload too large'}), 413
|
||||
raise PayloadTooLargeError()
|
||||
|
||||
server = agent_health_service.authenticate_report(raw, request.headers, MAX_SKEW_SECONDS)
|
||||
if not server:
|
||||
return jsonify({'error': 'authentication failed'}), 401
|
||||
raise AuthenticationError('authentication failed')
|
||||
if server.allowed_ips and not is_ip_allowed(request.remote_addr, server.allowed_ips):
|
||||
return jsonify({'error': 'IP not allowed'}), 403
|
||||
raise PermissionDeniedError('IP not allowed')
|
||||
|
||||
body = request.get_json(silent=True)
|
||||
if not isinstance(body, dict):
|
||||
return jsonify({'error': 'JSON object required'}), 400
|
||||
raise ValidationError('JSON object required')
|
||||
level = body.get('permission_level')
|
||||
if level not in permission_levels.LEVELS:
|
||||
return jsonify({'error': 'permission_level must be monitor, protect or full'}), 400
|
||||
raise ValidationError('permission_level must be monitor, protect or full')
|
||||
|
||||
interval = agent_health_service.ingest_report(server, level, body)
|
||||
return jsonify({'ok': True, 'interval_seconds': interval})
|
||||
@@ -57,6 +57,12 @@ class PermissionDeniedError(ApplicationError, PermissionError):
|
||||
default_message = 'Access denied'
|
||||
|
||||
|
||||
class PayloadTooLargeError(ApplicationError):
|
||||
status_code = 413
|
||||
code = 'payload_too_large'
|
||||
default_message = 'Payload too large'
|
||||
|
||||
|
||||
class NotFoundError(ApplicationError, LookupError):
|
||||
status_code = 404
|
||||
code = 'not_found'
|
||||
|
||||
@@ -16,12 +16,12 @@ debt. Regenerate with `python scripts/generate-migration-inventory.py`.
|
||||
| Routes on bare @jwt_required() | auth_required() / role decorators | 598 | 598 | REGISTERED EXCEPTION: JWT-only is the deliberate default; conversion grants API-key access and happens per route, on decision |
|
||||
| HTTP statuses chosen by sniffing error text | typed errors from app.exceptions | 0 | 0 | INVARIANT at 0 - migration completed 2026-08-19 |
|
||||
| API crashes swallowed without recording | app.error_reporting | 0 | 0 | INVARIANT at 0 |
|
||||
| Hand-shaped {'error': ...} bodies in app/api | typed errors + the global handler | 855 | 850 | migrate when touched; new endpoints raise |
|
||||
| Hand-shaped {'error': ...} bodies in app/api | typed errors + the global handler | 849 | 850 | migrate when touched; new endpoints raise |
|
||||
| Raw subprocess calls outside the runners | app/utils/system.py runners | 23 | 23 | migrate when touched |
|
||||
| Controller-boundary violations (routes doing service work) | service layer extraction | 453 | 453 | migrate when touched (first-wave ratchet) |
|
||||
| raw api.* calls in pages/ | E1: useServerQuery/useServerMutation | 374 | 398 | migrate when touched |
|
||||
| per-page toast.error extractions in pages/ | E1: query-layer error presentation | 191 | 206 | migrate when touched |
|
||||
| hand-rolled form-group blocks | F2: FormField/useForm | 314 | 321 | migrate when touched |
|
||||
| hand-rolled form-group blocks | F2: FormField/useForm | 315 | 321 | migrate when touched |
|
||||
| unencoded ?k=${v} query interpolations in services/api | C4: buildQuery/encoding template | 0 | 0 | INVARIANT at 0 |
|
||||
| raw setInterval pollers | E2: usePolling/refetchInterval | 7 | 7 | DELIBERATE RESIDUE: clock ticks, socket-fallback hooks, and sibling-repo extension timers - each listed per file |
|
||||
| direct navigator.clipboard call sites | F3: copyToClipboard | 0 | 0 | INVARIANT at 0 |
|
||||
|
||||
Reference in new issue
Block a user