refactor(web): secure Pico websocket access behind launcher auth

- stop exposing the raw Pico token to the frontend
- add /api/pico/info for non-secret Pico connection metadata
- proxy /pico/ws through the launcher with same-origin and dashboard auth checks
- inject the upstream Pico websocket protocol server-side
- update frontend chat connection flow and Vite websocket proxy path
- refresh related docs and tests
This commit is contained in:
wenjie
2026-04-16 16:47:23 +08:00
parent 6126ede963
commit 4b76196e2c
14 changed files with 253 additions and 171 deletions
-2
View File
@@ -18,8 +18,6 @@ const (
TypeError = "error"
TypePong = "pong"
PicoTokenPrefix = "pico-"
PayloadKeyContent = "content"
PayloadKeyThought = "thought"
+1 -23
View File
@@ -9,7 +9,6 @@ import (
"path/filepath"
"sort"
"strconv"
"strings"
"sync"
"sync/atomic"
"syscall"
@@ -27,7 +26,7 @@ import (
_ "github.com/sipeed/picoclaw/pkg/channels/line"
_ "github.com/sipeed/picoclaw/pkg/channels/maixcam"
_ "github.com/sipeed/picoclaw/pkg/channels/onebot"
"github.com/sipeed/picoclaw/pkg/channels/pico"
_ "github.com/sipeed/picoclaw/pkg/channels/pico"
_ "github.com/sipeed/picoclaw/pkg/channels/qq"
_ "github.com/sipeed/picoclaw/pkg/channels/slack"
_ "github.com/sipeed/picoclaw/pkg/channels/teams_webhook"
@@ -316,8 +315,6 @@ func executeReload(
) error {
defer runningServices.reloading.Store(false)
overridePicoToken(newCfg, runningServices.authToken)
return handleConfigReload(ctx, agentLoop, newCfg, provider, runningServices, msgBus, allowEmptyStartup, debug)
}
@@ -386,8 +383,6 @@ func setupAndStartServices(
fms.Start()
}
overridePicoToken(cfg, authToken)
runningServices.ChannelManager, err = channels.NewManager(cfg, msgBus, runningServices.MediaStore)
if err != nil {
if fms, ok := runningServices.MediaStore.(*media.FileMediaStore); ok {
@@ -788,23 +783,6 @@ func setupCronTool(
return cronService, nil
}
// overridePicoToken replaces the pico channel token with the one from the PID file.
// The PID file is the single source of truth for the pico auth token;
// it is generated once at gateway startup and remains unchanged across reloads.
func overridePicoToken(cfg *config.Config, token string) {
picoBC := cfg.Channels.GetByType(config.ChannelPico)
if picoBC == nil || !picoBC.Enabled {
return
}
var picoCfg config.PicoSettings
picoBC.Decode(&picoCfg)
picoToken := picoCfg.Token.String()
if picoToken == "" || strings.HasPrefix(picoToken, pico.PicoTokenPrefix) {
return
}
picoCfg.SetToken(pico.PicoTokenPrefix + token + picoToken)
}
func createHeartbeatHandler(agentLoop *agent.AgentLoop) func(prompt, channel, chatID string) *tools.ToolResult {
return func(prompt, channel, chatID string) *tools.ToolResult {
if channel == "" || chatID == "" {