fix(runtime)!: the /auth domain answers 501 instead of fabricating a login (#4113) - #4179
Merged
Merged
Conversation
…login (#4113) `domains/auth.ts` carried a `mockAuthFallback` that answered `POST /auth/sign-up/email`, `/register`, `/sign-in/email`, `/login`, `GET /get-session` and `POST /sign-out` with 200 and a fabricated user plus a 24-hour `mock_token_*` session — for ANY email and ANY password, which was never read. It shipped in `@objectstack/runtime`, not behind a dev-only plugin, and gated on nothing but an empty `auth` slot, so `os serve --preset minimal` and any embedder that mounts the dispatcher without plugin-auth served it. It was never a bypass: no session store backs that token, so `resolve-execution-context.ts` still resolved anonymous and `shouldDenyAnonymous` still denied data access. It was wrong in a different way — it told the client the one thing a server must never lie about, that it had authenticated someone, while discovery simultaneously reported `auth: unavailable` and advertised no `routes.auth`. A `declared ≠ enforced` gap pointing the opposite direction from the usual one. Its stated justification ("MSW/browser-only environments") had no consumer in this repo or in objectui, whose auth tests mock at the HTTP client layer. The only things pinning it were tests asserting the mock itself — nine of them, across three describe blocks. ADR-0115 retired this class of fabricating fallback inside plugin-dev. This was its last surviving member and the only one that shipped to production; the lineage before it (the #3891 analytics shim, #4000's dev stub, the three in #4058/#4086, #4126's security trio) was retired the same way: deleted, not put behind a flag. A flag would have kept the code path in every release and become the next thing nobody audits — ADR-0078's silently-inert declaration in environment-variable form. 501, not 404, following `/i18n` — the nearest precedent in shape: a core capability, a dispatcher-owned domain, an optional plugin behind it, and a route discovery already declines to advertise when the slot is empty. The route IS mounted; what is missing is the implementation behind it, which is what 501 states and 404 would misdescribe. It also keeps faith with the one true observation the mock was built on — its comment said it existed to keep sign-in "from 404ing", and a bare 404 there does send an operator hunting for a routing bug — without the lie it used to answer that concern with. The message names the remedy. The `randomUUID` helper goes with it: a CSPRNG built solely to mint mock session ids (its own comment says "these ids feed mock session tokens"), dead the moment the mock is. `domains/auth.ts` drops from 141 lines to 81. A wrong-shaped occupant — a service in the slot without the contract's `handleRequest` — takes the same 501, and that is the sharper case: the slot is FILLED, so discovery advertises `routes.auth` and reports auth available, while the request used to get a fabricated session. #4143 had already narrowed the mock's reach by fixing that probe (`handler(req, res)`, a method no implementation has, to the contract's `handleRequest`); before it, even a correctly registered auth service was bypassed in favour of the mock. Tests replace the nine that pinned the mock: all six formerly-mocked paths parametrized so a partial re-introduction names itself, no `mock_`/user/session anywhere in the body, the remedy named, an identical answer for any credentials (a 501 that varied by password would be an oracle), a registered service still delegated to, and the wrong-shaped-occupant case. Verified: runtime 940, plugin-hono-server 149, plugin-auth 579, rest 512 pass; build 71/71; eslint clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018UNGqBQcdJ2RYHtWgntJ9B
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 1 package(s): 18 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
…retire the docs section describing the mock
Two things the deletion left behind, both caught after the fact — the
route-envelope ledger by CI, the docs by the drift check.
`scripts/check-route-envelope.mjs` declared `handBuilt: 4` for
`domains/auth.ts`, exempting the mock's four better-auth-shaped bodies
(`{ user, session }`, `{ session: null, user: null }`, `{ success: true }`)
from BaseResponseSchema on the grounds that better-auth's client parses ITS
shapes. That was a sound exemption for a real bridge — but those four bodies
were the MOCK's, not the bridge's: the real service returns its own `Response`
as `result`, built nowhere in this file. With the mock gone the count is 0 and
the exemption goes with it, which is the ledger working exactly as intended
(`found 0, declared 4` — it refuses to let a stale allowance sit there).
`content/docs/permissions/authentication.mdx` carried a "Mock Fallback
Endpoints" table documenting all six fabricating endpoints as a feature, and
told readers it "ensures that registration and sign-in flows do not return 404
errors in MSW/browser-only environments". It now documents the 501, says why
the mock was removed, and points anyone who relied on it at the two real
options: mock at the HTTP client layer (what the console does), or load
AuthPlugin, which needs no HTTP server.
Verified: pnpm lint clean; all ten check:* scripts pass (the CI job that failed
runs check:org-identifier, check:authz-resolver and check:route-envelope
alongside eslint — all green now).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018UNGqBQcdJ2RYHtWgntJ9B
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
关闭 #4113。这是 ADR-0115 那一类编造型 fallback 的最后一个幸存者,也是其中唯一随发行版进生产的那个 —— 其余全在 plugin-dev 里。
删掉的东西
packages/runtime/src/domains/auth.ts的mockAuthFallback,对POST /auth/sign-up/email、/register、/sign-in/email、/login、GET /get-session、POST /sign-out:它在
@objectstack/runtime里而不是 dev-only 插件里,判据只有"auth 槽位空不空",所以os serve --preset minimal和任何不装 plugin-auth 的 dispatcher 宿主都会命中。不是越权 —— token 没有 session 存储支撑,
resolve-execution-context.ts仍解析为匿名、shouldDenyAnonymous仍拒绝数据访问。它错在另一个方向:对客户端伪造了"我已经认证了某个人",而 discovery 同时在报auth: unavailable且不广告routes.auth—— 一个方向相反的declared ≠ enforced。理由写的"MSW/browser-only environments"在本仓与
objectui都没有消费者(objectui 的 auth 测试全在 HTTP 客户端层自己 mock);唯一在支撑它的是钉它自己的 9 条测试(分散在三个 describe 块)。连带删除
randomUUID助手 —— 一个纯粹为铸造 mock session id 而存在的 CSPRNG(它自己的注释写着 "these ids feed mock session tokens"),mock 一走就是死代码。文件从 141 行降到 81 行。为什么是删除,不是加开关
先例全部是删:#3891(analytics shim)、#4000、#4058/#4086、#4126、#4137。加环境开关会把代码路径继续留在每个发行版里,而那个 flag 会变成下一个没人审计的东西 —— ADR-0078 说的"沉默的失效声明"的环境变量形态。
为什么是 501,不是 404
各域现状确实不一致(
analytics/automation/notifications→handled: false→404;i18n→ 501;ui→ 503;mcp→ flag 关时 404、启用但无实现时 501)。选 501 的依据:/i18n是形状上最近的先例:核心能力、dispatcher 自有域、背后是可选插件、且空槽时 discovery 同样不广告路由 —— 它选的就是501 'i18n service not available'。wrong-shaped occupant 走同一条 501,而且那才是更尖锐的情形:槽位是满的,discovery 会广告
routes.auth并报 auth 可用,而这种请求过去拿到的是伪造 session。#4143 此前已经缩小过它的射程(把handler(req, res)这个没有任何实现拥有的探测改成契约的handleRequest)—— 在那之前,即使正确注册了真实 auth 服务也会被 mock 顶掉。测试
替换掉钉 mock 的 9 条,新的钉诚实行为:
mock_/user/sessionplugin-auth验证
runtime 940 / plugin-hono-server 149 / plugin-auth 579 / rest 512 全过;build 71/71;eslint 干净。
ADR-0115 收尾更新里"#4113 是那类假实现仅存的地方"一句已同步收口。
🤖 Generated with Claude Code
https://claude.ai/code/session_018UNGqBQcdJ2RYHtWgntJ9B
Generated by Claude Code