Repository navigation
plugin-auth: POST /two-factor/verify-totp on the enrolment lane echoes user.twoFactorEnabled: false after the flag has flipped — the vendor's pre-rotation snapshot, which two-factor-rotated-token-echo repairs for token only #16535
Description
Activity
- addedbugSomething isn't workingSomething isn't working
on Sep 8, 2026 分诊:
domain:services/bug/p2/pm:queue/ typeBug车道(读代码判定,不猜标题):修复面是
packages/plugins/plugin-auth/src/two-factor-rotated-token-echo.ts——packages/plugins/plugin-auth在车道表domain:services行(plugin-auth)。⛔ 不是domain:engine:这张卡不碰 objectql/metadata,只碰 auth 插件的 after-hook。准入类别 (a) 可复现缺陷 —— 卡面自带两次驱动(in-memory engine + 真实
SqlDriver经真实ObjectStackClient),且带差分对照:同一次会话里verify-backup-code(不轮转)回显twoFactorEnabled: true,证明 flag 确实已翻,回显的false是 pre-rotation 快照而非"还没写"。有对照的读数才是读数。我独立复核了"修复点已在位、且只修了
token"这一条(origin/main5e53d73d):target : grep -c 'twoFactorEnabled|payload.user|\.user\b' two-factor-rotated-token-echo.ts -> 0 positive: grep -c 'token' two-factor-rotated-token-echo.ts -> 21 control : grep -rln 'twoFactorEnabled' plugin-auth/src/ -> auth-schema-config.ts, auth-manager.ts, managed-extension-fields.test.ts, objectql-adapter.test.ts, auth-manager.test.ts对照答话(该词在本包里确实是这个拼法、确实存在),所以 target 的 0 是真零而不是拼错。
echoInstalledSessionToken()命中ROTATING_TWO_FACTOR_VERIFY_PATHS后只改payload.token,user原样透出 —— 卡面主张成立,且修复条件已经在文件里算好了("staged 了 session cookie 且其 token ≠ 回显 token"就是同一个判据),扩到user不需要新的判定逻辑。p2 不是 p1 的理由:服务端状态是对的 —— flag 真的翻了,下一次
get-session就正确。坏的只有这一次成功响应的回显,客户端有一次额外请求的规避路径。所以不是"无法绕过的功能不可用",但它落在安全相关字段的成功路径上,会让刚开启 2FA 的用户看到"仍关闭",缓存了回显 user 的 bearer 客户端还会一直带错标志 —— 高于 p3。验收口径(承接 PR 请照抄进
## 验收备注):- 同一次
verify-totp成功响应里,user.twoFactorEnabled与 adapter 里该行的真实值必须一致;断言要读行、不要只断言字面量true。 verify-otp走同一份 vendor 轮转块(文件头注释已声明),修token时它就在同一个路径表里 —— 修user也必须覆盖它,⛔ 不留"已知同形的洞"。verify-backup-code(不轮转)必须不受影响:它今天就回显活行,回归后仍须回显活行。这条是防止用"无条件重读"覆盖掉本来正确的路径。- 承袭原文件的失败姿态:整理响应体失败 ⛔ 不得把已经成功的验证变成失败(
echoInstalledSessionToken的catch契约)。
本席权限声明:分诊席只分类/定级/定车道。⛔ 不认领、⛔ 不派发、⛔ 不写代码、⛔ 不合并、⛔ 不裁决决策箱卡。此卡不入决策箱:它既不迁移存量数据形状、也不删除已发布能力,且方向唯一(回显与行对齐),没有需要维护者裁的岔路。
Generated by Claude Code
- 同一次
更正:上一条评论里"origin/main
5e53d73d"这句是错的(结论不变)我上一条把复核标称为在
origin/main 5e53d73d上做的。实际上那三条 grep 跑在工作区检出上,而该检出落后 origin/main 584 个提交(HEAD 27875fc7,git rev-list --count HEAD..origin/main= 584)。落后的树上读出来的数字,不能挂 origin/main 的名字。已在
origin/main上重跑同一组(这次用git grep <pat> origin/main -- <path>,读的是提交对象而不是磁盘):file : two-factor-rotated-token-echo.ts + .test.ts 都在 origin/main 上 target : git grep -c twoFactorEnabled origin/main -- two-factor-rotated-token-echo.ts -> 0 positive: git grep -c token origin/main -- two-factor-rotated-token-echo.ts -> 21 control : git grep -l twoFactorEnabled origin/main -- plugin-auth/src/ -> auth-manager.ts, auth-schema-config.ts, managed-extension-fields.test.ts, objectql-adapter.test.ts, auth-manager.test.ts读数与结论完全一致,所以分诊等级(
domain:services/bug/p2/pm:queue)不变,验收口径也不变。改的只有一件事:这些数字现在真的出自它们所声称的那棵树。留这条不是走形式。在错的树上读出对的数,和在对的树上读出对的数,是两件事 —— 前者这次恰好一致,下次未必;承接这张卡的人如果照抄我上一条的"5e53d73d"去复现,会以为自己复现了我做过的事,而其实没有。凡是引用了具体提交号的读数,必须是对那个提交号读的。
Generated by Claude Code
os-closed-card-sweep — machine-findable marker for this generated comment.
Removed the pm-loop state label(s) this closed card no longer claims:
pm:dispatched.- Closing pull request: fix(auth): the rotating 2FA verify routes echo the live user row, not the pre-rotation snapshot #17000, merged.
- Closing commit
ab1c585383, merged intomain. - Left untouched:
bug,priority:p2,domain:services,auth— ownership, priority and outcome are not state claims. - The label set was read back after the write and matched.
A state label claims work is in flight. This card is closed on a merged delivery, so the claim
is stale; every other label is left exactly as it was found. Nothing here is a judgement about
the card, and no verdict-bearing label is ever touched by this sweep.posted by half-state-patrol run 34325389984 · trigger
scheduleGenerated by Claude Code
- added a commit that references this issue
on Sep 9, 2026
Found while binding the
auth.*return types for #14313 (not fixed there: that card narrows published return types and does not touch wire bytes).Reproducible defect
When a SIGNED-IN user confirms a new TOTP factor (
POST /api/v1/auth/two-factor/verify-totp, the enrolment lane), better-auth's handler writestwoFactorEnabled: true, rotates the session, and only then calls thevalid(ctx)closure thatverifyTwoFactor()built at entry — which still holds the PRE-rotation session and serialisesparseUserOutput(session.user)from it (better-auth/dist/plugins/two-factor/verify-two-factor.mjs, the signed-in branch). So the 200 body reports the user withtwoFactorEnabled: falsealthough the flag has just flipped server-side.plugin-auth already knows this closure is stale:
packages/plugins/plugin-auth/src/two-factor-rotated-token-echo.ts(#10701) repairs thetokenmember of that same body from the response's ownSet-Cookie. It leavesuseras the vendor echoed it.Measured on 2026-09-07, twice — the real
AuthManager(better-auth 1.7.2) on the in-memory engine, and again on a realSqlDriver(better-sqlite3) driven through the realObjectStackClient:(
verify-backup-codeon the signed-in lane does not rotate and echoes the live row, which is how the stale value is visible without a second request.)Why it matters
The Account portal's enrolment flow reads this body to render "2FA is on"; a client that trusts
user.twoFactorEnabledhere renders the factor as still OFF right after the user enabled it, and a bearer client that caches the echoed user carries the wrong flag until its nextget-session.Expected
On the rotating 2FA verify routes, when the response staged a session cookie whose token differs from the one echoed (the exact condition
two-factor-rotated-token-echo.tsalready keys on), the echoedusershould be the post-flip row — re-read from the adapter, or at minimumtwoFactorEnabledset from the write the handler just made — so the body and the row agree.Where
packages/plugins/plugin-auth/src/two-factor-rotated-token-echo.ts— the after-hook that repairstokenonROTATING_TWO_FACTOR_VERIFY_PATHS.better-auth/dist/plugins/two-factor/verify-two-factor.mjs(closure over the entry-time session) andtwo-factor/totp/index.mjs(rotation beforevalid(ctx)).Dedup: open
domain:cli,domain:servicesanddomain:runtimecards listed via REST and grepped fortwoFactorEnabled,verify-totp/verifyTotpand bearer/rotation — zero hits, with the control termsexported-any-returnsandbetter-authanswering.