* 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>
* refactor(web): rebuild shared ui primitives and tokens
* feat(web): add shell, theming, and page layout integration
* feat(auth): expose setup-state auth contract
* feat(web): rebuild login, settings, and auth flows
* test(web): add smoke coverage and ui regressions
* feat(web): add page titles to all pages
Add document.title to all pages for better browser tab identification:
- HomePage: '每日选股分析 - DSA'
- ChatPage: '策略问股 - DSA'
- SettingsPage: '系统设置 - DSA'
- BacktestPage: '策略回测 - DSA'
- PortfolioPage: '持仓分析 - DSA'
- LoginPage: '登录 - DSA'
- NotFoundPage: '页面未找到 - DSA'
All pages use useEffect to set the title on mount.
* feat(web): enhance UI with ongoing adjustments and improvements; update README for user guidance
feat(tests): add unit tests for useSystemConfig hook to ensure stability and functionality
chore(changelog): document major updates including UI refresh, auth workflow overhaul, and test coverage enhancements
* fix: encode non-ascii email sender names (#712)
* fix: encode non-ascii email sender names
* fix: clarify _close_server silent-exception intent and add inline-image sender name test
fixes#708
* docs: add EN doc index, contributing guide, bot guide; bilingual issue/PR templates (#713)
* docs: add EN doc index, contributing guide, bot guide; bilingual issue/PR templates
- Add docs/INDEX_EN.md: full English docs index with China-market glossary
- Add docs/CONTRIBUTING_EN.md: English contributing guide (setup, CI, commit conventions)
- Add docs/bot-command_EN.md: English bot integration guide (commands, webhooks, config)
- Bilingualize .github/ISSUE_TEMPLATE/bug_report.md and feature_request.md
- Update .github/ISSUE_TEMPLATE/config.yml with English Docs Index link
- Bilingualize .github/PULL_REQUEST_TEMPLATE.md checklist and field labels
- Add CONTRIBUTING_EN and INDEX_EN links to docs/README_EN.md nav bar
Refs #711
* docs: fix review feedback on bot-command_EN and CONTRIBUTING_EN
- Correct bot/platforms/ directory tree to match actual files
(feishu_stream.py+discord.py present; feishu.py/wecom.py/telegram.py absent)
- Add missing commands: /ask, /chat, /batch to commands table
- Fix BotCommand.execute() signature: sync def, not async
- Clarify webhook routes as planned/not-yet-registered in FastAPI;
point to bot/handler.py as the actual implementation location
- Fix backend-gate description in CI table to include ./test.sh code
and ./test.sh yfinance steps from ci_gate.sh
* docs: fix format_response signature and webhook route status in bot-command_EN
- format_response: correct signature to (response, message) -> WebhookResponse
to match bot/platforms/base.py abstract method
- Webhook route table: clarify that only dingtalk is in ALL_PLATFORMS (webhook
mode ready); feishu is stream-only; wecom/telegram not yet implemented
- Add concrete example for mounting dingtalk webhook in FastAPI
* docs: fix remaining review feedback in EN docs
- bot-command_EN: stop claiming bot env keys are in .env.example
- bot-command_EN: mount webhook routes in api/app.py instead of api/v1/router.py
- CONTRIBUTING_EN: keep PR CI table limited to actual pull-request checks
- CONTRIBUTING_EN: clarify network-smoke is schedule/workflow_dispatch only
* docs: clarify EN issue links and bot env guidance
* feat(ui): refine settings actions and import conflict recovery
* feat(auth): implement session invalidation on logout and handle errors
---------
Co-authored-by: mumu <42829555+ZhuLinsen@users.noreply.github.com>
* feat: mark model used in reports and notifications (#528)
* refactor: centralize model_used normalization and raw_result parsing (#528)
---------
Co-authored-by: mumu <42829555+ZhuLinsen@users.noreply.github.com>