Implement LinuxDO OAuth authorization-code login flow with state validation and local account binding, and add admin APIs/UI to manage OAuth runtime settings with URL validation and secret masking. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
107 lines
3.6 KiB
Python
107 lines
3.6 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
BASE_DIR = Path(__file__).resolve().parent
|
|
SETTINGS_FILE = BASE_DIR / "settings.json"
|
|
|
|
DEFAULT_SETTINGS: dict[str, Any] = {
|
|
"secret_key": "echo-tavern-dev-secret",
|
|
"db_path": "echo_tavern.db",
|
|
"deepseek_api_key": "sk-9e94feccd21640ada969990e792cbc11",
|
|
"deepseek_base_url": "https://api.deepseek.com",
|
|
"deepseek_model": "deepseek-chat",
|
|
"deepseek_timeout": 25,
|
|
"deepseek_connect_timeout": 8,
|
|
"deepseek_read_timeout": 90,
|
|
"deepseek_retries": 2,
|
|
"max_room_players": 8,
|
|
"max_action_length": 280,
|
|
"app_host": "0.0.0.0",
|
|
"app_port": 8095,
|
|
"debug": True,
|
|
"linuxdo_oauth": {
|
|
"enabled": False,
|
|
"client_id": "",
|
|
"client_secret": "",
|
|
"authorize_url": "https://connect.linux.do/oauth2/authorize",
|
|
"token_url": "https://connect.linux.do/oauth2/token",
|
|
"userinfo_url": "https://connect.linux.do/api/user",
|
|
"scope": "read",
|
|
},
|
|
}
|
|
|
|
|
|
def _resolve_db_path(db_path: str) -> str:
|
|
path = Path(db_path)
|
|
if path.is_absolute():
|
|
return str(path)
|
|
return str((BASE_DIR / path).resolve())
|
|
|
|
|
|
def _load_settings() -> dict[str, Any]:
|
|
if not SETTINGS_FILE.exists():
|
|
SETTINGS_FILE.write_text(
|
|
json.dumps(DEFAULT_SETTINGS, ensure_ascii=False, indent=2),
|
|
encoding="utf-8",
|
|
)
|
|
return dict(DEFAULT_SETTINGS)
|
|
|
|
try:
|
|
payload = json.loads(SETTINGS_FILE.read_text(encoding="utf-8"))
|
|
if not isinstance(payload, dict):
|
|
return dict(DEFAULT_SETTINGS)
|
|
except (OSError, json.JSONDecodeError):
|
|
return dict(DEFAULT_SETTINGS)
|
|
|
|
merged = dict(DEFAULT_SETTINGS)
|
|
merged.update(payload)
|
|
linuxdo_defaults = DEFAULT_SETTINGS["linuxdo_oauth"]
|
|
linuxdo_payload = payload.get("linuxdo_oauth", {})
|
|
if isinstance(linuxdo_payload, dict):
|
|
merged["linuxdo_oauth"] = {**linuxdo_defaults, **linuxdo_payload}
|
|
else:
|
|
merged["linuxdo_oauth"] = dict(linuxdo_defaults)
|
|
return merged
|
|
|
|
|
|
SETTINGS = _load_settings()
|
|
|
|
|
|
class Config:
|
|
SECRET_KEY = str(SETTINGS.get("secret_key", DEFAULT_SETTINGS["secret_key"]))
|
|
DB_PATH = _resolve_db_path(str(SETTINGS.get("db_path", DEFAULT_SETTINGS["db_path"])))
|
|
DEEPSEEK_API_KEY = str(SETTINGS.get("deepseek_api_key", DEFAULT_SETTINGS["deepseek_api_key"]))
|
|
DEEPSEEK_BASE_URL = str(
|
|
SETTINGS.get("deepseek_base_url", DEFAULT_SETTINGS["deepseek_base_url"])
|
|
)
|
|
DEEPSEEK_MODEL = str(SETTINGS.get("deepseek_model", DEFAULT_SETTINGS["deepseek_model"]))
|
|
DEEPSEEK_TIMEOUT = float(
|
|
SETTINGS.get("deepseek_timeout", DEFAULT_SETTINGS["deepseek_timeout"])
|
|
)
|
|
DEEPSEEK_CONNECT_TIMEOUT = float(
|
|
SETTINGS.get("deepseek_connect_timeout", DEFAULT_SETTINGS["deepseek_connect_timeout"])
|
|
)
|
|
DEEPSEEK_READ_TIMEOUT = float(
|
|
SETTINGS.get("deepseek_read_timeout", DEFAULT_SETTINGS["deepseek_read_timeout"])
|
|
)
|
|
DEEPSEEK_RETRIES = int(SETTINGS.get("deepseek_retries", DEFAULT_SETTINGS["deepseek_retries"]))
|
|
MAX_ROOM_PLAYERS = int(
|
|
SETTINGS.get("max_room_players", DEFAULT_SETTINGS["max_room_players"])
|
|
)
|
|
MAX_ACTION_LENGTH = int(
|
|
SETTINGS.get("max_action_length", DEFAULT_SETTINGS["max_action_length"])
|
|
)
|
|
APP_HOST = str(SETTINGS.get("app_host", DEFAULT_SETTINGS["app_host"]))
|
|
APP_PORT = int(SETTINGS.get("app_port", DEFAULT_SETTINGS["app_port"]))
|
|
DEBUG = bool(SETTINGS.get("debug", DEFAULT_SETTINGS["debug"]))
|
|
LINUXDO_OAUTH = SETTINGS.get("linuxdo_oauth", DEFAULT_SETTINGS["linuxdo_oauth"])
|
|
|
|
|
|
class TestConfig(Config):
|
|
TESTING = True
|
|
SECRET_KEY = "test-secret"
|
|
DEEPSEEK_API_KEY = ""
|