app.bettersight.io/backend/extensions.py

42 lines
1.9 KiB
Python

import os
from flask_cors import CORS
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address
# RATELIMIT_STORAGE_URI lets tests point the limiter at an in-process
# memory:// backend instead of the production Redis instance — Flask-
# Limiter reads its storage_uri/enabled flags once at init_app() time,
# so this must be set via env var before app.py imports this module,
# not by mutating app.config afterwards (see tests/conftest.py).
limiter = Limiter(
key_func=get_remote_address,
storage_uri=os.getenv('RATELIMIT_STORAGE_URI', os.getenv('REDIS_URL', 'redis://bettersight-redis:6379')),
default_limits=['200 per hour', '30 per minute'],
)
# CORS was absent from CLAUDE.md entirely, but the Vue dashboard
# (api_service.js) calls the Flask API directly from the browser via
# Axios — that's a cross-origin request the moment the dashboard and
# API are served from different hosts/ports. Confirmed true in local
# dev (Vite on :5173, Flask on :5050) via VITE_API_BASE_URL; CLAUDE.md
# doesn't pin down the production hostnames, but Pangolin fronting a
# separate flask-api container makes a same-origin setup unlikely
# there either. Without this, every browser-originated API call is
# silently blocked by the browser's CORS preflight check regardless of
# X-API-Key being correct.
#
# ALLOWED_ORIGINS is a comma-separated env var so each environment
# (dev/staging/production) lists only its own dashboard origin(s) —
# CORS is deliberately not wildcarded ("*") since credentials-bearing
# requests (the PocketBase Bearer JWT) require an explicit origin.
ALLOWED_ORIGINS = [
origin.strip()
for origin in os.getenv('ALLOWED_ORIGINS', 'http://localhost:5173,https://app.bettersight.io').split(',')
if origin.strip()
]
cors = CORS(
resources={r'/*': {'origins': ALLOWED_ORIGINS}},
supports_credentials=True,
allow_headers=['Content-Type', 'Authorization', 'X-API-Key', 'X-User-Email'],
)