Commit Graph

6 Commits

Author SHA1 Message Date
Nicholas-Xiong
5edee53550 fix(#1970): 关闭认证强制要求当前管理员密码二次确认 (#2050)
* 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>
2026-08-01 14:33:38 +08:00
mumu
97a3c37760 fix: close logout gap and harden tushare follow-ups (#732)
* fix: close logout gap and harden tushare follow-ups

* test: avoid masking optional deps in tushare follow-up test
2026-03-17 22:17:39 +08:00
LouisHong
f3a8993283 #602 [PR 5 + PR 6] 全局导航与应用外壳 & 系统管理与登录模块 (#714)
* 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>
2026-03-17 21:35:17 +08:00
LouisHong
65034ec16e #602 [PR 3] 认证安全与初始化流程 (Auth & Security) (#639)
* feat(auth): add runtime auth settings endpoint

* feat(auth): robust runtime auth settings and security hardening

- Added '/api/v1/auth/settings' to enable/disable Web authentication at runtime
- Implemented session secret rotation (HMAC invalidation) on any auth toggle
- Fixed TOCTOU race condition in settings update via mandatory session validation
- Added comprehensive integration tests for auth re-enablement and rate limiting
- Improved credential persistence: added 'ENV_FILE' support and atomic 'replace' operations
- Refactored auth initialization flow to prevent password overwrites and ensure rollback on failure

* fix(auth): harden auth settings race checks

* fix(auth): share config writer and propagate rotation failures

* fix(auth): order toggle persistence and document worker scope

* fix(auth): enhance error handling and logging for auth toggle application

---------

Co-authored-by: mumu <42829555+ZhuLinsen@users.noreply.github.com>
2026-03-13 23:15:40 +08:00
Alfred
c85423dfca [feat] 在分析报告中标记使用模型并完善展示链路 Issue #528 (#534)
* 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>
2026-03-07 20:29:02 +08:00
Alfred
32162ff745 feat: Web 页面密码验证 (Issue #320, #349) (#351)
* feat(auth): Web 登录认证与 Settings 保护 (Issue #320, #349)

- 新增 ADMIN_AUTH_ENABLED 开关,启用后需登录才能访问 Web UI 和 API
- 首次访问时在网页设置初始密码,凭证存储于 data/.admin_password_hash
- Settings 页面及 /api/v1/* 在启用认证后需登录访问,保护 API 密钥等敏感配置
- 系统设置中可修改密码,密码输入框支持眼睛图标显示/隐藏
- CLI: python -m src.auth reset_password 可重置密码
- 错误提示统一为中文
- 更新 README、full-guide、CHANGELOG

* fix: 凭证文件权限 + ESLint 修复

- src/auth.py: .session_secret 创建后显式 chmod(0o600)
- Pagination.tsx: PageButton 移至组件外,避免 render 内创建组件
- useTaskStream.ts: connectRef 解决闭包引用;isConnected 改用 useState;disconnect setState 延后执行
- HomePage.tsx: 初始加载 useEffect 添加 exhaustive-deps 注释

* test(auth): add unit and integration tests for auth (Issue #320, #349)

- tests/test_auth.py: password validation, hash verification, session, rate limit, set/change/overwrite password
- tests/test_auth_api.py: login, logout, change-password, API protection (401 without session)
- scripts/ci_gate.sh: add src/auth.py to py_compile

* fix(test): disable auth in system_config_api tests to fix 401

* fix: remove duplicate PageButton declaration in Pagination.tsx

Fixes #320
Fixes #349
2026-02-20 21:47:19 +08:00