Commit Graph
8 Commits
Author SHA1 Message Date
chenchenandClaude Opus 4.8 66423c0845 feat(mcp-server): Heicode magic-link 邮箱登录三端点(仅登录,不触碰自有登录)
按 Docs/Heicode-magic-link…md 契约 §7.4/§9.4/§11/§13.2 新增并行登录方式:
- app/magic_link.py:Redis 一次性 token(600s)/code(120s) + 邮箱60s限流 + 复用
  现有 SMTP 通道发链接邮件(SMTP_PASSWORD 未配则不外发,DEBUG 打日志)
- app/routes/auth.py:新增 /api/auth/magic-link/{request,landing,verify}
  · request:IP+邮箱限流,防枚举一视同仁,仅对已存在 role=user 发信(D-1/D-2/D-3)
  · landing:消费 token→生成 code→302 heicode://auth/callback,失败回 HTML
  · verify:消费 code→复用 create_access_token/refresh + 与 /login 逐字段相同
    token_data → 登录产物等价,EU/计费零改动(§1.3)
- config.py:新增 MAGIC_LINK_PUBLIC_BASE_URL(默认 APIM 域,§11 终态)
- 三端点不声明 Depends(require_auth) 即公开,未改 allow_paths(§12.2)
- /login、/me、/refresh、/logout、/register 一行未改

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 18:25:02 +08:00
chenchenandClaude Opus 4.7 2477d01d61 docs(heicode): §7.17 ack token rotation + answer email-lookup question
Heicode rotated NewAPI service token (fingerprint 25b85d67…b1d6) per §7.16.7.
Walked through §7.7.2.1 flow:
  1. Read Docs/heicode-svc-token.txt — fingerprint matched
  2. kubectl create secret generic heicode-newapi (key=service-token)
  3. rollout restart — completed
  4. P4 smoke 4/4 with 55@55.com — balance/models/usage/logs all 200
  5. Deleted local Docs/heicode-svc-token.txt
  6. This §7.17 ack

Answer to Heicode's lookup question:
  Their `zsbgnw@gmail.com → USER_NOT_FOUND` observation was a side-effect of
  the stale token: while their new token was staged but our k8s secret still
  held the previous value, every NewAPI call returned 401, and our
  resolve_user_id_by_email() catches HeicodeNewAPIError and returns None —
  which the P4 router translates to 404 HEICODE_USER_NOT_FOUND.

  Direct re-test from inside the pod after rotation:
  `GET /api/user/search?keyword=zsbgnw@gmail.com&group=` → 200, 1 item,
  id=22 email=zsbgnw@gmail.com username=chenchen. Our query path is exactly
  what they suggested (`/api/user/search` with empty `group`), and we filter
  by email field downstream — implementation is fine.

Bonus finding: post-rotation `/balance` for zsbgnw@gmail.com returns
502 HEICODE_NEWAPI_UPSTREAM_ERROR because user 22 is super-admin and our
admin token holder (user 26) cannot read same-or-higher-level users
("No permission to access users of same or higher level"). NewAPI returns
HTTP 200 with success=false, our client correctly raises HeicodeNewAPIError.
This is an authorization policy on their side, not a bug — three options
proposed in §7.17.3 for product decision.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 11:58:37 +08:00
chenchenandClaude Opus 4.7 267172e103 fix(mcp-server): two audit-found register bugs
Found by post-fix audit pass over this session's changes:

1. Predcheck used `effective_username = (req.username or "").strip()` but the
   write at the User() construction site still used `req.username` raw. If a
   client sent "alice " with trailing whitespace, predcheck queried for "alice"
   (clean), missed the conflict, then wrote "alice " back. Now both sites use
   the same `effective_username` value — single source of truth.

2. Post-commit `verify_code` was guarded by `except Exception`, but
   `asyncio.CancelledError` is a BaseException and propagates through. If the
   request task is cancelled (client disconnect / pod shutdown) after DB
   commit but before verify_code finishes, the verification code stays in
   Redis with full 10-min TTL. Wrapped with `asyncio.shield(...)` so
   verify_code completes regardless of cancellation, and an explicit
   `except CancelledError: raise` preserves FastAPI's cancellation semantics
   for the outer request.

Verified via smoke:
- Register with username "ws_user_$ts  " (trailing spaces) → DB stores
  "ws_user_$ts" (18 chars, no whitespace). Predcheck and write now agree.
- P4 透传 (balance/models/usage/logs via 55@55.com) still 4/4 — no regression.

Latent bug noticed but NOT introduced this session, deferred:
- auth.py:981 writes ResourceAllocation.resource_id=str(provider.id) for
  model allocations, but channel.py:1881 queries by resource_id==model_name.
  Pre-existing inconsistency means update_tenant_model_quota never finds
  rows created at register time. TenantModelKey row is still updated
  correctly so end-user quota is honored; only the ResourceAllocation
  audit/reporting view diverges. Fix requires deciding which side is
  canonical — out of scope for security hardening.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 17:09:55 +08:00
chenchenandClaude Opus 4.7 1461051755 fix(mcp-server): correct Heicode user-models endpoint + username predcheck
Two small follow-ups to the register hardening + Heicode P4 work:

