64 KiB
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. 硬约束(最重要,先读)
- 绝不改动、绝不影响现有密码登录链路:
POST /api/auth/login、GET /api/auth/me、POST /api/auth/refresh、POST /api/auth/logout行为、字段、JWT 一律不变。magic-link 是并行新增的获取 token 的方式,密码登录继续作为兼容/降级路径保留。 - magic-link 登录的"账号"与密码登录是同一套用户:同一邮箱无论用密码还是 magic-link 登录,解析到的都是同一个 user 记录(同一
id、channelId、role)。 - 登录产物必须等价:
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 不得引入新的会话类型或新的计费分支。
- 不得在日志/URL 持久留存敏感凭证:magic-link token、一次性 code 不进普通日志、不在 URL query 长期留存(仅一次性回跳)。
2. 需要 mcp-server 新增的内容(3 个端点 + 发邮件 + 落地页)
路径前缀建议放在现有 auth 命名空间下(如
/api/auth/magic-link/*),最终 path 由 mcp 定;定稿后 HM 会在 heicodeDocsintegration/锁定契约,客户端据此切真。客户端经 HM 同源代理/api/heicode-auth/*访问这些端点(无需关心跨域)。
2.1 POST /api/auth/magic-link/request — 申请登录链接
请求
{ "email": "user@example.com" }
行为
- 校验邮箱格式;查用户(同密码登录的用户库)。
- 生成**一次性、短 TTL(建议 10 分钟)**的 magic-link token,与该邮箱 + 一个随机
state绑定。 - 发邮件,链接指向落地页(§2.2),链接携带
token+state。 - 防枚举:无论邮箱是否注册,统一返回成功;配合限流(对齐现有登录 5 次/分钟/IP 量级)。
成功响应 200
{ "success": true, "data": { "request_id": "<uuid>", "state": "<random>", "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=<one-time-code>&state=<state> - 校验失败/过期/已用 → 返回一个人类可读 HTML 页(如「链接无效或已过期,请回客户端重新获取」)。
- Phase 1 跨设备提示(重要):落地页文案明确写「请在已安装 HeiCode 的同一台设备上打开此链接」。因为深链
heicode://只能唤起本机客户端;若用户在手机/另一台机器打开,本机客户端收不到回跳。Phase 2 再考虑轮询兜底(见 §4)。
2.3 POST /api/auth/magic-link/verify — 用 code 换登录态
请求
{ "code": "<one-time-code>", "state": "<state>", "device_pubkey": "<可选>" }
行为
- 校验
code:存在、未过期(≤2min)、未被用过(一次性)、与state一致。 - 通过 → 签发与
/api/auth/login完全相同的登录产物(见 §1.3)。 device_pubkey可选:mcp 可忽略(设备配对由 HM 侧完成,见 §3);若 mcp 想绑定设备也可记录,但不得改变返回的 token 结构。
成功响应 200(必须与 login 一致)
{
"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/<path>→ 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 确认/拍板 + 开放问题
- 最终 path 前缀:
/api/auth/magic-link/{request,landing,verify}是否 OK?定了我在 heicodeDocs 锁契约。 - 邮件基础设施:mcp 当前是否具备发信能力(SMTP/SES/SendGrid 等)?magic-link 邮件的发送、模板、送达率、防滥用由 mcp 侧承载——这是上线前置,请确认负责人/通道。(HM owner 已在 #19 标注"邮件基建负责人待产品/基建指认"。)
- token / code TTL:magic-link token 10min、code ≤2min、均一次性 —— 是否可行?
- device_pubkey:你倾向忽略(交给 HM 配对)还是要绑定?(不影响 token 结构即可)
- 跨设备:Phase 1 用落地页"同设备"提示即可(无需轮询);若你们愿意提供
GET /api/auth/magic-link/status?request_id=(返回 pending/confirmed)作 Phase 2 兜底,客户端可轮询,但非必须。 - 隐私/合规:本流程仅"邮箱 → 一次性 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 代码(全仓 grepmagic无命中),属全新增。
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_tokenTTL =settings.jwt_expire_minutes= 1440(24h)(app/auth.py:45+config.py:95),追加 claimsexp/iat/type="access",其余 claims 来自传入token_data(sub/email/role/channelId)。create_refresh_tokenTTL = 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 拍板后执行;改鉴权链路属高风险,先出方案再写码)
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。app/auth.py:两处allow_paths同步加入request/landing/verify(§7.2-A)。app/email_verification.py:新增 magic-link 邮件模板与发送函数(URL 形式)。- Redis:
magic_link_token:{token}(10min)、magic_link_code:{code}(≤2min) 两类 one-time key,复用现有 setex/delete。 config.py:新增MAGIC_LINK_PUBLIC_BASE_URL(§7.2-B),由运维注入;并在 k8s configmap 增配。- 全程不触碰
/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。
- (方案1,推荐) landing 在你们公网域(
[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:
- 我方生产有两个公网入口:
- nginx Ingress
mcp.taiji-ai.com→ 直连mcp-server:8000(k8s/prod/ingress.yaml:35,39-45),纯反代透传,无 JSON 限制,原生支持 302 + HTML; - APIM
https://apimtaiji.azure-api.net/api/mcp—— 现有客户端 JSON 登录走它(《Heicode-登录接口对接文档.md》§2.1)。
- nginx Ingress
- 结论:
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": "<magic-link token>", "state": "<state>" } - 行为:校验 token(存在/未过期/未用,一次性消费)+ 校验 user
role==user(D-3)→ 通过则生成一次性code(TTL ≤2min,绑定 user+state)。 - 成功 200:
{ "success": true, "data": { "code": "<one-time-code>", "state": "<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-verifyJSON API,HM 收浏览器 GET→调它→302 到heicode://,失败渲染 HTML;HM 保证一次性 code 不落普通日志)。
- 已启用/可启用 HTTPS → 直接方案1(
开始实现的前置(对应"两方确认无问题才开搞")
双方技术设计已对齐。还差两条确认才进入实现:
- 落地页 HTTPS 路径(你们运维):确认
mcp.taiji-ai.comHTTPS 可用 → 定方案1;否则告知,我按方案1-APIM 或方案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 必须满足——否则浏览器拿不到回跳:
- 放行该 GET
/api/mcp/api/auth/magic-link/landing(含token/statequery 透传)。 - 原样返回后端的 302:不得 follow-redirect(APIM 不能代为跟随,必须把 302 透回浏览器)、不得缓冲改写、不得强制 JSON。
- 保留并放行
Location头,且其值是非 http(s) 的自定义 schemeheicode://auth/callback?...—— 这是最容易被网关/策略拦的点,需确认 APIM 不会校验/重写/丢弃非 http scheme 的 Location。 - 失败分支 landing 返回 HTML(人类可读"链接无效"页),APIM 需允许
text/html响应透传。
若 APIM 经确认无法满足上述(尤其第 3 条非 http Location 透传),则按 §9.3 回退方案2(HM 托管落地页 + 我方
landing-verifyJSON 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):
- APIM 透传(§11 的 4 条,重点 302 +
heicode://非 http Location)—— APIM owner 确认/配置; - 邮件基建负责人/签字(D-5)—— 产品/基建,HM 在工单 #19 追。
- APIM 透传(§11 的 4 条,重点 302 +
- 据此,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 无权配置)
- [最关键] APIM 透传确认/配置(§11 的 4 条,重点第 2、3 条):
- APIM 对
GET /api/mcp/api/auth/magic-link/landing必须:放行该 GET(含token/statequery)、原样透回后端 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 透传)。
- APIM 对
- 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 签字后真实发信)。
- Redis 一次性凭证:
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+ 与/loginuser 分支逐字段相同的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。该端点为增量小改,确认后即补。
- ✅ APIM 能透传
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 /health200;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)→ 400text/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/requestbody{email}→ 200{request_id, state, expires_in_sec:600}POST https://code.xinghanlab.com/api/heicode-auth/api/auth/magic-link/verifybody{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/requestbody{"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/verifybody{"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)
- HM 调
POST .../magic-link/request {email:"55@55.com"}→ 记下返回的state(token 同时进你们 Redis,TTL 600s)。 - 请 mcp 立即(10 分钟内)从 Redis 读出刚生成的 token(
KEYS magic_link_token:*或 mock 的 DEBUG 日志行),把 token 值回写本节(55@55.com是公开测试账号,token 一次性、读后我马上消费)。 - 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 托管落地页)。
- ✅ 拿到带
- 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-6c52708e3387state=c2IJAScWGyMhl5FYrprprAexpires_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 真正剩余(仅这两条,与功能无关)
- 真实发信(D-5):现
MAGIC_LINK_EMAIL_ENABLED=false(本次用露 token 绕过)。真发信需产品对邮件基建签字 → 运维置 true。 - 客户端 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 把发信打通。请在本节下方回:
- 邮件/通知基础设施 owner 是谁(负责账号/域名/配额/合规)?
- 走哪家发信(SMTP / SES / SendGrid / 阿里云邮件…)?凭据走 secret_ref / 密钥保管库,不进代码/日志/Markdown。
- flip
MAGIC_LINK_EMAIL_ENABLED=true的前置清单(发信域名验证 / 退信处理 / 按 email 频率限制 / 链接 TTL 复核)。 - 确认除 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 secrettaiji-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)将提交并推到 Giteafeature/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 的
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 经新反代透传"。