mirror of
https://github.com/ZhuLinsen/daily_stock_analysis.git
synced 2026-10-07 16:18:49 +08:00
* fix(#1970): 关闭认证强制要求当前管理员密码二次确认
后端 api/v1/endpoints/auth.py 的 auth_update_settings 在 disable 路径上即使携带有效 session cookie 也强制要求 current_admin_password,否则返回 400;密码错误统一返回 401,命中 rate limit 与 enable 路径一致返回 429。enable 与 initial setup 路径行为保持不变。
前端 AuthSettingsCard 在关闭认证场景下若 currentPassword 缺失,submit 按钮保持可点击(disabled 仅由 isDirty 决定),handleSubmit 校验后给出内联错误,避免用户面对一个长期 disabled 的按钮但不知所缺。新增 i18n key settings.authDisableRequiredCurrentPassword 中英文本,同步修订 authHelperTurnOff / authPasswordHintOff 文案以反映新契约,并避免 hint 文案与 inline error 文案完全重复导致测试 findByText 多匹配。
测试覆盖:
- tests/test_auth_api.py 新增 disable 路径在有/无 session、有/无 current_password、密码对错、rate limit 命中 6 种分支用例,全部通过。
- apps/dsa-web/__tests__/AuthSettingsCard.test.tsx 把原 'missing current password when session valid' 反向为 'blocks disabling when current password missing',并补 'disables auth with current password provided'。本机 vitest run AuthSettingsCard.test.tsx 6/6 通过。
issue #1970
* test(#1970): 关闭认证回归补真实 ASGI 端到端用例并清理无效 mock
针对 PR #2050 review 反馈,补齐两条回归用例并修正既有用例的误导性 mock:
1. 删除 AuthApiTestCase 三个 valid-session 用例中对 verify_session 的 patch。
Disable 分支不会调用 verify_session(仅在 enable 分支的 TOCTOU 复检里用到),
旧 mock 既不生效也容易让读者误以为 disable 路径会做 session 校验。
2. 新增 AuthDisableViaRealASGITestCase 通过真实 ASGI / AuthMiddleware / auth
路由组合链路(create_app + httpx.ASGITransport,与 test_api_health.py 同路径)
验证 Issue #1970 修复:
- 真实 POST /api/v1/auth/login 拿到签名 cookie 后,仅带 session 不带
currentPassword 调 /api/v1/auth/settings 关闭认证 -> 400 current_required;
- 同上下文携带正确 currentPassword 关闭认证 -> 200,.env 翻转至
ADMIN_AUTH_ENABLED=false,响应头携带 Set-Cookie 轮换 session secret。
3. 同步 /api/v1/auth/settings OpenAPI description:明确「关闭认证时
currentPassword 必填、有效 session 不足够」这一新契约,与 endpoint
行为及 Issue #1970 上下文对齐。
测试:tests/test_auth_api.py 35/35 全过。
* test(auth): fix rate-limit trigger test — needs MAX+1 iterations to reach 429
Previously range(RATE_LIMIT_MAX_FAILURES) ran only 5 iterations, but
check_rate_limit returns False only when count >= MAX. This means the
5th request enters with count=4 (4 < 5), passes check_rate_limit,
runs verify_stored_password + record_login_failure, and returns 401.
Only a 6th request - entering with count=5 (5 >= 5) - is rejected
early by check_rate_limit and returns 429.
Fix the loop range to RATE_LIMIT_MAX_FAILURES + 1 and update the
assertion: the first MAX attempts return 401 (each recording a
failure), and the final attempt returns 429. Also normalise a
mixed Chinese/English docstring to English-only. 35 tests pass.
* test: tighten ASGI auth disable coverage
- Drop the middleware-is_auth_enabled patch now that the endpoint
disables auth by rotating the session secret in a single in-memory
transition. Subsequent middleware checks in the same client see the
disabled state via the auth module, so the patch was masking a
state-leak instead of testing the real path.
- Make the positive disable test assert cookie deletion semantics
(empty value + Max-Age=0/Expires-past + jar cleared) instead of just
a presence check on Set-Cookie. A leaked pre-disable cookie must not
remain usable after disable, and the previous assertion would still
pass if the endpoint rotated to a fresh session id.
* test(auth): strip surrounding quotes when asserting empty dsa_session value
Starlette's delete_cookie serializes the empty cookie value as
dsa_session=""; Max-Age=0; ... — i.e. with surrounding double quotes.
The previous assertion expected the raw value to equal '' and so
failed on CI (which uses Starlette's stock serializer); locally the
TestClient happened to round-trip the same way but the strip happened
to make the assertion spuriously true (or the assertion was correct
against an older Starlette). Strip surrounding double quotes before
comparison so the test matches the actual deletion-form emitted by
delete_cookie.
* docs(changelog): move #1970 entry to [Unreleased] + drop 5 stray 3.28.0 文档段 entries
OR-COR-3defa936 blocker fix: 上轮 commit (`5219a43b`) 把 6 条 bullet 错误地追加到了已发布的 `## [3.28.0] - 2026-07-26` -> `### 文档` 段落,而不是 `[Unreleased]`。其中:
- 1 条属于本 PR (#1970) — 应放进 [Unreleased]
- 5 条属于其他 PR / issue (#2026 / #1985 / #2051 / Windows mimetypes / TUSHARE_HTTP_URL 工作流映射),且这些条目在 `3.28.0` 的 `### 新功能` / `### 改进` / `### 修复` 段已经发布过,再放进 `### 文档` 段属于重复 + 跨段误归类。
修复:
- 删除 `3.28.0 -> ### 文档` 段下的 6 条新增 bullet,恢复该段原本只剩「修复文档中的失效相对链接。」的 upstream/main 原貌;
- 在 `[Unreleased]` 段追加本 PR 的 #1970 单条扁平条目(与仓库约定一致:本 PR 自身只追加自身条目,不替其他 PR 处理)。
合并后 `docs/CHANGELOG.md` 的 `3.28.0 -> ### 文档` 段恢复 1 条原貌;`[Unreleased]` 段只新增 1 条本 PR 的条目,不再污染已发布版本历史。
* chore: trigger CI re-run after changelog fix (e01e0cf7 didn't fire pull_request event)
---------
Co-authored-by: xxiaoxiong <xxiaoxiong@users.noreply.github.com>