1. heicode_client.list_user_models: the path `/api/user/{id}/models`
   prescribed in §7.11.2 returns 404 `Invalid URL` on the live Heicode
   NewAPI — that path is not registered on their router. Switched to
   `/api/user/models` (no path segment), which Heicode binds to the
   `New-Api-User: 26` admin header. End-to-end P4 smoke now 4/4 with
   user 55@55.com (id=2 on Heicode): /balance /models /usage /logs.
   Future: if Heicode ships an "admin-replaces-user" path, switch back
   and pass the actual heicode_user_id.

2. routes/auth.register: previously line-744 SELECT only checked
   req.username, but line 778 falls back to email.split("@")[0] when
   blank — so two users registering with alice@foo.com and alice@bar.com
   would both clear the predcheck, then the second would IntegrityError
   on flush. Now predcheck uses `effective_username` matching what'll
   actually be inserted.

Also append §7.15 to Heicode-对接进度与待办.md:
- 4-item agent-manager / Vault / Workload-Identity audit results
- §7.13 token rotation acknowledgement
- P4 end-to-end first-pass results
- This-session internal security hardening summary

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 16:50:20 +08:00
chenchenandClaude Opus 4.7 610fde5d03 feat(mcp-server): Heicode integration + register transaction hardening
== Heicode integration (~41 endpoints across 5 modules) ==
- §2 ResourceBinding (5 endpoints) — resources.py / resource_grants.py
- §4 NewAPI metadata proxy (4 endpoints) — heicode_proxy.py + heicode_client.py
- §5 Agnet platform stub (12 endpoints, in-memory mock) — agnet_stub.py
- §6 Task orchestration (5 endpoints + 3 extension endpoints) — heicode_tasks.py
  6.1-6.5: intent / list / get / answer / messages
  6.6-6.8: execution / delivery / audit?tab=... (Slice 8/9/10)
- §7 SSE single channel + approvals (4 endpoints + 5 event types) —
  heicode_events.py + event_bus.py
- §7.8.1 internal billing-provider PUT endpoint — auth.py (routes)

== Schema changes ==
- migrations/026 heicode_tasks (orchestration state)
- migrations/027 users.billing_provider (litellm | newapi switch)
- migrations/028 heicode_approvals (high-risk approval queue)

== Register transaction hardening (P0 + P1 + P2) ==
routes/auth.py register():
- Pre-existing P0: failed register returned IntegrityError str verbatim
  (leaking SQL params + ~50 plaintext LiteLLM keys per attempt).
  Now logs exc_info, returns {code: REGISTER_FAILED, message: ...}.
- Pre-existing P0: model dedupe — two ModelProvider rows with overlapping
  supported_models (e.g. taiji/gpt-4o-mini in both taiji and azure providers)
  collide on uq_tenant_model. seen_models set deduplicates within the loop.
- New P1: track created_litellm_keys; on any failure call delete_key() for
  each — prevents remote orphan keys when DB rollback fires.
- New P1: replace verify_code with peek_verification_code at the start;
  only call verify_code (which consumes) after commit succeeds. Failed
  registrations no longer burn the user's one-shot code.
- New P2: narrow inner `except (LiteLLMClientError, Exception)` to just
  LiteLLMClientError so SQLAlchemy errors bubble to the outer rollback
  instead of being silently swallowed into a half-allocated 200 response.
- New P2: same narrowing on outer `except (AgentManagerError, Exception)`.

== Auth middleware ==
- app/auth.py: allow /api/auth/internal/billing-provider and
  /api/auth/internal/approvals to bypass user JWT (service-token auth
  via HEICODE_INTERNAL_SERVICE_TOKEN, validated in-route).

== Docs ==
- Heicode-接口契约文档.md v2.2 (41 endpoints + SSE schema + 6.6-6.8)
- Heicode-对接进度与待办.md (through §7.14 SSE + 7.8.2 delivery回执)
- Heicode-完整调用流程图.md (sequence + routing diagrams)
- Agent-Manager-Heicode对接需求文档.md
- HEICODE_API_INTEGRATION.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 15:43:10 +08:00
chenchenandClaude Opus 4.7 eb17ed84f8 fix(mcp-server): plug LiteLLM API key leak via register & tenant model assignment errors
A failed POST /api/auth/register returned the SQLAlchemy IntegrityError verbatim
to the caller, which included the full INSERT INTO tenant_model_keys statement
along with every bound parameter — ~50 plaintext LiteLLM API keys per failed
attempt. Same pattern was reproduced in 3 channel.py endpoints that wrap
LiteLLM key INSERTs.

Changes:
- channel.py: assign_resources_to_tenant / assign_model_to_tenant /
  update_tenant_model_quota — log full exc_info, return a typed
  {code, message} error instead of f"...{str(e)}". 6 leakage points sealed.
- email_verification.py: add peek_verification_code() — checks a code
  without burning it. Lets the register handler verify *before* the
  multi-step transaction so a downstream failure doesn't waste the user's
  one-shot code.
- scripts/cleanup_orphan_litellm_keys.py: one-shot orphan key reaper.
  Scans LiteLLM /key/list by metadata.tenant_id (plus a manual list of
  the 8 publicly-leaked sk- prefixes from the original incident).
  Used to nuke 16 orphan keys for tenant fab9dc27-… on 2026-05-12.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 15:42:38 +08:00
chenchen 46a3da0eb4 更新md 2026-05-05 14:25:28 +08:00
chenchen d0b79030f1 更新heicode 2026-05-05 14:13:59 +08:00