Files
GarminHealthLab/backend/.env.example
ericwyuan 9503fca370 feat(auth): 接入 auth-hub 统一登录,网页登录与 Garmin 同步彻底分离
网页身份改由 auth-hub 做 OAuth2 + PKCE 单点登录,本地邮箱/密码登录与注册整条链路删除
(routes/auth.py、auth.py 的密码哈希、config.py 的 ALLOW_REGISTRATION)。Garmin 账号绑定/
同步保持完全独立、可选:routes/garmin.py 不再直接查 users 表,Garmin 邮箱回退统一走新增
的 services/garmin.py::get_remembered_email()(优先读 garmin_tokens 当前绑定,兼容早期账号
落在 users.garmin_email 的历史值),彻底把「你是谁」和「你绑没绑 Garmin」两件事拆开。

- db.py: users 表新增 auth_hub_sub/auth_hub_username,MIGRATIONS 补上这两列(此前遗漏导致
  已存在的生产 MariaDB 表永远不会自动加列);同时把历史遗留的 garmin_email/
  garmin_password_hash NOT NULL 约束在线迁移为可空,因为新账号不再在注册时收集这些字段。
- routes/auth.py: 修掉 /callback 路由重复拼接 /api/auth 前缀导致 404 的 bug。
- client: LoginPage 去掉本地登录/注册标签页,只保留 auth-hub 统一登录;登录成功/失败后都
  用 history.replaceState 清理地址栏,修掉 Framework7 browserHistory 读取
  /auth/callback?code=... 导致「找不到页面」的问题。
- 新增 test_auth_hub_client.py 锁定 find_or_create_user 按 auth_hub_sub 幂等——生产上曾经因为
  这个函数在没有该测试保护时被测试触发,误建过一个空账号,靠手工核对 health_data 计数才发现。
- 生产 auth-hub 侧另行为该项目注册了正式 client(未随本次提交变更,凭证只存在服务器 .env)。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-31 23:12:17 +08:00

80 lines
3.3 KiB
Plaintext

# --- Server ---
# BACKEND_PORT takes precedence over PORT. Prefer it: many tools inject PORT
# for the frontend, and Flask would otherwise take the React dev server's port.
BACKEND_PORT=5000
# --- Database: sqlite (default) or mariadb ---
DB_TYPE=sqlite
# SQLite file (used when DB_TYPE=sqlite)
DATABASE_PATH=./data/health.db
# MariaDB (used when DB_TYPE=mariadb) — production DB on the Oracle server
# (129.146.26.249, local MariaDB 10.3). Dedicated account over TCP 127.0.0.1;
# MARIADB_SOCKET is only needed if TCP auth is disabled for the app user.
# MARIADB_HOST=127.0.0.1
# MARIADB_PORT=3306
# MARIADB_USER=garmin
# MARIADB_PASSWORD=your_production_mariadb_password
# MARIADB_DATABASE=garmin_health_lab
# --- Auth ---
# CHANGE THIS in production! Used to sign JWTs (7-day expiry by default).
JWT_SECRET=dev_secret_change_me
JWT_EXPIRY_DAYS=7
# --- auth-hub OAuth2 provider (centralized SSO) ---
# See docs/AUTH_HUB_INTEGRATION.md for setup instructions.
#
# Base URL of the auth-hub service
AUTH_HUB_BASE_URL=http://129.146.26.249:5300
#
# OAuth2 client credentials (obtain from auth-hub.manage_clients create)
# NOTE: these must be registered against the auth-hub instance AUTH_HUB_BASE_URL
# actually points to (dev vs prod are separate databases with separate clients).
# Put the REAL values in backend/.env (gitignored) — never here.
AUTH_HUB_CLIENT_ID=your_client_id
AUTH_HUB_CLIENT_SECRET=your_client_secret
#
# Callback URL (must exactly match what's registered in auth-hub)
AUTH_HUB_REDIRECT_URI=http://129.146.26.249:8123/auth/callback
# --- CORS (comma-separated allowed front-end origins) ---
# localhost stays in the production list on purpose: CORS is not an auth
# boundary — every data route requires a valid JWT — so allowing a developer's
# dev server costs nothing and saves toggling this on every session.
CORS_ORIGIN=http://localhost:3000,http://localhost:5173
# --- AI models (text-only, large context) ---
# Put REAL keys in backend/.env — that file is gitignored. Never commit keys.
# Any model whose credentials are absent is skipped automatically.
# Self-hosted AI gateway (model id "gateway"). OpenAI-compatible; it fans out
# over nvidia/gemini/ollama itself and rotates several Gemini keys, so it
# absorbs single-vendor quota limits. Reached directly, bypassing any local
# HTTP proxy. NOTE: its NVIDIA upstream is a large reasoning model — replies
# can take 2-3 minutes, so set AI_TIMEOUT_SECONDS accordingly.
AI_GATEWAY_BASE_URL=http://129.146.26.249:5100/v1
AI_GATEWAY_TOKEN=
AI_GATEWAY_MODEL=ai-gateway-auto
# Google AI Studio -> "gemini-flash". Free-tier quota is small; 429s are common.
GEMINI_API_KEY=
# NVIDIA NIM -> "llama-70b", "nemotron-49b", "mistral-large".
# Model ids come from that account's live GET /v1/models — do not guess them.
NVIDIA_API_KEY=
# NVIDIA_BASE_URL=https://integrate.api.nvidia.com/v1
# Preference order. The first configured model answers; if it fails or times
# out, the next is tried. Read per request, so changes need no restart.
AI_MODEL_CHAIN=gateway,gemini-flash,llama-70b
# Max days of history sent (CSV-encoded). Trimmed further per model so the
# payload always fits that model's own context window.
AI_DAY_BUDGET=365
AI_TIMEOUT_SECONDS=180
# Output cap. Reasoning models spend part of it thinking before they answer;
# entries that need more declare their own budget in services/ai.py.
AI_MAX_TOKENS=1024