mirror of
https://github.com/AletheiaVox/signal_bridge_remote.git
synced 2026-10-07 03:18:17 +08:00
- Remove the authless "sole connected phone" fallback and SB_REQUIRE_MCP_AUTH: an unauthenticated request no longer reaches whichever phone is online alone. - Mcp-Session-Id is no longer a credential; the Bearer token is checked on every request (MCP auth spec). - Refresh tokens live 90 days (was 30, equal to the access token, so they were always dead when first needed). - A rejected refresh no longer counts toward the IP ban: a client with an expired token was retrying into a self-renewing ban on its own IP. - 401s carry the RFC 9728 WWW-Authenticate discovery header.
59 lines
3.6 KiB
Python
59 lines
3.6 KiB
Python
"""
|
||
Signal Bridge Remote — Server Configuration
|
||
|
||
All settings are loaded from environment variables with sensible defaults.
|
||
In production, set SB_SECRET_KEY to a random 64-char string.
|
||
"""
|
||
import os
|
||
from pathlib import Path
|
||
|
||
from dotenv import load_dotenv
|
||
load_dotenv() # Load .env file before reading any env vars
|
||
|
||
# ── Server ──────────────────────────────────────────────────────────────
|
||
HOST = os.getenv("SB_HOST", "0.0.0.0")
|
||
PORT = int(os.getenv("SB_PORT", "8420"))
|
||
SECRET_KEY = os.getenv("SB_SECRET_KEY", "") # MUST be set in production
|
||
CORS_ORIGINS = os.getenv("SB_CORS_ORIGINS", "*").split(",")
|
||
|
||
# ── Auth ────────────────────────────────────────────────────────────────
|
||
TOKEN_EXPIRY_HOURS = int(os.getenv("SB_TOKEN_EXPIRY_HOURS", "168")) # 1 week
|
||
REGISTRATION_OPEN = os.getenv("SB_REGISTRATION_OPEN", "true").lower() == "true"
|
||
# SB_REQUIRE_MCP_AUTH is gone: MCP auth is always required. An old .env
|
||
# that still sets it is harmless.
|
||
|
||
# ── Rate Limiting ───────────────────────────────────────────────────────
|
||
# Format: "count/period" — e.g. "5/minute", "100/hour"
|
||
RATE_LIMIT_AUTH = os.getenv("SB_RATE_LIMIT_AUTH", "5/minute")
|
||
RATE_LIMIT_COMMANDS = os.getenv("SB_RATE_LIMIT_COMMANDS", "120/minute")
|
||
RATE_LIMIT_GLOBAL = os.getenv("SB_RATE_LIMIT_GLOBAL", "300/minute")
|
||
MAX_WS_PER_IP = int(os.getenv("SB_MAX_WS_PER_IP", "3"))
|
||
BAN_THRESHOLD = int(os.getenv("SB_BAN_THRESHOLD", "20"))
|
||
BAN_DURATION_MINUTES = int(os.getenv("SB_BAN_DURATION_MINUTES", "30"))
|
||
|
||
# ── Safety ──────────────────────────────────────────────────────────────
|
||
HEARTBEAT_INTERVAL_S = float(os.getenv("SB_HEARTBEAT_INTERVAL", "2.0"))
|
||
HEARTBEAT_TIMEOUT_S = float(os.getenv("SB_HEARTBEAT_TIMEOUT", "6.0"))
|
||
|
||
# ── Governor (session intensity limiter) ───────────────────────────────
|
||
# Heat accumulates based on intensity × time, dissipates when idle.
|
||
# Cooldown triggers when heat reaches threshold, exits at the floor.
|
||
GOVERNOR_ENABLED = os.getenv("SB_GOVERNOR_ENABLED", "true").lower() == "true"
|
||
GOVERNOR_HEAT_RATE = float(os.getenv("SB_GOVERNOR_HEAT_RATE", "3.0")) # heat units/sec at intensity=1.0
|
||
GOVERNOR_COOL_RATE = float(os.getenv("SB_GOVERNOR_COOL_RATE", "2.0")) # heat units/sec dissipation when idle
|
||
GOVERNOR_COOLDOWN_THRESHOLD = float(os.getenv("SB_GOVERNOR_COOLDOWN_ENTER", "90.0")) # heat% to trigger cooldown
|
||
GOVERNOR_COOLDOWN_EXIT = float(os.getenv("SB_GOVERNOR_COOLDOWN_EXIT", "30.0")) # heat% to exit cooldown
|
||
GOVERNOR_COOLDOWN_DURATION = float(os.getenv("SB_GOVERNOR_COOLDOWN_DURATION", "30.0")) # min seconds in cooldown
|
||
|
||
# ── Database ────────────────────────────────────────────────────────────
|
||
DB_PATH = os.getenv("SB_DB_PATH", str(Path(__file__).parent / "signal_bridge.db"))
|
||
|
||
|
||
def validate():
|
||
"""Check that critical config is set. Call on startup."""
|
||
if not SECRET_KEY:
|
||
raise RuntimeError(
|
||
"SB_SECRET_KEY is not set. Generate one with: "
|
||
"python -c \"import secrets; print(secrets.token_hex(32))\""
|
||
)
|