Skip to content

plugin-auth 的终结式 catch-all 吞掉 /api/v1/auth/* 下别人的路由 —— console 权限层目前靠 kernel.use() 顺序才活着 #4088

Description

@os-zhuang

按 Prime Directive #10 记录,#4073 / #4079(拆分 registerStandardEndpoints)实施中发现。

现象

plugin-auth 在自己的 kernel:ready hook 里挂一个覆盖整个 auth 命名空间的 catch-all(auth-plugin.tsregisterAuthRoutes):

rawApp.all(`${basePath}/*`, async (c) => {
    const response = await this.authManager!.handleRequest(c.req.raw);
    // …日志 / jwks cache-control…
    return response;                  // ← 终结式:从不 next()
});

basePath 默认 /api/v1/auth。这个 handler 从不 next() —— 它无条件返回 better-auth 的响应,包括 better-auth 不认识那条路径时返回的 404。

plugin-hono-server它自己的 kernel:ready hook 里挂 /api/v1/auth/me/permissions/api/v1/auth/me/localization#4079 后这两条已改为无条件注册)。两个 hook 都在 kernel:ready 阶段,Hono 对同一路径按注册顺序依次执行 handler,先返回 Response 的赢。

于是这两条端点能不能工作,完全取决于 kernel.use() 的顺序

顺序 结果
HonoServerPlugin 先(今天 os serve 的顺序) hono 的具体路由先注册 → 正常应答
AuthPlugin 先 catch-all 先注册 → better-auth 不认识 /me/permissions404

为什么值得单独处理

坏掉的时候是静默的,而且砸在最要命的路径上:

  • objectui console 的整个权限层/auth/me/permissionsMePermissionsProviderDEFAULT_ENDPOINT)—— 404 意味着 FLS / apiOperations 全线失去服务端答案;
  • apps/console/src/AppContent.tsx/auth/me/localization 取区域默认值;
  • core/security/auth-gate.ts/me/localization 列为"被门禁拦住的用户必须仍能访问"。

而且没有任何东西保护它#4079 新增的测试只钉住了 hono 插件内部的注册顺序(/auth/me/* 在 CRUD 块之前),跨插件的 hono-先于-auth 没有任何断言、也没有任何机制保证。

这与 #2567(匿名拒绝取决于谁先注册 /data)、#4018(discovery 被顺序遮蔽)是同一类问题:不变量靠加载顺序侥幸成立,而非被强制。前两个都是发现后单独收口的。

更一般地说:任何插件想在 /api/v1/auth/* 下挂路由,今天都会被这个 catch-all 吞掉,除非它恰好注册得更早。这是命名空间所有权的问题,不只是这三条端点的问题。

建议修法

让 catch-all 在 better-auth 不拥有该路径时落穿,而不是把 404 当作最终答案:

rawApp.all(`${basePath}/*`, async (c, next) => {
    const response = await this.authManager!.handleRequest(c.req.raw);
    if (response.status === 404) {
        await next();                     // 让后注册的具体路由有机会应答
        if (!c.finalized) return response; // 没人接 → 保持 better-auth 原本的 404
        return;                           // 有人接了
    }
    // …现有的 >=500 日志 / jwks cache-control…
    return response;
});

要点:

  • 只对 404 落穿。 401/403 是 better-auth 的真实回答,不是"不认识这条路径"。
  • c.finalized 兜底(Hono 4.12.31 有这个属性),所以真正无人认领的 auth 路径线上形状不变 —— 仍是 better-auth 原本那个 404,而不是平台的 notFound。
  • 优先级方向正确:plugin-auth 拥有这个命名空间,所以 better-auth 实现了的路径仍然先赢;别人只能捡它不要的。同时结果与注册顺序无关。
  • 代价是这三条 /me/* 每次请求会多走一次 better-auth 的未知路径查找(应无副作用)。

不建议改成"按枚举只挂 better-auth 真实拥有的路径":#3656 的 route-ledger 明确依赖"catch-all 会自动发布上游新增的端点"这一性质,改成枚举会引入一份需要长期同步的表 —— 正是 #4018 / #4073 一路在消除的那种重复供给税。

保护

auth-plugin.test.ts 已有用 mock rawApp 驱动 kernel:ready 注册的先例,换成真实 Hono app 即可测到真代码:catch-all 注册、具体路由注册,断言后者仍能应答;并断言无人认领的路径仍拿到 better-auth 原本的 404、401/403 不会被落穿。

关联:#4073#4079#2567#4018#3656(auth route ledger)、ADR-0076 D11(单一 owner)。

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions