Skip to content

29 函数级剖析:方法鉴权与请求分发

核心文件:src/gateway/server-methods.ts

模块定位

握手完成后,每条普通 req 帧都经过方法鉴权(authorizeGatewayMethod),通过后才调用对应的 handler。这是细粒度权限控制的核心,决定哪个角色/scope 能访问哪些方法。

一、请求处理链路

WS req 帧


message-handler.ts → handleGatewayRequest(method, params, client)


authorizeGatewayMethod(method, client)   ← 本模块核心

    ├─ 通过 → 从 extraHandlers / coreGatewayHandlers 取 handler
    │           └─ 执行 handler(req, params, client, context, respond)

    └─ 拒绝 → 返回 "unauthorized" 或 "unknown method"

二、角色约束(role)

ts
// src/gateway/server-methods.ts

// node 角色:仅限以下 3 个方法
const NODE_ROLE_METHODS = new Set([
  "node.invoke.result",
  "node.event",
  "skills.bins",
]);

// 所有其他方法:要求 operator 角色
if (client.role === "node") {
  if (!NODE_ROLE_METHODS.has(method)) {
    return { authorized: false, reason: "node role cannot access operator methods" };
  }
  return { authorized: true };
}

三、五个 scope 常量(精确成员列表)

APPROVAL_METHODS(3 个)

ts
const APPROVAL_METHODS = new Set([
  "exec.approval.request",
  "exec.approval.waitDecision",
  "exec.approval.resolve",
]);

PAIRING_METHODS(11 个)

ts
const PAIRING_METHODS = new Set([
  "node.pair.request",
  "node.pair.list",
  "node.pair.approve",
  "node.pair.reject",
  "node.pair.verify",
  "device.pair.list",
  "device.pair.approve",
  "device.pair.reject",
  "device.token.rotate",
  "device.token.revoke",
  "node.rename",
]);

READ_METHODS(15 个)

ts
const READ_METHODS = new Set([
  "health",
  "logs.tail",
  "channels.status",
  "status",
  "usage.status",
  "usage.cost",
  "tts.status",
  "tts.providers",
  "models.list",
  "agents.list",
  "agent.identity.get",
  "skills.status",
  "voicewake.get",
  "sessions.list",
  "sessions.preview",
  "cron.list",
  "cron.status",
  "cron.runs",
  "system-presence",
  "last-heartbeat",
  "node.list",
  "node.describe",
  "chat.history",
  "config.get",
  "talk.config",
]);

WRITE_METHODS(14 个)

ts
const WRITE_METHODS = new Set([
  "send",
  "agent",
  "agent.wait",
  "wake",
  "talk.mode",
  "tts.enable",
  "tts.disable",
  "tts.convert",
  "tts.setProvider",
  "voicewake.set",
  "node.invoke",
  "chat.send",
  "chat.abort",
  "browser.request",
]);

ADMIN_METHOD_PREFIXES(强制 admin 的前缀)

ts
const ADMIN_METHOD_PREFIXES = ["exec.approvals."];

四、authorizeGatewayMethod(完整判定逻辑)

ts
// src/gateway/server-methods.ts

