mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-09-22 01:48:11 +00:00
feat: 设备鉴权迁移至客户端 + 插件卸载保护
安全修复(客户端鉴权): - remotedevice 服务端移除授权状态存储(authorized map/SetAuthorized/handleDeviceAuth) - DeviceMeta.Authorized 改为设备 hello 自报,服务端仅透传展示 - device_ctl_* 工具移除服务端授权检查,无条件转发,设备端自行决定是否执行 - 共享设备桥库 Bridge 新增本地 authorized 状态,未授权收到 cmd 直接拒绝 - waiter: --device-authorized / device_authorized 配置控制本地授权 - GUI: 授权存 gui-prefs 本地文件;设备页仅本机可切换开关 - webui /device/auth 旧路径返回 410 Gone - 根因:agent 可经 config_set 篡改服务端授权配置自行授权设备 插件管理强化: - 内置插件禁止卸载(IsBuiltinPlugin + 409),外部插件卸载即时生效 - 卸载不存在插件返回 404;移除误导性 reload_required 提示 - webui 插件路由:名称白名单校验防路径穿越、保留字路径保护
This commit is contained in:
@ -22,25 +22,28 @@ type DataHandler func(reqID, kind, mime string, data []byte)
|
||||
|
||||
// Bridge 是设备桥客户端核心结构体。
|
||||
// 管理 WebSocket 连接、消息路由、心跳保活和命令分发。
|
||||
// 授权状态由设备端本地存储(客户端鉴权),服务端不存储;
|
||||
// 未授权时收到 cmd 直接拒绝执行并回执 error。
|
||||
type Bridge struct {
|
||||
mu sync.RWMutex
|
||||
gateway string
|
||||
token string
|
||||
deviceID string
|
||||
name string
|
||||
kind string
|
||||
caps []string
|
||||
info map[string]interface{}
|
||||
mu sync.RWMutex
|
||||
gateway string
|
||||
token string
|
||||
deviceID string
|
||||
name string
|
||||
kind string
|
||||
caps []string
|
||||
info map[string]interface{}
|
||||
authorized bool // 客户端本地授权状态(用户在设备上手动开启)
|
||||
|
||||
ws *wsConn
|
||||
stopCh chan struct{}
|
||||
doneCh chan struct{}
|
||||
started bool
|
||||
ws *wsConn
|
||||
stopCh chan struct{}
|
||||
doneCh chan struct{}
|
||||
started bool
|
||||
|
||||
// 回调
|
||||
cmdHandler CmdHandler
|
||||
resultHandler CmdResultHandler
|
||||
dataHandler DataHandler
|
||||
cmdHandler CmdHandler
|
||||
resultHandler CmdResultHandler
|
||||
dataHandler DataHandler
|
||||
|
||||
// 二进制数据聚合(服务端→设备,如 TTS 音频)
|
||||
speechAccum *speechBuffer
|
||||
@ -85,19 +88,52 @@ func New(gateway, token, deviceID, name string, caps []string, info map[string]i
|
||||
}
|
||||
|
||||
return &Bridge{
|
||||
gateway: gateway,
|
||||
token: token,
|
||||
deviceID: deviceID,
|
||||
name: name,
|
||||
kind: "computer",
|
||||
caps: caps,
|
||||
info: info,
|
||||
stopCh: make(chan struct{}),
|
||||
doneCh: make(chan struct{}),
|
||||
pingInterval: 30 * time.Second,
|
||||
gateway: gateway,
|
||||
token: token,
|
||||
deviceID: deviceID,
|
||||
name: name,
|
||||
kind: "computer",
|
||||
caps: caps,
|
||||
info: info,
|
||||
stopCh: make(chan struct{}),
|
||||
doneCh: make(chan struct{}),
|
||||
pingInterval: 30 * time.Second,
|
||||
}
|
||||
}
|
||||
|
||||
// SetAuthorized 设置客户端本地授权状态(用户在设备上手动开启)。
|
||||
// 授权后立即重新发送 hello 同步到服务端展示。
|
||||
func (b *Bridge) SetAuthorized(auth bool) {
|
||||
b.mu.Lock()
|
||||
b.authorized = auth
|
||||
b.mu.Unlock()
|
||||
// 重新 hello 同步状态
|
||||
b.mu.RLock()
|
||||
ws := b.ws
|
||||
connected := ws != nil && !ws.closed
|
||||
b.mu.RUnlock()
|
||||
if connected {
|
||||
b.sendJSON(map[string]interface{}{
|
||||
"op": "hello",
|
||||
"device": map[string]interface{}{
|
||||
"device_id": b.deviceID,
|
||||
"name": b.name,
|
||||
"kind": b.kind,
|
||||
"caps": b.caps,
|
||||
"info": b.info,
|
||||
"authorized": auth,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Authorized 返回当前客户端本地授权状态。
|
||||
func (b *Bridge) Authorized() bool {
|
||||
b.mu.RLock()
|
||||
defer b.mu.RUnlock()
|
||||
return b.authorized
|
||||
}
|
||||
|
||||
// OnCmd 注册命令处理器。当收到 remotedevice 下发的 cmd 时调用。
|
||||
func (b *Bridge) OnCmd(handler CmdHandler) {
|
||||
b.mu.Lock()
|
||||
@ -149,15 +185,19 @@ func (b *Bridge) Start() error {
|
||||
b.ws = ws
|
||||
b.mu.Unlock()
|
||||
|
||||
// 发送 hello
|
||||
// 发送 hello(含设备自报的授权状态,服务端仅展示不决策)
|
||||
b.mu.RLock()
|
||||
auth := b.authorized
|
||||
b.mu.RUnlock()
|
||||
b.sendJSON(map[string]interface{}{
|
||||
"op": "hello",
|
||||
"device": map[string]interface{}{
|
||||
"device_id": b.deviceID,
|
||||
"name": b.name,
|
||||
"kind": b.kind,
|
||||
"caps": b.caps,
|
||||
"info": b.info,
|
||||
"device_id": b.deviceID,
|
||||
"name": b.name,
|
||||
"kind": b.kind,
|
||||
"caps": b.caps,
|
||||
"info": b.info,
|
||||
"authorized": auth,
|
||||
},
|
||||
})
|
||||
|
||||
@ -382,12 +422,19 @@ func (b *Bridge) handleMessage(msg map[string]interface{}) {
|
||||
if reqID == "" || command == "" {
|
||||
return
|
||||
}
|
||||
// 客户端鉴权:未授权时拒绝执行(服务端不存储授权状态,无法被 agent 篡改)
|
||||
b.mu.RLock()
|
||||
auth := b.authorized
|
||||
handler := b.cmdHandler
|
||||
b.mu.RUnlock()
|
||||
if !auth {
|
||||
log.Printf("[devicebridge] cmd rejected (unauthorized) req=%s cmd=%s", reqID, truncateString(command, 60))
|
||||
b.SendResult(reqID, "error", "", "设备未授权:请在设备本机开启远程控制授权")
|
||||
return
|
||||
}
|
||||
// 记录日志
|
||||
log.Printf("[devicebridge] cmd req=%s type=%s cmd=%s", reqID, cmdType, truncateString(command, 60))
|
||||
|
||||
b.mu.RLock()
|
||||
handler := b.cmdHandler
|
||||
b.mu.RUnlock()
|
||||
if handler != nil {
|
||||
handler(reqID, command)
|
||||
}
|
||||
@ -480,4 +527,4 @@ func truncateString(s string, n int) string {
|
||||
return s
|
||||
}
|
||||
return s[:n] + "..."
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user