Files
ServerKit/backend/app/middleware/api_key_auth.py
T
Juan DenisandClaude Opus 4.8 363a09dc7e feat(security): trust reverse-proxy client IP via one gated seam
Rate-limit buckets, login lockout and audit-log source IPs hand-parsed the
leftmost X-Forwarded-For token — a client-controlled value — letting an
attacker rotate it to dodge per-IP limits or forge audit trails.

- config: add TRUST_PROXY_HEADERS / TRUSTED_PROXY_HOPS (+ _env_bool/_env_int)
- create_app: wrap in gated Werkzeug ProxyFix (rightmost trusted hops) before
  the limiter/handlers so every remote_addr consumer sees the real client
- new app/utils/client_ip.py::get_client_ip() — the single trusted seam
- replace all six leftmost-XFF parsers (rate_limit, audit middleware,
  api_key_auth, api_analytics, audit_service, ddns) with get_client_ip()
- deploy: root .env.example ships TRUST_PROXY_HEADERS=true (behind nginx);
  backend/.env.example documents it off for dev
- tests: test_trusted_client_ip.py (7) incl. real flask-limiter bucket isolation

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-18 00:51:21 -04:00

32 lines
1021 B
Python

"""API Key authentication middleware."""
from flask import g, request, jsonify
def register_api_key_auth(app):
"""Register API key authentication as a before_request handler."""
@app.before_request
def authenticate_api_key():
"""Check for X-API-Key header and validate."""
api_key_header = request.headers.get('X-API-Key')
if not api_key_header:
return # No API key provided, fall through to JWT auth
from app.services.api_key_service import ApiKeyService
api_key = ApiKeyService.validate_key(api_key_header)
if not api_key:
return jsonify({'error': 'Invalid or expired API key'}), 401
# Record usage against the trusted client IP (see app.utils.client_ip).
from app.utils.client_ip import get_client_ip
api_key.record_usage(get_client_ip())
from app import db
db.session.commit()
# Store in g for downstream use
g.api_key = api_key
g.api_key_user = api_key.user