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.write | READ + WRITE + APPROVAL | 变更操作 |
operator.read | READ_METHODS | 只读查询 |
operator.approvals | APPROVAL_METHODS | exec 审批流 |
operator.pairing | PAIRING_METHODS | device/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" });
}八、自检清单
NODE_ROLE_METHODS只有 3 个:node.invoke.result、node.event、skills.bins。operator.admin绕过所有 scope 检查(第 2 步就返回 true)。ADMIN_METHOD_PREFIXES = ["exec.approvals."],匹配前缀优先于集合查找。config.get在ADMIN_ONLY_METHODS中(需要 admin),不在READ_METHODS里。- 未知方法默认要求
operator.admin(保守策略,新方法不会被默认放行)。 extraHandlers优先于coreGatewayHandlers(插件可覆盖内建方法)。
九、开发避坑
- 新增方法时必须将其加入对应的 scope 集合,否则会落入"未知方法要求 admin"的兜底逻辑。
operator.write隐含operator.read权限(read 方法在 write scope 下也能访问)。- 不要把敏感管理方法加入
READ_METHODS或WRITE_METHODS——应明确加入ADMIN_ONLY_METHODS。 - pairing 方法有独立 scope,不受 write scope 覆盖,必须显式授予
operator.pairing。