Commit Graph

18 Commits

Author SHA1 Message Date
Fischer c005642851 refactor: tech debt Wave 3+4 (tools/skills/mcp/rag/calendar/auth/cli/quality/channels/telemetry/session/bus/documents Any 治理) (#11)
Deploy to Production / deploy (push) Waiting to run Details
Test / backend-test (push) Waiting to run Details
Test / frontend-unit (push) Waiting to run Details
Test / api-e2e (push) Waiting to run Details
Test / frontend-e2e (push) Waiting to run Details
2026-07-01 08:08:36 +08:00
chiguyong 2747bb4e64 chore(prior): malformed tool call handling, auth whitelist, dev scripts, wave1 plan 2026-06-29 20:25:03 +08:00
chiguyong 5c15238a5a fix(calendar): 修复 agent 创建日历事件后 UI 不刷新 + 文档化三根因三部曲
Test / backend-test (pull_request) Has been cancelled Details
Test / frontend-unit (pull_request) Has been cancelled Details
Test / api-e2e (pull_request) Has been cancelled Details
Test / frontend-e2e (pull_request) Has been cancelled Details
代码修复 (ce-debug):
- CalendarService.create_event 注入 notify_callback,成功后广播 calendar_event_created WS 消息
- app.py 调整 _calendar_ws_sender 闭包定义顺序,注入 CalendarService(与 ReminderScheduler 共享)
- tauri-auth.ts keychain fallback 修复(localStorage 始终作为备份)
- 新增 2 个广播回归测试

文档 (ce-compound + ce-compound-refresh):
- 新增 docs/solutions/ui-bugs/calendar-agent-create-no-refresh.md(第三根因:WS 广播缺失)
- 更新 calendar-capability-and-ui-fixes.md:刷新 test count + 加 Related Issues 前向引用
- 更新 jwt-secret-dev-mode-user-id-mismatch.md:扩展 e2e bullet + 加第三个根因引用
- CONCEPTS.md 新增 Service Broadcast Callback 条目 (Real-Time Fan-Out 节)

测试:
- 新增 E2E 测试套件 (admin/auth-persistence/bitable/calendar/conversation/documents/evolution/settings/skills)
- 新增 tests/e2e/test_api_coverage.py
- CI: .gitea/.github workflows/test.yml
2026-06-29 02:20:33 +08:00
chiguyong c9ce15fa4b fix(code-review): 修复走查发现的 13 High + Medium 安全/可靠性问题
代码修复(8 High + 9 Medium):
- portal.py — C1 IDOR 文档 / C2 类型修复 / C3 WS 连接上限 16 / C4 ws_user_id 早初始化 / M silent swallow 日志化
- auth/middleware.py — C5 WS sid 补齐
- calendar_tool.py — C6 偏移量 ±43200 双向校验 + reminder_channels 类型/白名单校验
- sqlite_conversation_store.py — C7 DELETE 事务回滚
- chat.ts (Pinia) — C8 deleteConversation 清理 pending 缓存
- app.py — M except: pass → logger.debug(exc_info=True)
- Scene6Error.vue — M onUnmounted 清理 setTimeout
- DocumentsTab.vue — M Invalid Date 守卫
- ChatSidebar/RightPanel/TopNav.vue — M aria-label 无障碍标签
- SystemMonitorPanel.vue — M v-else 兜底 + active 边框色 + tablist 键盘导航
- CalendarDrawer.vue — M overflow-y: auto
- CalendarGrid.vue — M ResizeObserver 反馈循环防护
- SkillsTab.vue — M onMounted 始终 fetchSkills

文档修复(5 High + 6 Medium):
- portal-platform-security-reliability-fixes.md — D2 测试路径 / D3 Root Cause+Impact 章节 / D4 severity: mixed / 标题中文化 / 12 处绝对路径转相对 / P2 #12 数字口径
- AGENTS.md — D5 路由表 22→28 / 专家模板 5→15 / LiteLLM U15 迁移 / 配置查找 fallback
- README.md — 8 处端口 8000→8001

新增测试:
- tests/unit/calendar/test_calendar_tool.py — ponytail 自检断言

验证:
- ruff check (5 文件) — All checks passed
- vue-tsc --noEmit — exit 0
- git stash baseline 验证 — portal 17 个 401 失败为预存在问题

已知限制(预存在):
- 17 个 portal 测试 401 失败 — 需另起 ce-debug 调查
- README.md 7 处 CostAwareRouter 引用过时 — 文档同步另起任务
2026-06-28 15:06:41 +08:00
chiguyong 43e9025c6d fix(calendar): 日历能力缺失修复 + UI 布局优化 + 会话404处理
P0: calendar_tool reminder_rules 未传入 create_event,提醒功能完全失效。P1: chat.ts deleteConversation 未清理 pending + 404 递归保护。P2: app.py 系统提示重复段落 + gui_mode F821 + SystemMonitorPanel flex 布局。P3: portal send_json 快照 + WS connected 清除 is_local + 移除死代码。验证: ruff+pytest 98passed+typecheck 通过。
2026-06-28 14:24:58 +08:00
chiguyong 698a8fafba fix(review): U7 refresh token hash verification on whoami
The whoami route accepted rotated/old refresh tokens for cold-start
because it only checked session revocation status, not the token hash.
Now when token_type == "refresh", the route computes hash_token(token)
and compares it with the session's stored refresh_token_hash using
hmac.compare_digest (constant-time). Mismatch returns 401.

- Add SessionService.get_stored_refresh_hash(session_id) helper
- Add hash verification in whoami route (R9)
- Add TestWhoamiTokenHash with 5 integration tests
2026-06-22 16:55:20 +08:00
chiguyong 206b82740c fix(review): U5 DB migration — always run init_auth_db + busy_timeout 2026-06-22 16:40:30 +08:00
chiguyong 45b6752e0d fix(review): U4 WebSocket quota enforcement + gateway-layer hardening 2026-06-22 16:37:23 +08:00
chiguyong fd7f6816b8 feat(admin): U6 — Skill & KB management endpoints + department binding
SkillService: enable/disable (persisted in skill_states table, schema
v4), import from YAML (with path traversal + name validation), reload
from file, update config. GET /skills now filters disabled skills.

KbService: list/upload/delete documents with department_id binding.
Added department_id field to KnowledgeSource + UploadedDocument.
Department visibility: (bound to user depts) ∪ (global = None).

10 new admin endpoints: skill enable/disable/import/reload/update,
KB documents CRUD, source sync/rebuild. All guarded by _require_admin.

Implemented reload stub in skill_management.py (was no-op).

54 new tests (26 unit + 28 integration). Fixed 4 pre-existing lint
errors. 357 admin tests pass, no regressions.
2026-06-21 16:19:51 +08:00
chiguyong ad65f7a8d7 feat(admin): U1+U2+U4 — schema v3, department service, context filtering
U1: Bump _SCHEMA_VERSION to 3, add 5 department tables (departments,
user_departments, department_skill_bindings, department_kb_bindings,
department_quotas) + 5 ORM models + helpers.

U2: DepartmentService (12 async methods: CRUD + bind/unbind skill/KB +
count_users). Mount admin_router in app.py. 36 unit + 28 integration tests.

U4: DepartmentContext FastAPI dependency (per-route, admin bypasses
filtering). filter_skills_by_department / filter_kb_sources_by_department
helpers. Applied to GET /skills and GET /kb-management/* routes.
15 integration tests for department isolation.

Also includes brainstorm + plan docs. 108 new tests, all pass.
2026-06-21 15:03:27 +08:00
chiguyong 6dca9ba4f2 feat(admin): U3 — user CRUD + password reset + multi-department
Add create_user method to LocalAuthProvider (bcrypt hash + INSERT,
raises ValueError on duplicate username/email).

Add UserService with 9 async methods: create/list/get/update/delete
(soft)/reset_password/assign_department/remove_department/list_user_departments. reset_password revokes all sessions via SessionService.
delete_user is soft (is_active=0, row preserved).

Add 9 user endpoints to routes/admin.py: POST/GET/PATCH/DELETE users,
reset-password, assign/remove department, list departments. All
guarded by _require_admin.

Tests: 40 unit + 37 integration = 77 new tests. Full admin suite
170 tests pass, no regressions.
2026-06-21 13:45:42 +08:00
chiguyong 67c0d67262 fix(auth,chat): P0 security fixes + stop-generation button + doc sync
U1: whoami cold-start security — add is_active check (disabled users
now get 401, not 200) and replace create_token_pair with create_access_token
to avoid minting a discarded refresh token (token-amplification risk).

U2: list_active_by_provider now filters expired sessions (expires_at > now)
matching its docstring promise; previously only checked revoked = 0.

U3: Fix asyncio.run() crash in test_revoke_other_user_session_returns_404
(converted to async). Add U1/U2 verification tests (disabled-user whoami,
no-refresh-leak, expired-session filtering, provider filtering) and
strengthen admin route tests (404 boundary, non-admin 403 on /admin/sessions).

U4: Update CLAUDE.md/AGENTS.md Request Flow — CostAwareRouter 3-layer
diagram replaced with actual RequestPreprocessor architecture (@board/@team
prefix intercepts then @skill: prefix then trivial-input regex then default
REACT). ExecutionMode list expanded to all 7 values.

U5: Frontend stop-generation button — ChatInput.vue shows a stop button
when isGenerating is true; chat store gains stopGeneration() that sends
{type:"cancel"} over WebSocket (backend portal.py already handles cancel).

Tests: 120 auth tests pass (unit + integration). ruff clean. vue-tsc clean.
2026-06-21 11:36:58 +08:00
chiguyong aee7362665 feat(auth): U3/U4/U9 logout-others + whoami cold-start + admin UI + integration tests 2026-06-21 09:08:34 +08:00
chiguyong 9328451050 feat(auth): U7-U10 会话管理 UI + admin API + 测试修复
- U7: 前端 ActiveSessionsPanel + ChangePasswordPanel 组件
- U8: 用户会话管理(查看/撤销/改密)集成到 SettingsView
- U9: 管理员会话管理 API + UserSessionsPanel + AdminApiClient
- U10: 认证中间件支持 sid 会话验证 + legacy client 兼容
- 修复 test_auth.py 测试夹具:注入 SessionService 单例绑定测试 DB
- 修复 wrong-password 断言大小写匹配
- ruff: 清理未使用导入
2026-06-21 08:48:25 +08:00
chiguyong b418c3dc95 feat(auth): U3 SessionService + validation cache
Adds the central business-logic layer for ``auth_sessions`` so routes,
the auth middleware, and the admin endpoints can call a single service
instead of touching the table directly.

Server
- session_service.SessionService: CRUD + lifecycle for auth_sessions.
  - create() enforces the per-user cap (default 10): the oldest
    active session is evicted with reason=session_cap_eviction.
  - rotate() swaps a refresh token, adds the old hash to the
    denylist, and raises SessionReuseDetected (revoking all sessions
    for the user) when the old token is replayed.
  - revoke() / revoke_by_refresh_token() / revoke_all_for_user()
    with explicit reasons: user_terminated, admin_revoked,
    password_changed, reuse_detected, session_cap_eviction.
  - touch() bumps last_active_at (called on /auth/whoami).
- session_cache.SessionValidationCache: bounded LRU+TTL wrapper
  (default 30s/1k entries) around SessionService.is_session_valid.
  The middleware hits this on every request carrying a V2 sid claim;
  one SQLite round-trip per 30s per session instead of per request.
- get_session_service() / get_validation_cache() module-level
  singletons overridable in tests via set_session_service() /
  set_validation_cache().

Tests
- tests/unit/auth/test_session_service.py: 15 cases covering
  create/rotate/revoke/list/cap-eviction/reuse-detection/expired
  sessions.

Refs: U3 in docs/plans/2026-06-20-002-feat-centralized-auth-token-persistence-plan.md
2026-06-21 01:58:30 +08:00
chiguyong 5ba1aceb96 feat(auth): U2 JWT sid/jti claims + refresh-token denylist
Adds V2 JWT claim schema that closes the kicked-out window and enables
refresh-token rotation with reuse detection.

Server
- jwt_utils.create_token_pair now takes ``session_id`` and ``remember_me``
  kwargs.  When ``session_id`` is provided, both tokens carry a ``sid``
  claim and the access token also carries a ``jti`` claim; the refresh
  token's jti is intentionally absent (rotation uses the token hash).
- New ``REFRESH_TOKEN_TTL_REMEMBER_ME = 30d`` (default 7d) selected by
  the ``remember_me`` flag.
- ``verify_token`` now supports an optional ``expected_type`` filter
  (e.g. ``"access"`` / ``"refresh"``); when omitted, both types pass
  (used by /auth/whoami's cold-start path).
- New ``auth.denylist`` module: ``InMemoryRecentlyRevoked`` (default for
  the Tauri sidecar / dev mode) and ``RedisRecentlyRevoked`` (multi-
  process server).  Bounded LRU with auto-expiry via ``time.monotonic()``.

Backwards-compat
- Tokens issued before U2 (no ``sid``) are still accepted by
  ``verify_token``; validation falls through to the legacy
  ``user_sessions`` table via the U10 shim (next commit).

Tests
- tests/unit/auth/test_jwt_utils.py: 12 cases — V1/V2 claim presence,
  default + remember-me TTL, expected_type filter, expiry, wrong secret.
- tests/unit/auth/test_denylist.py: 6 cases — add/contains, TTL expiry,
  LRU eviction, re-add refresh, clear, hash stability.

Refs: U2 in docs/plans/2026-06-20-002-feat-centralized-auth-token-persistence-plan.md
2026-06-21 01:53:13 +08:00
chiguyong 2f55fc7434 feat(auth): U11 AuthProvider 抽象层 + auth_sessions schema
为未来对接集团 IdP(OIDC / SAML / LDAP / 飞书 / 钉钉 / 企微)留扩展点,
同时落地 auth_sessions 表(V2 替代 user_sessions)。

变更
- models.py: 新增 auth_sessions + auth_meta 表,V1→V2 数据回填
- providers/base.py: AuthProvider Protocol 接口契约
- providers/local.py: LocalAuthProvider 默认实现(封装 SQLite + bcrypt)
- providers/oidc_stub.py: StubOIDCProvider 占位(NotImplementedError)
- providers/__init__.py: get_auth_provider DI 工厂(lru_cache 单例)
- providers/exceptions.py: AuthProviderError / InvalidCredentials / ProviderNotImplemented
- providers/user.py: Provider-agnostic User 值对象
- tests/unit/auth/: 37 个测试覆盖 Protocol / DI / Local / OIDC 行为

auth_sessions.auth_provider 字段记录登录来源(local / oidc-stub / 未来
oidc-keycloak / saml / ldap),未来切 IdP 时审计可溯源。

测试: 37 passed (providers) + 62 passed (auth 全集) + ruff check clean
2026-06-21 01:28:14 +08:00
chiguyong 91f56ca663 feat: 企业级客户端-服务端架构 + 代码审查修复
## 主要变更

### 新增功能
- 企业级客户端-服务端架构(JWT 认证 + RBAC 权限 + 终端安全)
- Tauri 桌面客户端与服务端配置同步
- 远程 LLM 网关(RemoteLLMProvider,支持 401 token 刷新重试)
- 服务端终端 WebSocket(带管理员审批流程)
- 终端白名单六层防御(黑名单 → shell 操作符检测 → 内置安全 → 全局/用户/会话白名单 → 危险检测)

### 代码审查修复(P0/P1/P2)
- P0: 危险二进制(rm/docker 等)不再加入白名单,compute_whitelist_entry 返回 None
- P1: 终端审批所有权追踪(_approval_owners dict)+ 会话清理防泄漏
- P1: 本地终端 WebSocket URL 补齐 JWT token
- P1: 审计日志支持 terminal_mode 过滤
- P1: /system/resources 端点强制 SYSTEM_CONFIG 权限
- P1: RemoteLLMProvider 增加 401 token 刷新重试机制
- P1: auth/models.py 使用 Mapping[str, object] 替代 Any 类型
- P2: 终端授权依赖检查 is_active 账户状态
- 修复 app.py 未使用的 APIKeyAuthMiddleware 导入

### 文档更新
- README.md: 新增第 16 章「企业级客户端-服务端架构」
- AGENTS.md / CLAUDE.md: 同步模块映射、路由表、前端页面
- 计划文档标记为 completed

Closes: docs/plans/2026-06-19-003-feat-enterprise-client-server-evolution-plan.md
2026-06-20 06:48:18 +08:00