# Heicode magic-link 邮箱登录 — 给 mcp-server 的对接需求 **来源**: Heicode Manager(HM)owner @zsbgnw12 · 对应 HM 工单 #19 **状态**: 需求/契约草案,**待 mcp-server 端确认与排期** **读者**: mcp-server 后端(Heicode Manager 身份服务) **基线**: 本文在现有《Heicode-登录接口对接文档.md v1.0》(`/api/auth/login` 等 4 接口已上线)之上**新增**一种登录方式,不改动现有任何接口。 --- ## 0. 一句话需求 参考 Claude Code 的**邮箱 magic-link 登录**:客户端只输入邮箱(**不输密码**)→ mcp 发一封邮件 → 用户点链接 → 回跳桌面客户端完成登录。 > **magic-link 是"换一种方式证明邮箱归属",最终必须产出与 `POST /api/auth/login` **完全相同**的登录产物(同样的 `{token, refreshToken, user}`、同样的 JWT claims 含 `channelId`、同样的 24h/7d 有效期、同样的审计与会话)。** --- ## 1. 硬约束(最重要,先读) 1. **绝不改动、绝不影响现有密码登录链路**:`POST /api/auth/login`、`GET /api/auth/me`、`POST /api/auth/refresh`、`POST /api/auth/logout` 行为、字段、JWT 一律不变。magic-link 是**并行新增**的获取 token 的方式,密码登录继续作为兼容/降级路径保留。 2. **magic-link 登录的"账号"与密码登录是同一套用户**:同一邮箱无论用密码还是 magic-link 登录,解析到的都是**同一个 user 记录**(同一 `id`、`channelId`、`role`)。 3. **登录产物必须等价**:`verify` 成功响应体 = `/api/auth/login` 成功响应体(同 `{success, data:{token, refreshToken, user:{id,name,email,role,channelId}}}`、同 JWT claims、同 TTL)。 - **原因(HM owner 强调)**:mcp-server 登录态 = 计费入口(agent-manager pods 运行时长 / EU 计量)。只要登录产物与现有登录一致,**EU 计费、pods 运行计量、下游一切都无需改动**。magic-link 不得引入新的会话类型或新的计费分支。 4. **不得在日志/URL 持久留存敏感凭证**:magic-link token、一次性 code 不进普通日志、不在 URL query 长期留存(仅一次性回跳)。 --- ## 2. 需要 mcp-server 新增的内容(3 个端点 + 发邮件 + 落地页) > 路径前缀建议放在现有 auth 命名空间下(如 `/api/auth/magic-link/*`),**最终 path 由 mcp 定**;定稿后 HM 会在 heicodeDocs `integration/` 锁定契约,客户端据此切真。客户端经 HM 同源代理 `/api/heicode-auth/*` 访问这些端点(无需关心跨域)。 ### 2.1 `POST /api/auth/magic-link/request` — 申请登录链接 **请求** ```json { "email": "user@example.com" } ``` **行为** - 校验邮箱格式;查用户(同密码登录的用户库)。 - 生成**一次性、短 TTL(建议 10 分钟)**的 magic-link token,与该邮箱 + 一个随机 `state` 绑定。 - **发邮件**,链接指向落地页(§2.2),链接携带 `token` + `state`。 - **防枚举**:无论邮箱是否注册,**统一返回成功**;配合限流(对齐现有登录 5 次/分钟/IP 量级)。 **成功响应 200** ```json { "success": true, "data": { "request_id": "", "state": "", "expires_in_sec": 600 } } ``` > `state` 客户端会存下,回跳时严格比对。`request_id` 仅供追踪/可选轮询(见 §4 跨设备)。 ### 2.2 落地页 `GET /api/auth/magic-link/landing?token=...&state=...` — 邮件链接指向它(mcp 托管) **行为** - 校验 token:存在、未过期、未被用过(一次性)。 - 校验通过 → 生成**一次性、TTL ≤ 2 分钟**的 `code`,绑定到该 user + `state`;**302 跳转**到桌面深链: ``` 302 Location: heicode://auth/callback?code=&state= ``` - 校验失败/过期/已用 → 返回一个**人类可读 HTML 页**(如「链接无效或已过期,请回客户端重新获取」)。 - **Phase 1 跨设备提示(重要)**:落地页文案明确写「**请在已安装 HeiCode 的同一台设备上打开此链接**」。因为深链 `heicode://` 只能唤起本机客户端;若用户在手机/另一台机器打开,本机客户端收不到回跳。Phase 2 再考虑轮询兜底(见 §4)。 ### 2.3 `POST /api/auth/magic-link/verify` — 用 code 换登录态 **请求** ```json { "code": "", "state": "", "device_pubkey": "<可选>" } ``` **行为** - 校验 `code`:存在、未过期(≤2min)、未被用过(一次性)、与 `state` 一致。 - 通过 → **签发与 `/api/auth/login` 完全相同的登录产物**(见 §1.3)。 - `device_pubkey` 可选:mcp 可忽略(设备配对由 HM 侧完成,见 §3);若 mcp 想绑定设备也可记录,但**不得改变返回的 token 结构**。 **成功响应 200(必须与 login 一致)** ```json { "success": true, "data": { "token": "eyJ...", "refreshToken": "eyJ...", "user": { "id": "...", "name": "...", "email": "...", "role": "user", "channelId": "..." } } } ``` **错误**:`code` 无效/过期/已用 → `400/401`(客户端会丢弃重来);限流 → `429 + Retry-After`。 --- ## 3. 与 HM / 客户端的边界(mcp 不用管的部分) - **HM 只做同源代理**:客户端经 `https://code.xinghanlab.com/api/heicode-auth/` → HM 透明转发到 `HEICODE_AUTH_BASE_URL`(=`https://apimtaiji.azure-api.net/api/mcp`)。所以你只要实现 §2 三个端点,HM 自动可达,**HM 侧无需你做任何额外接口**。 - **设备配对/会话(HM 侧,已存在,不变)**:客户端拿到 mcp 的 token 后,继续走 HM 现有的 `from-agent → sk- + V2 设备配对(Ed25519/ChaCha20)`——这是 HM↔客户端的模型访问会话,**与你无关、不变**。magic-link 只改变"客户端如何拿到 mcp token"这一段。 - **深链注册/转发(客户端侧)**:`heicode://auth/callback` 由客户端(winos/macos)注册与转发,你只需在落地页 302 到它。 --- ## 4. 待 mcp 确认/拍板 + 开放问题 1. **最终 path 前缀**:`/api/auth/magic-link/{request,landing,verify}` 是否 OK?定了我在 heicodeDocs 锁契约。 2. **邮件基础设施**:mcp 当前是否具备发信能力(SMTP/SES/SendGrid 等)?magic-link 邮件的发送、模板、送达率、防滥用由 mcp 侧承载——**这是上线前置**,请确认负责人/通道。(HM owner 已在 #19 标注"邮件基建负责人待产品/基建指认"。) 3. **token / code TTL**:magic-link token 10min、code ≤2min、均一次性 —— 是否可行? 4. **device_pubkey**:你倾向忽略(交给 HM 配对)还是要绑定?(不影响 token 结构即可) 5. **跨设备**:Phase 1 用落地页"同设备"提示即可(无需轮询);若你们愿意提供 `GET /api/auth/magic-link/status?request_id=`(返回 pending/confirmed)作 Phase 2 兜底,客户端可轮询,但非必须。 6. **隐私/合规**:本流程仅"邮箱 → 一次性 code",不采集用户内容;与遥测(另一个工单 #24)的隐私政策修订是两回事,本登录流程无新增 PII 采集。请确认无合规阻碍。 --- ## 5. 验收(契约定稿 + 邮件可用后) - 输入邮箱 → 收到真实邮件 → 点链接 → 落地页 302 → 客户端回跳 → `verify` 换到 token → 进主界面; - 用 magic-link 登录后,`/me`、`/refresh`、`/logout`、**EU/pods 计费**与密码登录**完全一致**; - 密码登录路径回归不变; - token/code 一次性 + 过期失效;限流生效;邮箱枚举不可探测。 --- ## 6. 请回复 请 mcp-server 端确认 §4 各项(尤其**邮件基建是否可用**、**最终 path**),并指出本需求与现状有无冲突。确认后:HM 立 heicodeDocs `integration/` 正式契约 → 客户端切真实 path 联调。客户端侧脚手架(邮箱登录 UI + `heicode://` 注册,对 mock)已可先行,不阻塞你们。 *(本文件放在 Heicode↔mcp-server 共享 Docs 通道;有问题直接在本文件追加或回 HM 工单 #19。)* --- # 7. mcp-server 端确认与讨论(2026-06-09,基于代码核验) > 本节由 mcp-server 后端逐条核对**真实代码**后给出,所有结论附文件:行号。涉及需 HM 拍板的开放项见 §7.3。 > 核验范围:`app/routes/auth.py`、`app/auth.py`、`app/email_verification.py`、`config.py`。当前仓库**无任何既有 magic-link 代码**(全仓 grep `magic` 无命中),属全新增。 ## 7.1 可直接确认的项(§4 逐条答复) **[§4.1 path 前缀] ✅ OK。** 现有认证路由 `router = APIRouter(prefix="/api/auth", ...)`(`app/routes/auth.py:51`),`/api/auth/magic-link/{request,landing,verify}` 完美落在该命名空间下,与现有 `/login`、`/register`、`/forgot-password/*` 并列。 **[§4.3 token/code TTL + 一次性] ✅ 可行。** 现有验证码已用 Redis `setex`(10min)存、命中即 `delete` 消费的 one-time 模式(`app/email_verification.py:204-231` 存、`:234-308` 验+删)。magic-link token(10min)/code(≤2min) 直接复用同一套,分别用独立 key 前缀即可。Redis 实例已在 `config.py:31` 配好并经 `state.redis_client` 全局可用。 **[§4.4 device_pubkey] ✅ 建议忽略。** 设备配对在 HM 侧(§3 已约定)。token 结构由 `create_access_token(token_data)` 固定,我方忽略 `device_pubkey` 即可,**不动 token 结构**。 **[§4.5 跨设备 Phase2] Phase1 仅落地页文案即可;Phase2 `status?request_id=` 轮询可做但非必须**——Redis 可存 `pending/confirmed` 状态兜底,待 Phase1 上线后再评估。 **[§4.6 隐私/合规] ✅ 无阻碍。** 本流程仅"邮箱 → 一次性 token/code",不新增 PII 采集。 **[§1.3 登录产物等价] ✅ 可严格满足,且 EU/计费零改动。** 核验如下: - `/login` 成功响应体 = `{success, data:{token, refreshToken, user:{id,name,email,role,channelId}}}`(`app/routes/auth.py:219-231`)。 - `create_access_token` TTL = `settings.jwt_expire_minutes` = **1440(24h)**(`app/auth.py:45` + `config.py:95`),追加 claims `exp/iat/type="access"`,其余 claims 来自传入 `token_data`(`sub/email/role/channelId`)。 - `create_refresh_token` TTL = **7 天**(`app/auth.py:56,68`),`type="refresh"`。 - 结论:magic-link `verify` 复用**同一个** `token_data` 和 `create_access_token/create_refresh_token`,产出的 JWT claims、TTL、响应体可与 `/login` **逐字段一致**。因此不引入新会话类型/新计费分支,满足 §1.1/§1.3。 **[§1.2 同一套用户] ✅ 同库。** `/login` 与认证助手都按 `User.email` 查同一张 `User` 表(`app/auth.py:78-80`、`app/routes/auth.py:158`)。magic-link 按 email 解析到的就是同一 user。 ## 7.2 落地必须处理的两处代码事实(非冲突,但属实现前置) **[A] 新端点必须显式加入鉴权白名单,否则会被全局鉴权拦成 401。** > ⚠️ **本条 [A] 经 §12 实现级验证后更正:表述不准确,结论作废。** 实际无需改任何 `allow_paths`——magic-link 三端点只要**不声明 `Depends(require_auth)`** 即为公开(与 `login`/`register` 一致)。详见 §12.2。以下原文保留仅作讨论留痕。 > > ~~`require_auth` 对 `/api` 前缀默认要求鉴权,仅 `allow_paths` 白名单放行(`app/auth.py:197-220`);`/api/auth/login` 在白名单内,但 `/api/auth/magic-link/*` **不在**。同样地,中间件 `authenticate_request` 也有一份独立白名单(`app/auth.py:335-346`)。→ `request`/`landing`/`verify` 三个端点必须**同时**加入这两处 `allow_paths`。~~ **[B] 缺"本服务公网自身 URL"配置——邮件里的 landing 绝对地址无处可取。** `config.py` 现有 `heicode_newapi_base_url = https://code.xinghanlab.com`(`config.py:124-126`),但**没有**指向 mcp-server 自身公网入口的配置项。需求 §3 的 `HEICODE_AUTH_BASE_URL=https://apimtaiji.azure-api.net/api/mcp` 是 **HM 侧**配置,不在我方代码里。 → 要在邮件正文里拼出可点击的 landing 绝对 URL,需**新增一个设置项**(拟 `MAGIC_LINK_PUBLIC_BASE_URL`)并由运维注入。这同时牵出一个链路事实(见 §7.3-D):**邮件链接是用户浏览器直接打开的,不经过 HM 的 `/api/heicode-auth/*` JSON 代理**,所以该 URL 必须公网直达、且 APIM 要放行这个 GET 并允许返回 302/HTML。 **[补充] landing 返回 302/HTML 是本服务首次引入的响应类型。** 现有 auth 端点全部返回 JSON `SuccessResponse`;`landing` 需用 FastAPI 的 `RedirectResponse(302)` + 失败 `HTMLResponse`。FastAPI 原生支持,无技术障碍,仅提示与现有风格不同。 **[补充] 邮件模板需新增。** 现有 `send_and_store_verification_code` 发的是**6位数字验证码纯文本**(`app/email_verification.py:59-61` 生成、`:104-118` 正文模板)。magic-link 要发**带 token+state 的 URL**,需新增一个邮件模板与发送函数;SMTP 通道本身可用(`smtp.189.cn:465` SSL,发件 `taijiagent@189.cn`,密码走 `SMTP_PASSWORD` secret,`app/email_verification.py:18-25`)。 **[限流] ✅ 已具备,对齐 §2.1。** 按 IP 5次/60s 的 `_LoginRateLimit`(`app/routes/auth.py:54-85`)+ 按邮箱 60s 的 `check_rate_limit/set_rate_limit`(`app/email_verification.py:146-201`)均可直接复用。 ## 7.3 mcp-server → HM 的反问(需 HM 拍板,否则会返工) **[D-1 ⭐最关键] magic-link 是否需要"首次登录即注册"?** 需求 §1.2/§2.3 表述为"查用户(同密码登录的用户库)"→"签发登录产物",**默认用户已存在**;而 `/login` 对查不到的邮箱直接 401(`app/routes/auth.py:161-165`)。我方 `/register` 在注册时要做一大串默认资源分配——taiji 渠道、CPU/内存配额、平台 Agent 配额、**逐模型在 LiteLLM 创建 key**、20 元余额(`app/routes/auth.py:720-1100`)。 → 若未注册邮箱点 magic-link 也要能进来,`verify` 就得触发整套注册 provisioning(一个大分支)。**mcp-server 建议:magic-link = 仅登录**;未注册邮箱在 `request` 阶段静默不发信(与 §7.3-D-2 的防枚举一致),注册仍走现有 `send-code + register`。请 HM 确认是否接受"仅登录"。 **[D-2] 防枚举行为将与现有两个接口语义不一致(需 HM 知晓)。** 需求 §2.1 要求 `request` **无论邮箱是否注册都返回成功**。但现有 `register/send-code` 对已注册邮箱返回 `400 该邮箱已被注册`(`app/routes/auth.py:701-705`)、`forgot-password/send-code` 对未注册返回 `404 该邮箱未注册`(`app/routes/auth.py:1136-1142`)——**这两个老接口会泄漏邮箱存在性**。magic-link 新端点将**刻意采用统一成功**的更安全行为。这不是冲突(新端点独立),但请 HM 知晓"magic-link 与老 send-code 语义不同"。 **[D-3] 覆盖角色范围?** 现有 `/login` 区分 `channel`(独立 `Channel` 表/独立 email,`app/routes/auth.py:114-117`)与 `user/admin/...`。HM 的桌面客户端用户应为 `role=user`。请确认 **magic-link 仅面向 `role=user`,channel/admin 继续走密码登录**(mcp-server 倾向如此,避免同一邮箱在 User/Channel 两表的歧义)。 **[D-4] landing 公网 URL 与 APIM 放行由谁配、给什么域名?**(对应 §7.2-B)需 HM/APIM 侧确认:邮件里的 landing 用哪个公网域名(是否 `https://apimtaiji.azure-api.net/api/mcp/api/auth/magic-link/landing`)、APIM 是否放行该 GET 并允许 302+HTML 透传。这属上线前置。 **[D-5] 邮件基建归属(§4.2 HM 已标注"负责人待指认")。** SMTP 通道我方可用(见 §7.2 补充),但 magic-link 邮件的**模板文案、发件人是否沿用 `taijiagent@189.cn`、送达率/防滥用**仍需产品/基建指认负责人。 ## 7.4 mcp-server 侧落地清单(待 §7.3 拍板后执行;改鉴权链路属高风险,先出方案再写码) 1. `app/routes/auth.py`:新增 `magic-link/request`、`magic-link/landing`(302+HTML)、`magic-link/verify` 三端点;`verify` 复用 `create_access_token/create_refresh_token` + 与 `/login` 逐字段相同的 `token_data`。 2. `app/auth.py`:两处 `allow_paths` 同步加入 `request`/`landing`/`verify`(§7.2-A)。 3. `app/email_verification.py`:新增 magic-link 邮件模板与发送函数(URL 形式)。 4. Redis:`magic_link_token:{token}`(10min)、`magic_link_code:{code}`(≤2min) 两类 one-time key,复用现有 setex/delete。 5. `config.py`:新增 `MAGIC_LINK_PUBLIC_BASE_URL`(§7.2-B),由运维注入;并在 k8s configmap 增配。 6. 全程不触碰 `/login`、`/me`、`/refresh`、`/logout` 现有行为(满足 §1.1)。 > **本变更影响面(按组织 PR 规则预声明)**:影响 Manager 身份/登录链路(mcp-server)与**邮件基建**;新增需注入的配置项 `MAGIC_LINK_PUBLIC_BASE_URL`;**不影响** Swarm、Agent、CodeGW、计费分支、密钥审批链(沿用现有 SMTP secret,不新增密钥类型);审计沿用现有 `log_audit_event`。正式实现前将另出实现方案评审。 --- # 8. HM owner @zsbgnw12 回复 mcp-server 反问(2026-06-09 · 拍板) 感谢逐条附代码核验,质量很高。§7.1 确认项与 §1.3 登录产物等价的核对(`create_access_token/refresh` + 同 `token_data`)完全符合预期——**这正是"不影响原始登录、EU/计费零改动"的关键**。逐条拍板 §7.3: **[D-1 ⭐ 仅登录,接受你的建议] ✅ magic-link = 仅登录,不做"首次登录即注册"。** 理由:与现有模型一致(注册走 web 端 / 现有 `send-code + register`,登录走客户端);#19 客户端诉求是"登录方式替代 OAuth STUB",**不含注册**。所以:`request` 阶段未注册邮箱**静默不发信**(与 D-2 防枚举一致),`verify` 只对已存在 user 签发登录产物,**绝不触发你那套 register provisioning(taiji 渠道/配额/LiteLLM key/20 元余额)**。新用户注册维持现状不变。 **[D-2 防枚举] ✅ 知晓并采纳。** 新 `magic-link/request` 用"统一返回成功"的更安全语义;**不要求**你改老的 `register/send-code`、`forgot-password/send-code`(它们维持现状,本次不动)。新端点独立、更安全即可,无冲突。 **[D-3 角色范围] ✅ magic-link 仅面向 `role=user`(桌面客户端)。** channel/admin 继续走密码登录,避免 User/Channel 两表歧义——按你倾向定。 **[D-4 landing 公网 URL + APIM] —— 澄清归属 + 给你二选一:** - 你判断正确:**邮件链接是浏览器直开的,不经 HM 的 `/api/heicode-auth/*` JSON 代理**。HM 代理只服务客户端的**程序化 JSON 调用**(`request`/`verify` 可经 `code.xinghanlab.com/api/heicode-auth/api/auth/magic-link/{request,verify}` 同源访问);**landing 是浏览器→公网直达,不走 HM**。 - **落地页托管二选一,你们定:** - **(方案1,推荐)** landing 在你们公网域(`https://apimtaiji.azure-api.net/api/mcp/api/auth/magic-link/landing`)。前置:**APIM 放行该 GET 并允许返回 302 + HTML**——这属 **apimtaiji 平台/APIM 侧配置(与 mcp 同侧),HM 无代码、不托管**。`MAGIC_LINK_PUBLIC_BASE_URL` 即填此域。 - **(方案2,APIM 不便返回 302/HTML 时的兜底)** 由 **HM 托管落地页**(`https://code.xinghanlab.com/heicode/magic-link/landing`):HM 收到浏览器 GET → 调你们一个**校验 token 的 JSON API**(如 `POST /api/auth/magic-link/landing-verify {token,state}` 返回校验结果 + 一次性 code)→ HM 负责 302 到 `heicode://`。这样 302/HTML 在 HM 出,APIM 只需放行一个 JSON。**若你们选方案2,请把 landing 拆成"JSON 校验 API",HM 来出 302。** **[D-5 邮件基建] 归属仍待产品/基建,我继续在 #19 追 @Fasthei/产品。** 你们 SMTP 通道(`taijiagent@189.cn`)可用是好事;模板文案/发件人/送达率/防滥用的最终签字属产品。这条是**上线前置**,不阻塞你们先把端点/落地页骨架写好(对 mock 邮件或先打日志)。 **[§7.2-A/B 实现前置] 👍 好的提醒,属你们实现细节,HM 无异议:** - A(两处 `allow_paths` 白名单加 3 端点)、B(新增 `MAGIC_LINK_PUBLIC_BASE_URL` 由运维注入)—— 你们按 §7.4 清单执行即可。改鉴权链路高风险、先出方案评审的做法 👍。 ## 下一步(HM 侧) - D-1/D-2/D-3 已拍定(仅登录 / role=user / 防枚举),**不阻塞你们开始写 request/verify/landing 骨架**。 - D-4 落地页方案(1 还是 2)请你们回一句;选方案2 我这边加 HM 落地页 + 302(小改动)。 - D-5 邮件基建 + path 定稿后,我在 heicodeDocs `integration/` 立正式契约,客户端切真实 path 联调。客户端脚手架(邮箱 UI + `heicode://` 注册,对 mock)已可先行。 --- # 9. mcp-server 回复 HM 拍板 + D-4 选型(2026-06-09,基于代码核验) > 收到 §8 拍板。D-1/D-2/D-3 我方无异议,按"仅登录 / role=user / 防枚举统一成功"实现。下面只就 **D-4 落地页托管**给出基于真实部署事实的选型与一个前置确认。 ## 9.1 先确认 D-1/D-2/D-3 落实口径(与 §8 一致) - **D-1 仅登录**:`verify` 只对**已存在 user** 签发登录产物,未注册邮箱在 `request` 阶段**静默不发信**,**绝不触发** `register` 那套 provisioning(`app/routes/auth.py:720-1100`)。 - 旁证:现有《Heicode-登录接口对接文档.md v1.0》§1 已写明"注册…走官网 web 端,不在本期范围"——与本次"仅登录"口径天然一致,无需任何注册侧改动。 - **D-2 防枚举**:仅新 `magic-link/request` 用统一成功语义;**不动**老 `register/send-code`、`forgot-password/send-code`。 - **D-3 role=user**:`verify` 解析到的 user 若 `role != "user"` 则不签发(按 §8 规避 User/Channel 两表歧义)。 ## 9.2 D-4 选型:**推荐方案1,且落地页走 nginx Ingress 直连、绕开 APIM 的 302/HTML 不确定性** 核实部署清单后发现一个关键事实,能让方案1**不依赖 APIM 能否返回 302/HTML**: - 我方生产有**两个公网入口**: 1. **nginx Ingress `mcp.taiji-ai.com`** → 直连 `mcp-server:8000`(`k8s/prod/ingress.yaml:35,39-45`),**纯反代透传**,无 JSON 限制,原生支持 302 + HTML; 2. **APIM `https://apimtaiji.azure-api.net/api/mcp`** —— 现有客户端 JSON 登录走它(《Heicode-登录接口对接文档.md》§2.1)。 - **结论**:`request`/`verify`(程序化 JSON)继续走 APIM(与现有 4 接口同源,HM 代理可达);**而落地页 `landing`(浏览器直开、需 302+HTML)改挂在 nginx Ingress 的 `mcp.taiji-ai.com` 上**。这样 302/HTML 由 nginx 透传,**完全不碰 APIM 的响应类型限制**——你 §8 担心的"APIM 是否放行 302/HTML"这条前置直接消解。 - 即 `MAGIC_LINK_PUBLIC_BASE_URL = https://mcp.taiji-ai.com`,邮件里的链接 = `https://mcp.taiji-ai.com/api/auth/magic-link/landing?token=...&state=...`。 - 安全上方案1 更干净:**token 与一次性 code 全程不离开 mcp-server**,落地页校验 token→生成 code→302 在同一服务内原子完成;不像方案2 要把一次性 code 交给 HM 再 302(多一跳、攻击面更大)。 **⚠️ 选方案1 必须先确认的一个事实(运维侧,我方代码无法判定):** `mcp.taiji-ai.com` 的 Ingress **TLS 块当前是注释状态**(`k8s/prod/ingress.yaml:29-33`,cert-manager 签发注解 `:26` 也注释)。而邮件链接**必须是 HTTPS**(《Heicode-登录接口对接文档.md》§6"禁止回退 HTTP")。 → 请运维确认 **`mcp.taiji-ai.com` 是否已实际启用 HTTPS / 证书**: - **若已启用** → 直接采用方案1(landing 挂 `mcp.taiji-ai.com`),`MAGIC_LINK_PUBLIC_BASE_URL` 即填此域,APIM 无需任何改动。 - **若未启用且不便启用** → 退而求其次仍走方案1、但 landing 挂 APIM(`apimtaiji.azure-api.net/api/mcp/...`),**此时回到你 §8 的前置:需 APIM owner 放行该 GET 并允许 302+HTML**。 - **两条 HTTPS 落地页都不可得时** → 才走**方案2**(HM 托管落地页),契约见 §9.3。 ## 9.3 方案2 兜底契约(仅当 9.2 两条 HTTPS 落地页都不可行时启用) 按 §8 要求,把 landing 拆成"纯 JSON 校验 API",302/HTML 由 HM 出: - **新增** `POST /api/auth/magic-link/landing-verify`(JSON,挂 APIM 即可,无 302/HTML) - 请求:`{ "token": "", "state": "" }` - 行为:校验 token(存在/未过期/未用,一次性消费)+ 校验 user `role==user`(D-3)→ 通过则生成一次性 `code`(TTL ≤2min,绑定 user+state)。 - 成功 200:`{ "success": true, "data": { "code": "", "state": "" } }` - 失败:`400/401`(token 无效/过期/已用 或 role 不符),HM 据此渲染"链接无效"页。 - HM 侧:收浏览器 GET → 调本 API → 成功则 `302 Location: heicode://auth/callback?code=...&state=...`,失败则渲染 HTML。 - 安全提示:方案2 下一次性 `code` 会经 HM 中转,请 HM 确保该响应不落普通日志(对齐 §1.4)。 > **我方倾向**:方案1(优先 `mcp.taiji-ai.com` 直连)。请 HM/运维就 9.2 的 HTTPS 前置回一句,即可定稿。 ## 9.4 据此更新落地清单(覆盖 §7.4 第 5 条) - `config.py` 新增 `MAGIC_LINK_PUBLIC_BASE_URL`(方案1 填 `https://mcp.taiji-ai.com` 或 APIM 域;方案2 该项可不用,改由 HM 域拼链接)。 - 方案1 无需新增 `landing-verify`;方案2 需新增并同样加入两处 `allow_paths` 白名单(§7.2-A)。 - 其余(request/verify、邮件模板、Redis one-time key、限流复用)不变。 > **影响面更新**:选方案1 时**不改 APIM、不增 HM 代码**,仅 mcp-server 内新增端点 + 一个配置项 + 确认 `mcp.taiji-ai.com` 的 TLS(运维)。仍不影响 Swarm/Agent/计费/密钥审批链。 --- # 10. HM owner @zsbgnw12 确认 §9 选型(2026-06-09) D-1/D-2/D-3 双方口径已一致(仅登录 / role=user / 防枚举统一成功),你 §9.1 的落实表述与 HM §8 完全吻合,无异议。 **D-4 选型 —— HM 同意你的方案1(landing 挂 `mcp.taiji-ai.com` 直连),理由认同:** - 你的洞察对:把 `landing` 放 nginx Ingress 直连,302/HTML 由 nginx 透传,**绕开 APIM 响应类型限制**,我 §8 担心的那条前置直接消解;且 **token/一次性 code 全程不出 mcp-server**,比方案2 少一跳、攻击面更小——安全上方案1 确实更优。`request`/`verify` 继续走 APIM(HM 同源代理可达)不变。 - `MAGIC_LINK_PUBLIC_BASE_URL = https://mcp.taiji-ai.com`,邮件链接 = `https://mcp.taiji-ai.com/api/auth/magic-link/landing?...`。 **关于 §9.2 的 HTTPS 前置 —— 归属澄清(非 HM):** - `mcp.taiji-ai.com` 是**你们(mcp/taiji 平台)侧的 Ingress 域名**,其 TLS/证书启用与否由**你们/平台运维**确认与开启,**HM(code.xinghanlab.com)无权也无代码涉及**。 - 所以这条 HTTPS 前置请你们运维拍: - **已启用/可启用 HTTPS** → 直接方案1(`mcp.taiji-ai.com`),APIM、HM 都不动 —— 这是最干净路径,**HM 侧零改动**。 - **该域名 HTTPS 不可得,但 APIM 可放行 GET+302+HTML** → 方案1-APIM(landing 挂 apimtaiji 域),需你们 APIM owner 放行。 - **两条 HTTPS 落地页都不可得** → 才走**方案2**:HM 托管落地页。**HM 接受 §9.3 兜底契约**(你们提供 `POST /api/auth/magic-link/landing-verify` JSON API,HM 收浏览器 GET→调它→302 到 `heicode://`,失败渲染 HTML;HM 保证一次性 code 不落普通日志)。 ## 开始实现的前置(对应"两方确认无问题才开搞") 双方技术设计已对齐。**还差两条确认才进入实现:** 1. **落地页 HTTPS 路径**(你们运维):确认 `mcp.taiji-ai.com` HTTPS 可用 → 定方案1;否则告知,我按方案1-APIM 或方案2 配合。 2. **邮件基建负责人/签字**(D-5,产品/基建,HM 在工单 #19 追)。 这两条一锁,契约即定稿:HM 在 heicodeDocs `integration/` 落正式契约,mcp 按 §7.4/§9.4 清单实现(默认即可上骨架,但**不外发**直到邮件基建就位),客户端切真实 path 联调。在此之前**双方都不动实现**(按 HM owner 要求:确认无误再开搞)。 → 请你们运维回 §9.2 的 HTTPS 一句;邮件基建我去催产品。两条齐 → 开搞。 --- # 11. mcp-server 定调 D-4:**统一走 APIM 网关(不启用 mcp.taiji-ai.com 直连)**(2026-06-09) > 我方(mcp/taiji 平台侧)拍板:**landing 也走 APIM,全部公网流量统一从网关进**。放弃 §9.2/§10 倾向的"`mcp.taiji-ai.com` 直连"路径。即采用 §9.2 列出的 **方案1-APIM**。 **决定与理由(统一网关):** - 现有 4 个登录接口本就**全部经 APIM** `https://apimtaiji.azure-api.net/api/mcp`(《Heicode-登录接口对接文档.md》§2.1)。magic-link 的 `request`/`verify`/`landing` **一律走同一网关**,不为落地页单开第二个公网入口。 - 不启用 `mcp.taiji-ai.com` 直连的现实依据:该 Ingress 的 TLS 当前就是注释状态(`k8s/prod/ingress.yaml:29-33`),本就没对外提供 HTTPS;与其临时给它配证书、多开一个公网面,不如**收敛到 APIM 单一入口**——公网攻击面、TLS/证书、WAF、限流、审计边界全部统一在网关,运维与安全口径一致。 - 安全姿态不变:landing 的"校验 token → 生成一次性 code → 302"逻辑仍在 mcp-server 的 FastAPI 内,**APIM 只做反向代理透传,token 与一次性 code 不落 HM、不出我方服务**(仍是方案1 的安全模型,**不是**方案2)。 **最终取值:** - `MAGIC_LINK_PUBLIC_BASE_URL = https://apimtaiji.azure-api.net/api/mcp` - 邮件链接 = `https://apimtaiji.azure-api.net/api/mcp/api/auth/magic-link/landing?token=...&state=...` - `request`/`verify` 路径不变,客户端经 HM 同源代理 `/api/heicode-auth/*` → APIM 调用。 **⚠️ 唯一技术前置(APIM owner 确认/配置,我方代码无法判定,必须开搞前锁定):** APIM 对 landing 这个 **GET** 的 operation 必须满足——否则浏览器拿不到回跳: 1. **放行该 GET** `/api/mcp/api/auth/magic-link/landing`(含 `token`/`state` query 透传)。 2. **原样返回后端的 302**:不得 follow-redirect(APIM 不能代为跟随,必须把 302 透回浏览器)、不得缓冲改写、不得强制 JSON。 3. **保留并放行 `Location` 头,且其值是非 http(s) 的自定义 scheme `heicode://auth/callback?...`** —— 这是最容易被网关/策略拦的点,需确认 APIM 不会校验/重写/丢弃非 http scheme 的 Location。 4. 失败分支 landing 返回 **HTML**(人类可读"链接无效"页),APIM 需允许 `text/html` 响应透传。 > 若 APIM 经确认**无法**满足上述(尤其第 3 条非 http Location 透传),则按 §9.3 **回退方案2**(HM 托管落地页 + 我方 `landing-verify` JSON API)。但在此之前,**首选统一走 APIM 的方案1-APIM**。 **请 HM/APIM owner 回一句**:APIM 能否满足上述 4 条(重点第 2、3 条)。能 → D-4 即按本节定稿(`MAGIC_LINK_PUBLIC_BASE_URL` 取 APIM 域);同时仍需 D-5 邮件基建负责人到位,才进入实现并对外发信。 --- # 12. mcp-server 实现级代码验证(2026-06-09,开写前最终核验) > 在让 HM 端开始写之前,mcp-server 对**应用装配层**做了一遍核验,确认本契约在我方代码里**无实现级阻碍**,并据此**更正 §7.2-A**。核验文件:`app/application.py`、`app/routes/__init__.py`、`app/rate_limiter.py`。 ## 12.1 landing 返回 302/HTML —— 应用层确认可行 ✅ - 应用工厂**没有任何把响应包成 JSON envelope 的中间件**:`auth_middleware` 仅 `return await call_next(request)`,不改写响应体(`app/application.py:45-74`)。 - 全局 `RateLimitMiddleware` 只对 `principal.type == "api_key"` 的请求限流;magic-link 三端点不带 api_key,命中"非 api_key 直接放行"分支(`app/rate_limiter.py:155-163`),**既不限流也不改写响应**。 - 结论:`landing` 用 `RedirectResponse(302, headers={"Location": "heicode://auth/callback?..."})` 与失败 `HTMLResponse`,在我方 FastAPI 内**原生可行、无中间件干扰**。(`heicode://` 非 http scheme 在 FastAPI 侧不校验;唯一风险在 APIM 透传,见 §11,属网关配置。) ## 12.2 ⚠️ 更正 §7.2-A:无需改任何 allow_paths 核验发现现网鉴权 wiring 与 §7.2-A 的假设不同: - 路由统一用 `app.include_router(router)` 挂载,**不带** `dependencies=[Depends(require_auth)]`(`app/routes/__init__.py:69`)。 - `auth_middleware` 是**放行式**的:调 `authenticate_request` 取 principal,**取不到也不 401**——对 `/api`/`/agents` 路径设 `request.state.principal = {}` 后照常 `call_next`(`app/application.py:64-74`)。所以 `authenticate_request` 的 allow_paths(`app/auth.py:335-346`)**在当前 wiring 下并不 gate 访问**。 - 真正的鉴权门是**路由函数显式声明的** `Depends(require_auth)`;而 `login`(`app/routes/auth.py:96`)、`register`(`:721`)等公开接口**都没声明它**,因此公开。 - **更正结论**:magic-link 的 `request`/`landing`/`verify` 只要**不声明 `Depends(require_auth)`** 即为公开,与 `login`/`register` 完全一致;**不需要**改 `require_auth` 或 `authenticate_request` 的任何 allow_paths。→ **§7.4 第 2 条作废**(删除"两处 allow_paths 同步加入")。 ## 12.3 登录产物等价 —— 再确认 `verify` 复用 `create_access_token/create_refresh_token` + 与 `/login` 逐字段相同的 `token_data`,产物 = 现有登录(24h/7d、claims `sub/email/role/channelId/type/iat/exp`)。该响应体与 token 模型已在《Heicode-登录接口对接文档.md v1.0》§2.3/§8 标注"已上线生产、端到端验证通过",无悬念。 ## 12.4 开写前状态小结 - **mcp-server 应用代码侧:无阻碍。** request/verify/landing 可按 §2 契约实现;landing 走 §11 的 APIM 统一网关;实现清单见 §7.4(**第 2 条已按 §12.2 作废**)+ §9.4。 - **仅剩两条外部前置(不阻塞 HM 写客户端/代理与 APIM 配置,仅阻塞对外发信/go-live)**: 1. **APIM 透传**(§11 的 4 条,重点 302 + `heicode://` 非 http Location)—— APIM owner 确认/配置; 2. **邮件基建负责人/签字**(D-5)—— 产品/基建,HM 在工单 #19 追。 - 据此,**HM 端可按本契约开始编写**(客户端邮箱登录 UI + `heicode://` 注册转发 + 同源代理 request/verify + APIM landing 配置);mcp-server 端按 §7.4/§9.4 实现端点骨架(**默认不外发**,直到邮件基建就位)。契约以本文件 §0–§5 + §8(D-1/2/3) + §11(D-4=APIM) + §12(实现级更正) 为准。 --- # 13. HM owner @zsbgnw12 开工指令 + 待 mcp-server 推进项(2026-06-09) > 收到 §12 实现级核验。契约以「§0–§5 + §8(D-1/2/3) + §11(D-4=APIM) + §12(更正)」为准,**HM 无新增异议**。下面给 🟢 开工授权 + 明确还需 mcp-server 侧推进的两条前置。 ## 13.1 HM 侧已就绪(已核验 HM 代码,mcp 可直接依赖) - **同源代理 `/api/heicode-auth/*proxyPath`(`controller/heicode_auth_proxy.go`)= 通用透传**:原样转发 method / query / body / `Authorization` / `Content-Type` / `Accept`,回传上游 status + body,20s 超时。 - ⇒ `request`/`verify`(POST JSON)经 `https://code.xinghanlab.com/api/heicode-auth/api/auth/magic-link/{request,verify}` → APIM **HM 零改动即通**,客户端无跨域问题。 - `landing` 是浏览器 → APIM **直达,不经此代理**(与 §11 一致),所以 302/HTML 由 APIM→mcp 透传,不碰 HM。 ## 13.2 🟢 开工授权(骨架,默认不外发) 请 mcp-server **现在就开始**实现端点骨架,不必再等 HM: - 按 §7.4(**第 2 条已按 §12.2 作废**,无需改 allow_paths)+ §9.4:`request` / `verify` / `landing`(302+HTML) 三端点;`verify` 复用 `create_access_token/create_refresh_token` + 与 `/login` **逐字段相同**的 `token_data`;Redis one-time key(token 10min / code ≤2min);限流复用现有 `_LoginRateLimit` + 邮箱限流。 - 邮件模板(URL 形式)先建好,**默认 mock 邮件 / 打日志,不真实发信**,直到 D-5 邮件基建签字。 - `MAGIC_LINK_PUBLIC_BASE_URL = https://apimtaiji.azure-api.net/api/mcp`(§11 终态)。 ## 13.3 ⭐ 还需 mcp-server 侧推进的前置(你们网关侧,HM 无权配置) 1. **[最关键] APIM 透传确认/配置**(§11 的 4 条,重点第 2、3 条): - APIM 对 `GET /api/mcp/api/auth/magic-link/landing` 必须:放行该 GET(含 `token`/`state` query)、**原样透回后端 302(不 follow、不缓冲改写、不强制 JSON)**、**保留并放行非 http 的 `Location: heicode://auth/callback?...`**、失败分支允许 `text/html` 透传。 - `apimtaiji.azure-api.net` 是**你们(mcp/taiji 平台)侧网关**,请你们 APIM owner 配置/确认,**结论回写本文件**: - ✅ 能透传 `heicode://` 302 → **D-4 定 APIM**,HM 零改动。 - ❌ 不能透传非 http Location → **回退方案 2**(§9.3):你们新增 `POST /api/auth/magic-link/landing-verify`(JSON 校验 token→出一次性 code),**HM 托管落地页出 302**(HM 接受此兜底,确认即开始加这段小改动)。 - **请在本文件明确回一句:APIM 能否满足上述(尤其第 3 条 `heicode://` Location 透传)。** 2. **D-5 邮件基建负责人/签字**:SMTP 通道(`taijiagent@189.cn`)可用是好事;**模板文案 / 发件人 / 送达率 / 防滥用最终签字属产品**——**HM 负责在工单 #19 追产品/基建指认负责人**。这条是「真实对外发信」的前置,**不阻塞你们先上骨架(mock 发信)**。 ## 13.4 HM 侧并行推进(不阻塞你们) - 客户端 cc-haha:邮箱登录 UI + `heicode://auth/callback` 深链注册转发 + `state` 比对,对 mock 先行。 - 契约定稿(13.3 两条回齐)后,HM 在 heicodeDocs `integration/` 落**正式契约**,客户端切真实 path 联调。 ## 13.5 一句话状态 **契约已锁、HM 代理已通、🟢 开工骨架。** 卡点只剩两条、且都在你们/产品侧:① APIM 能否透传 `heicode://` 302(你们 APIM owner 回一句);② D-5 邮件基建签字(HM 在 #19 追)。这两条回齐即定稿对外。请把 ① 的结论回写本文件。 --- # 14. mcp-server 骨架已落地 + ① APIM 待办归属(2026-06-09) > 收到 §13 🟢 开工授权。按 §7.4(第 2 条已按 §12.2 作废)+ §9.4 + §11 + §13.2,mcp-server 端三端点**骨架已实现并通过语法/导入级核验**,默认 mock 发信、不外发。以下据真实代码逐条交代落地点,并按 §13.5 回写 ① 的当前结论。 ## 14.1 已落地(真实代码位置) - **新增独立支撑模块 `app/magic_link.py`**(与既有逻辑零耦合,**未改动**任何密码登录/注册验证码代码): - Redis 一次性凭证:`magic_link_token:{token}`(TTL 600s)、`magic_link_code:{code}`(TTL 120s),沿用现网验证码同款 `setex` 落地 + 命中即 `delete` + 集群 MOVED 重定向重试。 - 邮箱维度限流:**独立**键 `magic_link_rate_limit:{email}`(60s),不与注册/忘记密码共用的 `verification_rate_limit:{email}` 互相误伤。 - `send_magic_link_email()`:**复用** `app/email_verification.py` 的 SMTP 通道(`taijiagent@189.cn`);`SMTP_PASSWORD` 未注入时**不外发**,仅 DEBUG 下打印链接(= §13.2「默认 mock / 打日志」,待 D-5 签字后真实发信)。 - **`app/routes/auth.py` 新增三端点**(均**不声明 `Depends(require_auth)`** → 公开,与 `login`/`register` 一致,**未动 allow_paths**): - `POST /api/auth/magic-link/request`:IP 限流(独立 `_LoginRateLimit` 实例)+ 邮箱 60s 限流;防枚举——限流在查用户前打、对存在/不存在邮箱一视同仁;仅对**已存在的 `role=user`** 用户真发信(D-1/D-2/D-3),响应一律 `200 {request_id, state, expires_in_sec:600}`。 - `GET /api/auth/magic-link/landing?token=&state=`:一次性消费 token + 校验 state + 复核 user 仍存在且 `role=user` → 生成一次性 code → **`302 Location: heicode://auth/callback?code=...&state=...`**;任一校验失败返回人类可读 `HTMLResponse`(含「请在已安装 HeiCode 的同一台设备打开」提示)。 - `POST /api/auth/magic-link/verify`:一次性消费 code + 校验 state + `role=user` → 复用**同一个** `create_access_token/create_refresh_token` + 与 `/login` user 分支**逐字段相同**的 `token_data` → 返回 `{token, refreshToken, user{id,name,email,role,channelId}}`;审计走现有 `log_audit_event(action="auth.login", details={method:"magic_link"})`。→ **EU/计费零改动**(§1.3 / §12.3)。 - **`config.py` 新增** `magic_link_public_base_url`,缺省 `https://apimtaiji.azure-api.net/api/mcp`(§11 终态),由运维注入。 ## 14.2 ① APIM 透传 —— 当前结论:**OPEN,挂我方 APIM owner**(不伪造结论) 按 §13.3.1,`apimtaiji.azure-api.net` 是我方(mcp/taiji 平台)侧网关,需 APIM owner 配置/确认 §11 的 4 条,**重点第 2、3 条**:原样透回后端 302(不 follow / 不缓冲改写 / 不强制 JSON)、且**保留并放行非 http 的 `Location: heicode://auth/callback?...`**。 - 本条属网关运维配置,**应用代码无法判定**,截至本次回写**尚未拿到 APIM owner 的确认**,故 ① 仍为 **OPEN**,由我方 APIM owner 跟进后回写本节。 - 应对两种结论我方均已就绪: - ✅ APIM 能透传 `heicode://` 302 → **D-4 定 APIM**,邮件链接即 `https://apimtaiji.azure-api.net/api/mcp/api/auth/magic-link/landing?...`,HM 零改动。 - ❌ APIM 不能透传非 http Location → 我方按 §9.3 **回退方案 2**:新增 `POST /api/auth/magic-link/landing-verify`(JSON 校验 token→出一次性 code),由 HM 托管落地页出 302。该端点为增量小改,确认后即补。 ## 14.3 状态 - **骨架 DONE(mock 发信)**,不阻塞 HM 客户端/代理并行推进。 - **真实对外发信前置仍两条**:① APIM 透传(我方 APIM owner,OPEN);② D-5 邮件基建签字(产品/基建,HM 在 #19 追)。两条齐 → 切真实 path + 真实发信联调。 ## 14.4 已部署生产(2026-06-09) 三端点**已上线生产 AKS(`taiji-ai` 命名空间,镜像 `mcp-server:magic-link-20260609-arm64-v2`)**,集群内实测通过: - `GET /health` 200;`POST /api/auth/magic-link/request` → 200 `{request_id,state,expires_in_sec:600}`(不存在邮箱也成功,防枚举);`POST /api/auth/magic-link/verify`(坏 code)→ 401;`GET /api/auth/magic-link/landing`(坏 token)→ 400 `text/html`。 - 现有 `/api/auth/login` 等回归正常(未受影响)。 - **发信总闸 `MAGIC_LINK_EMAIL_ENABLED=false`(mock)**:即便生产 SMTP 可用也不外发,待 D-5 签字后由运维在 configmap 置 `true`。 - ⇒ **HM 可即刻就 `request`/`verify`(经 `code.xinghanlab.com/api/heicode-auth/...` → APIM)开始联调**(mock 模式:`request` 返回 200 但不发真信,链接需在 mcp-server pod 日志 DEBUG 下取)。 - **仅 `landing` 仍待 ① APIM 放行 `heicode://` 302 透传**——这条仍是 OPEN,待我方 APIM owner 确认后回写 §14.2。 --- # 15. mcp-server → HM:可开始 request/verify 联调 + 联调期发信怎么处理(2026-06-09) > @zsbgnw12 三端点已上生产(§14.4),按下面分工推进。 ## 15.1 HM 现在就能开始(不卡你们) - 客户端 cc-haha:邮箱登录 UI + `heicode://auth/callback` 深链注册转发 + `state` 比对 + 同源代理调 `request`/`verify`,对着**生产**端点跑: - `POST https://code.xinghanlab.com/api/heicode-auth/api/auth/magic-link/request` body `{email}` → 200 `{request_id, state, expires_in_sec:600}` - `POST https://code.xinghanlab.com/api/heicode-auth/api/auth/magic-link/verify` body `{code, state}` → 成功体与 `/login` 逐字段一致;坏 code → 401。 - 这两个端点经 APIM 已可达(与现有 4 个登录接口同源),HM 代理零改动。 ## 15.2 ① APIM 透传(landing 的 302)—— 我方在跟进 - `landing` 的 `heicode://` 302 能否经 APIM 透回浏览器,仍是 §14.2 的 OPEN 项,**属我方 APIM owner**,我们这边推进、结论回写 §14.2,**不需要 HM 动作**。在它落定前,"点邮件链接→回跳客户端"这一段端到端先跑不通;`request`/`verify` 的对接和回跳后的 `verify` 换 token 逻辑你们可以先用手造 code/state 联调。 ## 15.3 ⭐需要 HM 回一句:联调期怎么拿到链接? 当前发信总闸 `MAGIC_LINK_EMAIL_ENABLED=false`(mock,§14.4)——`request` 返回 200 但**不发真邮件**,联调时测试者拿不到 magic-link。两种走法,请 HM/产品定: - **(A) 联调期临时开真发信**:运维把 `MAGIC_LINK_EMAIL_ENABLED=true`,真发到测试邮箱。前提是产品同意在 D-5 正式签字前先用于**联调**(发件人仍 `taijiagent@189.cn`、模板见 §14.1)。 - **(B) 维持 mock,从后端取链接**:我方在联调期临时把 magic-link 日志级别调到可见,HM 测试者从我方提供的渠道拿链接。较绕,仅适合纯功能验证。 > 我方倾向 (A)(最接近真实链路)。请 HM 在 #19 跟产品确认「联调期可否先开发信」,回这一句即可。 ## 15.4 一句话 **request/verify 可即刻联调;landing 的 ① APIM 我方跟进;只差 HM 回 §15.3 一句(联调发信走 A 还是 B)。** --- # 16. HM owner @zsbgnw12:经 HM 生产代理实测三端点已通(2026-06-09) > 收到 §14/§15。HM 侧立即对**生产**做了端到端实测:客户端 → `https://code.xinghanlab.com/api/heicode-auth/...`(HM 同源代理)→ APIM → mcp-server 三端点。结果如下(真实返回)。 ## 16.1 实测结果(HM 代理 → APIM → mcp-server,均生产) - `POST /api/heicode-auth/api/auth/magic-link/request` body `{"email":"hm-integ-test@example.com"}`(**未注册邮箱**)→ **200** `{"success":true,"data":{"request_id":"e1134b6c-…","state":"42YbF-…","expires_in_sec":600}}`。✅ 防枚举生效(未注册也 200)、HM `{success,data}` 信封正常、`state` 已下发。 - `POST …/magic-link/verify` body `{"code":"bad…","state":"x"}` → **401** `{"detail":"登录凭证无效或已过期"}`。✅ 坏 code 正确拒绝。 - `GET …/magic-link/landing?token=bad&state=x` → **400 + `text/html`** 「链接无效」页。✅ - ⇒ **HM 同源代理对三端点全部可达、零改动**;`request`/`verify` **联调即刻可用**。 ## 16.2 对 ① APIM 透传的范围收窄(实测旁证) - 上面 landing 的**失败分支 HTML 已经成功经 APIM 透回浏览器**(拿到了 400 + `text/html`)。⇒ APIM 放行该 GET、且能透传 `text/html` 这两点**已被实测证明**。 - 所以 ① 现在**唯一未验证的只剩「成功路径的 `302 Location: heicode://auth/callback?...`」是否被 APIM 原样透回**(非 http scheme 的 Location)。请你方 APIM owner 重点只验这一条;其余(放行 GET / 透 HTML)实测已 OK。 ## 16.3 HM 回 §15.3(联调发信 A/B) - HM 也倾向 **(A) 联调期临时开真发信**(最接近真实链路)。但「D-5 正式签字前先开发信用于**联调**」需**产品点头**——**HM owner 正在 #19 跟产品确认**,确认后回写本节并请运维置 `MAGIC_LINK_EMAIL_ENABLED=true`(测试邮箱)。 - 在产品确认前的过渡:可先用 **(B)** 从 mcp-server pod 日志(DEBUG)取链接做纯功能验证,不阻塞 `request`/`verify` 对接与回跳后 `verify` 换 token 的逻辑联调。 ## 16.4 HM 并行推进 - 客户端 cc-haha:邮箱登录 UI + `heicode://auth/callback` 深链注册转发 + `state` 比对 + 同源代理调 `request`/`verify`,对**生产**端点先行(landing 成功回跳待 ① 落定)。 - D-5 邮件基建签字:HM 在 #19 追产品。 ## 16.5 一句话 **HM 代理→APIM→mcp 三端点实测全通,request/verify 联调已开跑;① 只剩「heicode:// 302 透传」一条待你方 APIM owner 验;§15.3 HM 倾向 A、待产品点头。** --- # 17. HM 提议:联合端到端「模拟真实」测试(一次性,不需真发信,顺带验掉 ①)(2026-06-09) > 目标:不等产品签字(D-5)、不真发信,**就把"邮箱→链接→landing→302→code→verify→登录产物"整条链跑通一遍**,并**用真实成功路径验掉 ① 的唯一悬念**(APIM 是否原样透回 `heicode://` 302)。 ## 17.1 为什么需要你们配合一步 magic-link `token` 按设计**只在邮件里 + 你们 Redis `magic_link_token:{token}`**;`request` 响应只回 `{request_id, state}`,**不回 token**。所以 HM 单方拿不到 token,走不到成功路径。**只要你们把一个有效 token 露出来一次,后面全程 HM 驱动。** ## 17.2 联合流程(用文档已有测试账号 `55@55.com`,role=user) 1. **HM 调** `POST .../magic-link/request {email:"55@55.com"}` → 记下返回的 `state`(token 同时进你们 Redis,TTL 600s)。 2. **请 mcp 立即**(10 分钟内)从 Redis 读出刚生成的 token(`KEYS magic_link_token:*` 或 mock 的 DEBUG 日志行),把 **token 值**回写本节(`55@55.com` 是公开测试账号,token 一次性、读后我马上消费)。 3. **HM 用真实 token 打成功路径**(经 APIM,`curl -i` 不跟随): `GET .../magic-link/landing?token=<你给的>&state=<步骤1的state>` → **期望 `302` + `Location: heicode://auth/callback?code=...&state=...`**。 - ✅ 拿到带 `heicode://` 的 302 → **① 当场验掉**(APIM 能原样透回非 http Location),D-4 定 APIM,无需回退方案2。 - ❌ 302 被 APIM 吞/改写/Location 丢失 → **① 证伪**,按 §9.3 回退方案2(HM 托管落地页)。 4. **HM 再打** `POST .../magic-link/verify {code:<上一步302里的code>, state}` → **期望 `200` + `{token, refreshToken, user{id,name,email,role,channelId}}`**,且与 `/api/auth/login` 逐字段一致 → **登录产物等价 + EU/计费零改动 当场坐实**。 ## 17.3 备选(更"真实",但需产品点头) 若产品同意 §15.3-A:运维把 `MAGIC_LINK_EMAIL_ENABLED=true` 指向一个**测试邮箱**,则连"收真信→点链接"都真实跑;否则用 17.2 的"露 token"即可完成功能与 ① 验证。 > **请 mcp 配合 17.2 第 2 步(露一个 token)**,或告知更方便的露出方式。HM 这边随时可发起步骤 1、并在拿到 token 后立即跑 3/4 把结果(尤其 ① 的 302)回写本文件。 ## 17.4 ⏱ HM 已发起步骤1(实时,2026-06-09) HM 刚对 `55@55.com` 调了 `request`,生产返回: - `request_id` = `9b93f06d-a2de-4ee5-8929-6c52708e3387` - `state` = `c2IJAScWGyMhl5FYrprprA` - `expires_in_sec` = 600(**约 10 分钟内有效**) → **请 mcp 立即从 Redis 读出对应 token**(`KEYS magic_link_token:*` 取最新一个,或 mock DEBUG 日志里 `55@55.com` 那条链接里的 token),把 **token 值回写到本节下方**。HM 拿到后立即用 `state=c2IJAScWGyMhl5FYrprprA` 跑 `landing`(`curl -i`)验 ① 的 `heicode://` 302,再 `verify` 验登录产物。**若超过 10 分钟,HM 会重发并更新本节的 request_id/state。** mcp 回填 token:`uoJTHO7nbM4tGNI-mTe58OWkVQjmMEwjcXc4gOqL7ZU`(已消费,见 §18 结果) --- # 18. HM owner @zsbgnw12:联合「模拟真实」端到端测试 **全链路通过**(2026-06-09,真实返回) > 用 §17.2 流程 + mcp 露出的 token(`uoJTHO7n…`)对 **生产** 完整跑了一遍 magic-link 链路。**① 当场验掉,登录产物等价当场坐实。** 全部真实返回如下。 ## 18.1 步骤3 — landing 成功路径(经 APIM,`curl -i`)→ ✅ **① 透传通过** ``` GET https://apimtaiji.azure-api.net/api/mcp/api/auth/magic-link/landing?token=uoJTHO7n…&state=c2IJAScWGyMhl5FYrprprA → HTTP/1.1 302 Found Location: heicode://auth/callback?code=7MPE4UZR7AYwhiDC4ghvhpzWKfGy3nBR&state=c2IJAScWGyMhl5FYrprprA X-Frame-Options: DENY / X-Content-Type-Options: nosniff ``` **结论:APIM 原样透回了非 http 的 `Location: heicode://...`,302 未被跟随/改写/吞掉。① 的唯一悬念解决 → D-4 定 `方案1-APIM`,无需回退方案2。** state 原样带回、code 一次性。 ## 18.2 步骤4 — verify(经 HM 同源代理)→ ✅ **登录产物等价** ``` POST https://code.xinghanlab.com/api/heicode-auth/api/auth/magic-link/verify {code:7MPE4U…, state:c2IJAS…} → 200 {success:true, data:{token, refreshToken, user}} token claims: sub=b00a7b8e-… email=55@55.com role=user channelId=6e6fc470-… type=access exp-iat = 86400s(24h)✅ refresh claims: type=refresh, exp-iat = 604800s(7天)✅ user: {id:b00a7b8e-…, name:"55", email:"55@55.com", role:"user", channelId:"6e6fc470-…"} ``` **与 `/api/auth/login`(《登录接口对接文档》§2.3/§3.1)的信封、JWT claims、TTL 逐字段一致** → 不引入新会话/新计费分支,**EU/计费零改动**坐实(§1.3/§12.3 验证完成)。 ## 18.3 整链状态 `request → (token) → landing 302 heicode:// → verify → 登录产物` **生产全通**。功能层面 **magic-link 后端 + HM 代理 + APIM 透传 + 登录等价 = 全部验证通过**。 ## 18.4 真正剩余(仅这两条,与功能无关) 1. **真实发信(D-5)**:现 `MAGIC_LINK_EMAIL_ENABLED=false`(本次用露 token 绕过)。真发信需产品对邮件基建签字 → 运维置 true。 2. **客户端 cc-haha**:邮箱登录 UI + `heicode://auth/callback` 深链注册转发 + state 比对 + 调 request/verify(HM 侧客户端代码,待实现)。 > **一句话:模拟真实测试整条链生产跑通,① 已验掉,契约 D-4 定 APIM。只差"真发信签字 + 客户端 UI"两件非功能项。** --- # 19. HM owner @zsbgnw12 — #19 收口状态 + 仅剩 D-5 待 mcp-server 推进(2026-06-10) > HM 侧把工单 #19(magic-link 邮箱登录)列为「等 mcp-server + 邮件基建」。功能链路(§18)已生产验证全通,**无契约/功能阻塞**。盘点后 mcp-server 侧只剩 **D-5 真实发信** 一件。 ## 19.1 现状(已确认,无需再动) - request → landing 302 `heicode://auth/callback` → verify 登录产物,**生产全链路验证通过**(§18.1–18.3)。 - 登录产物与 `/api/auth/login` **逐字段等价**(JWT claims / TTL / 信封)→ **EU/计费零改动**。 - 契约 **D-4 = APIM 方案1**(§16/§18.1 已验)。密码登录并存、不受影响(§1)。 ## 19.2 ⭐ 还需 mcp-server 推进/回复(仅 D-5,**#19 卡点**) 当前 `MAGIC_LINK_EMAIL_ENABLED=false`(测试期靠 response 露 token 绕过)。要真正发链接给用户,需 mcp-server 把发信打通。请在本节下方回: 1. **邮件/通知基础设施 owner 是谁**(负责账号/域名/配额/合规)? 2. **走哪家发信**(SMTP / SES / SendGrid / 阿里云邮件…)?凭据走 **secret_ref / 密钥保管库,不进代码/日志/Markdown**。 3. flip `MAGIC_LINK_EMAIL_ENABLED=true` 的**前置清单**(发信域名验证 / 退信处理 / 按 email 频率限制 / 链接 TTL 复核)。 4. 确认**除 D-5 外 mcp-server 侧无其它阻塞**(三端点 + landing + verify 已冻结且生产可用)。 ## 19.3 HM 侧并行(不阻塞你们) - 客户端 cc-haha:邮箱登录 UI + `heicode://auth/callback` 深链转发 + `state` 比对 + 调 request/verify(HM/客户端侧代码,自推)。 ## 19.4 请回复 mcp-server 在本节下方回 **§19.2 的 1–4**(尤其邮件基建 owner + 前置清单),HM 据此推进 #19 收尾;真发信开关由对应 owner 在前置满足后置 true。 --- # 20. mcp-server 回复 §19.2(D-5 发信收口,2026-06-10) > 逐条回 §19.2 的 1–4。**发信通道经我方拍板:就用现有 189.cn SMTP,送达率/企业用户收不到的问题暂不考虑**(作为已知接受风险记录在案,见 Q2/Q3)。 ## Q1 邮件基建 owner - **暂无独立的"邮件基建 owner"**;过渡期由 **mcp-server 运维**以现有 189.cn 通道承载发信。正式 owner / 合规签字若后续要正式运营再由产品指认——但**不阻塞本次开闸**(已接受 189.cn 现状,见 Q2)。 ## Q2 走哪家发信 - **维持现有 189.cn SMTP**:`smtp.189.cn:465 (SSL)`、发件 `taijiagent@189.cn`,**复用** `app/email_verification.py` 的发信通道(magic-link 不另起炉灶)。凭据 `SMTP_PASSWORD` 走 k8s secret `taiji-secrets` 的 `secretKeyRef` 注入——**不进代码 / 日志 / Markdown**(符合 §1.4 与组织密钥规则)。 - ⚠️ **已知接受风险(经决策)**:189.cn 为运营商邮箱,**对企业/海外用户的送达率、进垃圾箱风险本期不处理**;无 SPF/DKIM/DMARC 域名背书、无退信(bounce)回收。本期仅满足"能发出链接"。后续若要正式运营再评估换 SES/SendGrid。 ## Q3 flip `MAGIC_LINK_EMAIL_ENABLED=true` 前置清单 | 项 | 状态 | |---|---| | 按 email 频率限制(60s/邮箱,独立键) | ✅ 已实现 `app/magic_link.py` `check_email_rate_limit` | | 链接 TTL:token 600s / code 120s / 一次性 | ✅ 已实现 | | 防枚举(限流前置、存在/不存在一视同仁) | ✅ 已实现 | | SMTP 凭据 `SMTP_PASSWORD` 注入 | ✅ 生产已注入(secret `taiji-secrets`) | | 发信域名验证(SPF/DKIM/DMARC)/ 送达率 | ⏸ **本期不做**(Q2 接受风险) | | 退信(bounce)处理 | ⏸ **本期不做**(Q2 接受风险) | - **开闸动作**(满足前置后由运维执行):确认 189.cn 的 `SMTP_PASSWORD` 有效 → 把 `k8s/prod/configmap.yaml` 的 `MAGIC_LINK_EMAIL_ENABLED` 置 `true` → 滚动重启 mcp-server 生效。`request` 即对**已存在的 role=user** 邮箱真发链接。 ## Q4 除 D-5 外有无其它阻塞 - ✅ **无功能/契约阻塞**。三端点 + landing + verify 已冻结、生产可用、登录产物等价已生产验证(§18,且 token 被成功路径一次性消费已独立核实)。契约 D-4=APIM 定稿。 - 备注(非阻塞,我方收尾项):§16–§20 文档 + 两个 k8s yaml(`MAGIC_LINK_EMAIL_ENABLED` 注入 + 镜像 tag 升 `arm64-v2`)将提交并推到 Gitea `feature/chenchen`,使远端与"已部署 arm64-v2 + 开闸配置"对齐。 ## 一句话 **发信定 189.cn(送达率本期不考虑、已接受);开闸前置除"运维置 `MAGIC_LINK_EMAIL_ENABLED=true`"外均已就绪;mcp-server 侧无其它阻塞。** HM 可据此在 #19 收尾,真发信由运维按上表开闸。 --- # 21. HM owner @zsbgnw12 — 确认收到 §20,#19 收口(2026-06-10) 收到 §20。**确认 mcp-server 侧无功能/契约阻塞**;契约 D-4=APIM、登录产物等价、三端点 + landing + verify 生产可用,均已在 §16/§18 共同验证。#19 在 mcp-server 侧视为完成,余下仅两类: ## 21.1 各方剩余(非 mcp-server 阻塞) - **运维**:满足 §20 Q3 表后,置 `k8s/prod/configmap.yaml` 的 `MAGIC_LINK_EMAIL_ENABLED=true` + 滚动重启即开闸。开闸动作归运维。 - **HM/客户端**:cc-haha 邮箱登录 UI + `heicode://auth/callback` 深链转发 + `state` 比对 + 调 request/verify(HM 侧自推,不依赖 mcp-server)。 ## 21.2 ⚠️ 需产品/owner 知悉的一个风险(非工程项) mcp-server 已**接受 189.cn 的送达率风险**(企业/海外可能收不到或进垃圾箱、无 SPF/DKIM/DMARC、无退信回收)作为本期已知风险。这属**产品/运营风险接受**,工程侧据此实现没问题;是否以此口径对外开闸,请产品 owner 知悉确认。后续要正式运营,按 §20 Q2 再评估换 SES/SendGrid + 域名背书。 ## 21.3 状态 magic-link **后端 + 代理 + APIM 透传 + 登录等价 = 全通**;**开闸=运维一个配置**;**客户端 UI=HM 侧自推**。本对接(mcp-server 侧)收口,感谢配合。 --- # 22. 产品/owner 拍板:接受 189.cn 风险 → 开闸放行(2026-06-10) **owner 已确认接受** §20 Q2/§21.2 的 189.cn 已知送达率风险(企业/海外送达、垃圾箱、无 SPF/DKIM/DMARC、无退信),作为本期已知风险记录在案。 → **开闸放行**:满足 §20 Q3 前置(均已就绪)后,**mcp-server 运维**可执行开闸:确认 189.cn `SMTP_PASSWORD` 有效 → `k8s/prod/configmap.yaml` 置 `MAGIC_LINK_EMAIL_ENABLED=true` → 滚动重启。开闸后 `request` 即对已存在 `role=user` 邮箱真发链接。 > 后续若要正式运营再按 §20 Q2 评估换 SES/SendGrid + 域名背书。开闸动作归 mcp-server 运维(其 k8s/prod);HM 侧并行推进客户端 cc-haha 邮箱登录 UI。本对接收口。 --- # 23. ⚠️ magic-link 邮件落地链接的**域名**要改(HM owner @zsbgnw12,2026-06-12,对应 HM #74) ## 23.1 现象 / 问题 客户端 magic-link 邮件里的**落地/verify 链接 base 落在 `apimtaiji.azure-api.net`**(例:`https://apimtaiji.azure-api.net/api/mcp/api/auth/magic-link/landing?token=...`)。这是 mcp-server 用**自身网关域名**拼的绝对 URL。问题: - 与客户端实际登录域名不一致(客户端 request/verify 走的是 HM 反代的登录域名,不是 apimtaiji),off-brand/钓鱼观感; - apimtaiji 曾是设备配对 404 的历史问题网关,脆弱; - 不符合"只登客户端登录域名"的口径。 ## 23.2 当前/未来的客户端登录域名(请据此拼落地链接) HM 已迁到 AKS,生产域名: - **`code.heicode.cc`** → App GW → HM(控制台 + `/api` + `/v1`)。**客户端登录走 `https://code.heicode.cc/api/heicode-auth/*`**,HM 反代到 mcp-server。 - `api.heicode.cc` → APIM → HM API(程序化访问,非登录落地)。 - (并行期客户端仍可能用旧域名 `https://code.xinghanlab.com/api/heicode-auth/*`。) ## 23.3 ✅ 推荐改法(域名无关,一次到位) **magic-link 邮件里的落地/verify 链接,不要硬编码 apimtaiji.azure-api.net;改为按 HM 反代透传的转发头拼绝对 URL:** - `X-Forwarded-Host`(= 客户端访问的域名,如 `code.heicode.cc`) - `X-Forwarded-Proto`(= `https`) - `X-Forwarded-Prefix`(= `/api/heicode-auth`,HM 反代前缀) → 落地链接拼成 `https://{X-Forwarded-Host}{X-Forwarded-Prefix}/api/auth/magic-link/landing?...`,即 `https://code.heicode.cc/api/heicode-auth/api/auth/magic-link/landing`(经 HM 反代可达、与 verify 同源)。**这样无论客户端用 code.xinghanlab.com 还是 code.heicode.cc 都自动正确,mcp-server 不用硬编码任何域名。** **HM 侧已实现透传**:`HeicodeAuthProxy` 在 `/api/heicode-auth/*` 已带上述三个头(HM PR#75 已合并 main)。 ## 23.4 兜底(若 mcp-server 不便读转发头) 则把 magic-link 落地 public base 配成客户端登录域名常量: - 迁 AKS 后:`https://code.heicode.cc/api/heicode-auth` - 并行期/旧:`https://code.xinghanlab.com/api/heicode-auth` ## 23.5 影响范围 仅 magic-link 邮件里**落地/verify 链接的 base 拼接**;**不改登录产物 `{token,refreshToken,user}`、不改 JWT claims、不改任何接口契约**。请 mcp-server 确认采用 23.3(推荐)还是 23.4,并排期。@mcp-server --- # 24. mcp-server 回复 §23:采用 23.4(`code.heicode.cc`)+ 已部署生产(2026-06-12) > 选 **23.4**(域名常量),**已部署生产验证通过**。未采纳 23.3 的原因是安全考量(见下)。本改动仅落地链接 base,**不动任何接口/JWT/登录产物**(与 §23.5 一致)。 ## 24.1 已落地(真实生产值) - `k8s/prod/configmap.yaml` 的 `MAGIC_LINK_PUBLIC_BASE_URL`:`https://apimtaiji.azure-api.net/api/mcp` → **`https://code.heicode.cc/api/heicode-auth`**,已 `apply` + 滚动重启(3 pod 全新,env 已生效)。 - 拼出的落地链接(生产 pod 实测)= **`https://code.heicode.cc/api/heicode-auth/api/auth/magic-link/landing?token=...&state=...`**,与 §23.3 期望一致、与 verify 同源。 - 代码无改动([auth.py:1411-1412](services/mcp-server/app/routes/auth.py:1411) 的 `base + /api/auth/magic-link/landing` 拼接逻辑不变,仅换 base 值)。 ## 24.2 为何没选 23.3(转发头动态拼)——安全 `/api/auth/magic-link/request` 经 APIM 也可**直达**(不只走 HM 反代)。若**裸读 `X-Forwarded-Host`** 拼落地域名,攻击者可伪造该头 + 填某真实用户邮箱 → 该用户收到的 magic-link 邮件**落地域名指向攻击者站**(钓鱼/窃 code)。所以 23.3 要做必须**配域名白名单**(只认 `code.heicode.cc`/`code.xinghanlab.com`,否则回退常量)。本期先用更稳的 23.4 常量;**若后续要 23.3 的"双域名自动正确",我方按"白名单版 23.3"另行排期 + 安全评审**(改鉴权路径,先评审)。 ## 24.3 ⚠️ 请 HM 确认一条(归你方反代) 落地链接改走 `code.heicode.cc/api/heicode-auth` 后,用户点链接 = **浏览器 → HM 反代 → mcp landing → 302 `heicode://`**。此前只验证过:① `apimtaiji` 直连的成功 302(§18.1)、② HM 反代的**失败分支** HTML(§16.1)。**"成功路径的 `302 Location: heicode://...` 经 `code.heicode.cc` HM 反代是否被原样透回浏览器"尚未实测**——这属 HM 反代行为,请你方按 §11 的 4 条(重点保留非 http 的 `Location`、不 follow/不改写)在 `code.heicode.cc/api/heicode-auth` 上验一次。mcp 侧 landing 返回 302 的行为不变(§18.1 已证)。 ## 24.4 状态 落地域名改 `code.heicode.cc` **已上生产**;`request/verify/landing` 契约不变;只待 HM 验 §24.3 的"成功 302 经新反代透传"。 --- # 25. HM owner @zsbgnw12 — 验 §24.3:**查出 bug 并已修**(2026-06-12) §24.2 的安全判断同意:`/request` 可经 APIM 直达,裸读 `X-Forwarded-Host` 拼落地域名会被伪造钓鱼;你们选 23.4 域名常量更稳,**采纳**。(HM 侧 §23.3 的 X-Forwarded 透传保留无害——你们用常量、不读它;若将来要 23.3 自动双域名,确实需"白名单版",再排期。) ## 25.1 §24.3 验证结果:HM 反代之前**不能**原样透回 302(已修) 按你们提示验了 `code.heicode.cc/api/heicode-auth` 的成功 302 透传,**查出两个 bug**: 1. `HeicodeAuthProxy` 的 `http.Client` 用 Go 默认 `CheckRedirect`(**自动 follow**)→ 收到 `302 Location: heicode://...` 会去 follow `heicode://`(非 http scheme)→ 失败 → 反代回 `502 BadGateway`(深链根本到不了浏览器)。 2. 反代只回传 `Content-Type`,**未回传 `Location`** → 即便不 follow,浏览器也拿不到深链。 → 即:**§24.3 担心的"成功 302 经反代透回"在修之前是断的。** 已修复(HM PR #78): - `CheckRedirect=ErrUseLastResponse`:不 follow,把上游 302 **原样**返回浏览器; - 透回 `Location`(深链)+ `Set-Cookie`(会话)+ `Cache-Control` + `Content-Type`; - 与 §11 4 条一致;非重定向(/me、/refresh、/login、verify 的 JSON)行为不变。单测覆盖:`heicode://` Location 原样透回 + JSON 透传。 ## 25.2 待办(HM 侧) PR #78 待评审合并 + **部署到 AKS 生产 HM** 后,`code.heicode.cc/api/heicode-auth` 上的 landing 成功 302→`heicode://` 才会真正透回浏览器。**部署前**,经 code.heicode.cc 点 magic-link 邮件链接的成功跳转会拿到 502(失败分支 HTML 不受影响)。我会尽快合并 + 部署,完成后在此 §25.3 标注。mcp 侧无需再改。