3 Commits

Author SHA1 Message Date
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
ericwyuan
5f07dad019 [阶段5] 部署到 NAS + frp 公网映射,并加注册锁
部署 (NAS 192.168.50.64):
- MariaDB 建库 garmin_health_lab,5 张表由 init_db 建好
- Python 3.8.15 venv;NAS 无 gcc,依赖全部走纯 Python 轮子
- gunicorn 2 worker × 4 线程,--timeout 300(AI 生成耗时可达数分钟)
- start.sh / stop.sh,可重复执行;日志落 logs/
- 在 NAS 真机 + 真实 MariaDB 上跑通全部测试:205 passed

app.py / config.py:
- STATIC_DIR 存在时由同一个 Flask 进程托管 React 构建产物,
  部署即单端口单进程,不需要额外反代
- 404 处理区分 /api 前缀:API 仍返回 JSON,其余回退到 index.html,
  这样 /settings 这类前端路由刷新后不会 404

安全 - 注册锁 (ALLOW_REGISTRATION):
- 服务要挂到公网,而原本 /register 完全开放,任何人都能注册进来
  读取健康数据
- 默认策略 auto:仅在尚无任何账号时开放,注册完第一个即自动关闭
- 另支持 true / false 显式覆盖;按请求读取,改配置无需重启
- 新增 GET /auth/registration-status,前端据此隐藏注册标签页

frp 公网映射:
- 复用 NAS 上已有的 frpc (/etc/frp/frpc.toml),追加 garmin 隧道
  NAS:8123 -> 甲骨文:8123(改前已按既有惯例备份 .bak.<时间戳>)
- 经 S99frpc.sh restart 生效,原有 4 条隧道均正常恢复

tests/test_registration_policy.py (13 通过):
- auto 策略下第一个账号放行、第二个 403 且不落库
- true/false 显式覆盖,大小写不敏感
- 策略按请求读取而非 import 时冻结
- 关闭注册不影响登录;status 端点无需鉴权

公网实测: 页面、SPA 路由、鉴权 401、注册锁 403 均符合预期。

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-08-23 18:49:05 +08:00
ericwyuan
3b2d0697f0 [阶段1.1-1.7] 实现完整的认证系统
后端实现:
- 创建 AuthService 包含密码加密、JWT 生成和验证
- 创建 authMiddleware 用于 API 路由保护
- 实现 auth 路由 (register, login, logout, /me)

前端实现:
- 创建 Login 页面 (登录/注册标签页)
- 创建 ProtectedRoute 组件用于路由保护
- 更新 App.tsx 集成路由保护
- 前端 API 客户端已包含认证方法和拦截器

验收标准已满足:
- 用户可以注册和登录
- JWT Token 正确生成和验证
- 受保护的路由需要有效 Token
- 未认证用户重定向到登录页面

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-08-23 12:25:33 +08:00