export function authorizeGatewayMethod(
  method: string,
  client: GatewayWsClient,
): { authorized: boolean; reason?: string } {

  // 1. node 角色:只允许 NODE_ROLE_METHODS
  if (client.role === "node") { ... }

  // 2. operator.admin → 直接通过所有
  if (client.scopes.has("operator.admin")) {
    return { authorized: true };
  }

  // 3. ADMIN_METHOD_PREFIXES → 强制要求 admin
  for (const prefix of ADMIN_METHOD_PREFIXES) {
    if (method.startsWith(prefix)) {
      return { authorized: false, reason: "requires operator.admin scope" };
    }
  }

  // 4. 敏感方法白名单 → 强制 admin
  const ADMIN_ONLY_METHODS = [
    "config.get", "config.set", "config.reload",
    "wizard.start", "wizard.step", "wizard.cancel",
    "update.check", "update.install",
    "sessions.patch", "sessions.reset", "sessions.delete", "sessions.compact",
    "cron.add", "cron.update", "cron.remove", "cron.run",
    "channels.logout",
    "agents.create", "agents.update", "agents.delete",
    "skills.install", "skills.update",
  ];
  if (ADMIN_ONLY_METHODS.includes(method)) {
    return { authorized: false, reason: "requires operator.admin scope" };
  }

  // 5. scope 集合判断
  if (APPROVAL_METHODS.has(method)) {
    return client.scopes.has("operator.approvals") || client.scopes.has("operator.write")
      ? { authorized: true }
      : { authorized: false, reason: "requires operator.approvals scope" };
  }

  if (PAIRING_METHODS.has(method)) {
    return client.scopes.has("operator.pairing")
      ? { authorized: true }
      : { authorized: false, reason: "requires operator.pairing scope" };
  }

  if (READ_METHODS.has(method)) {
    return client.scopes.has("operator.read") || client.scopes.has("operator.write")
      ? { authorized: true }
      : { authorized: false, reason: "requires operator.read scope" };
  }

  if (WRITE_METHODS.has(method)) {
    return client.scopes.has("operator.write")
      ? { authorized: true }
      : { authorized: false, reason: "requires operator.write scope" };
  }

  // 6. 未知方法:要求 admin(保守默认)
  return client.scopes.has("operator.admin")
    ? { authorized: true }
    : { authorized: false, reason: "unknown method requires operator.admin" };
}

五、scope 层级一览

scope可访问内容说明
operator.admin全部方法最高权限,覆盖所有 scope
operator.writeREAD + WRITE + APPROVAL变更操作
operator.readREAD_METHODS只读查询
operator.approvalsAPPROVAL_METHODSexec 审批流
operator.pairingPAIRING_METHODSdevice/node 配对

六、handler 聚合方式(coreGatewayHandlers)

ts
// server-methods.ts 内部

const coreGatewayHandlers = {
  ...chatHandlers,         // chat.send / chat.abort / chat.inject / chat.history
  ...sessionsHandlers,     // sessions.list / sessions.patch / sessions.compact ...
  ...configHandlers,       // config.get / config.set / config.reload ...
  ...nodeHandlers,         // node.invoke / node.pair.* / node.list ...
  ...cronHandlers,         // cron.add / cron.list / cron.run ...
  ...ttsHandlers,          // tts.enable / tts.convert ...
  ...skillsHandlers,       // skills.install / skills.status ...
  ...agentsHandlers,       // agents.list / agents.create ...
  // ...更多方法组...
};

每个方法组独立文件,网关入口只负责路由分发,不包含业务逻辑。

七、extraHandlers(插件/扩展注入)

ts
// 插件通过注册 extraHandlers 添加自定义方法

handleGatewayRequest(method, params, client, {
  extraHandlers: pluginRegistry.getHandlers(),
  coreHandlers: coreGatewayHandlers,
});

// 查找优先级:extraHandlers 先于 coreHandlers
const handler = extraHandlers[method] ?? coreHandlers[method];
if (!handler) {
  return respond({ error: "unknown method" });
}

八、自检清单

  1. NODE_ROLE_METHODS 只有 3 个:node.invoke.resultnode.eventskills.bins
  2. operator.admin 绕过所有 scope 检查(第 2 步就返回 true)。
  3. ADMIN_METHOD_PREFIXES = ["exec.approvals."],匹配前缀优先于集合查找。
  4. config.getADMIN_ONLY_METHODS 中(需要 admin),不在 READ_METHODS 里。
  5. 未知方法默认要求 operator.admin(保守策略,新方法不会被默认放行)。
  6. extraHandlers 优先于 coreGatewayHandlers(插件可覆盖内建方法)。

九、开发避坑

  1. 新增方法时必须将其加入对应的 scope 集合,否则会落入"未知方法要求 admin"的兜底逻辑。
  2. operator.write 隐含 operator.read 权限(read 方法在 write scope 下也能访问)。
  3. 不要把敏感管理方法加入 READ_METHODSWRITE_METHODS——应明确加入 ADMIN_ONLY_METHODS
  4. pairing 方法有独立 scope,不受 write scope 覆盖,必须显式授予 operator.pairing

用工程视角拆解 AI 智能体框架