feat: require paired browser receiver
This commit is contained in:
@@ -15,6 +15,7 @@ import (
|
||||
"github.com/wailsapp/wails/v2/pkg/runtime"
|
||||
|
||||
"github.com/verstak/verstak-desktop/internal/core/appsettings"
|
||||
"github.com/verstak/verstak-desktop/internal/core/browserreceiver"
|
||||
"github.com/verstak/verstak-desktop/internal/core/capability"
|
||||
"github.com/verstak/verstak-desktop/internal/core/contribution"
|
||||
"github.com/verstak/verstak-desktop/internal/core/events"
|
||||
@@ -62,6 +63,7 @@ type App struct {
|
||||
workbench *coreworkbench.Router
|
||||
workspace *workspace.Manager
|
||||
syncSvc *syncsvc.Service
|
||||
browserReceiver *browserreceiver.Receiver
|
||||
secretsSession *coresecrets.VaultSession
|
||||
fileWatcher *filewatcher.Service
|
||||
debug bool
|
||||
@@ -87,6 +89,7 @@ func NewApp(
|
||||
pluginStateMgr *pluginstate.Manager,
|
||||
workspaceMgr *workspace.Manager,
|
||||
syncService *syncsvc.Service,
|
||||
browserReceiverService *browserreceiver.Receiver,
|
||||
debugEnabled bool,
|
||||
) *App {
|
||||
app := &App{
|
||||
@@ -104,6 +107,7 @@ func NewApp(
|
||||
workbench: coreworkbench.NewRouter(workbenchPrefsFromSettings(appSettingsMgr)),
|
||||
workspace: workspaceMgr,
|
||||
syncSvc: syncService,
|
||||
browserReceiver: browserReceiverService,
|
||||
fileWatcher: filewatcher.NewService(bus, 0),
|
||||
debug: debugEnabled,
|
||||
activityEvents: make(map[string]bool),
|
||||
@@ -2105,6 +2109,62 @@ func (a *App) GetPluginAssetContent(pluginID, assetPath string) (string, string)
|
||||
return string(data), ""
|
||||
}
|
||||
|
||||
// ─── Browser Receiver API ──────────────────────────────────
|
||||
|
||||
func (a *App) requirePluginBrowserReceiverAccess(pluginID string) error {
|
||||
_, err := a.requirePluginAccess(pluginID, "browser.receiver.manage")
|
||||
return err
|
||||
}
|
||||
|
||||
func (a *App) browserReceiverPairing() (map[string]string, error) {
|
||||
if a.browserReceiver == nil {
|
||||
return nil, fmt.Errorf("browser receiver is unavailable")
|
||||
}
|
||||
if a.appSettings == nil {
|
||||
return nil, fmt.Errorf("app settings not initialized")
|
||||
}
|
||||
token := strings.TrimSpace(a.appSettings.Get().BrowserReceiver.Token)
|
||||
if token == "" {
|
||||
return nil, fmt.Errorf("browser receiver token is unavailable")
|
||||
}
|
||||
return map[string]string{
|
||||
"receiverUrl": browserreceiver.DefaultCaptureURL,
|
||||
"receiverToken": token,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// PluginBrowserReceiverPairing returns the local receiver settings for authorized plugins.
|
||||
func (a *App) PluginBrowserReceiverPairing(pluginID string) (map[string]string, string) {
|
||||
if err := a.requirePluginBrowserReceiverAccess(pluginID); err != nil {
|
||||
return nil, err.Error()
|
||||
}
|
||||
pairing, err := a.browserReceiverPairing()
|
||||
if err != nil {
|
||||
return nil, err.Error()
|
||||
}
|
||||
return pairing, ""
|
||||
}
|
||||
|
||||
// PluginRotateBrowserReceiverToken invalidates previous extension pairings.
|
||||
func (a *App) PluginRotateBrowserReceiverToken(pluginID string) (map[string]string, string) {
|
||||
if err := a.requirePluginBrowserReceiverAccess(pluginID); err != nil {
|
||||
return nil, err.Error()
|
||||
}
|
||||
if _, err := a.browserReceiverPairing(); err != nil {
|
||||
return nil, err.Error()
|
||||
}
|
||||
token, err := a.appSettings.RotateBrowserReceiverToken()
|
||||
if err != nil {
|
||||
return nil, err.Error()
|
||||
}
|
||||
a.browserReceiver.SetReceiverToken(token)
|
||||
pairing, err := a.browserReceiverPairing()
|
||||
if err != nil {
|
||||
return nil, err.Error()
|
||||
}
|
||||
return pairing, ""
|
||||
}
|
||||
|
||||
// ─── Sync API ──────────────────────────────────────────────
|
||||
|
||||
func (a *App) requirePluginSyncAccess(pluginID string, remote bool) error {
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/verstak/verstak-desktop/internal/core/appsettings"
|
||||
"github.com/verstak/verstak-desktop/internal/core/browserreceiver"
|
||||
"github.com/verstak/verstak-desktop/internal/core/capability"
|
||||
"github.com/verstak/verstak-desktop/internal/core/contribution"
|
||||
"github.com/verstak/verstak-desktop/internal/core/events"
|
||||
@@ -2000,6 +2001,54 @@ func TestPluginSyncBridgeRequiresDeclaredPermissions(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestPluginBrowserReceiverPairingRequiresPermissionAndRotatesToken(t *testing.T) {
|
||||
app := newBridgeTestApp(t)
|
||||
app.appSettings = appsettings.NewManager(filepath.Join(t.TempDir(), "config.json"))
|
||||
if err := app.appSettings.Load(); err != nil {
|
||||
t.Fatalf("settings Load: %v", err)
|
||||
}
|
||||
initialToken, err := app.appSettings.EnsureBrowserReceiverToken()
|
||||
if err != nil {
|
||||
t.Fatalf("EnsureBrowserReceiverToken: %v", err)
|
||||
}
|
||||
app.browserReceiver = browserreceiver.NewWithOptions(app.eventBus, browserreceiver.Options{
|
||||
RequireToken: true,
|
||||
ReceiverToken: initialToken,
|
||||
})
|
||||
app.plugins = append(app.plugins, plugin.Plugin{
|
||||
Manifest: plugin.Manifest{
|
||||
ID: "browser.local",
|
||||
Name: "Browser Local",
|
||||
Version: "1.0.0",
|
||||
Permissions: []string{"browser.receiver.manage"},
|
||||
},
|
||||
Status: plugin.StatusLoaded,
|
||||
Enabled: true,
|
||||
})
|
||||
|
||||
pairing, errStr := app.PluginBrowserReceiverPairing("browser.local")
|
||||
if errStr != "" {
|
||||
t.Fatalf("PluginBrowserReceiverPairing: %s", errStr)
|
||||
}
|
||||
if pairing["receiverToken"] != initialToken {
|
||||
t.Fatalf("pairing token = %q, want %q", pairing["receiverToken"], initialToken)
|
||||
}
|
||||
if pairing["receiverUrl"] != browserreceiver.DefaultCaptureURL {
|
||||
t.Fatalf("pairing receiver URL = %q, want %q", pairing["receiverUrl"], browserreceiver.DefaultCaptureURL)
|
||||
}
|
||||
if _, errStr := app.PluginBrowserReceiverPairing("no.storage"); !strings.Contains(errStr, "browser.receiver.manage") {
|
||||
t.Fatalf("missing permission error = %q", errStr)
|
||||
}
|
||||
|
||||
rotated, errStr := app.PluginRotateBrowserReceiverToken("browser.local")
|
||||
if errStr != "" {
|
||||
t.Fatalf("PluginRotateBrowserReceiverToken: %s", errStr)
|
||||
}
|
||||
if rotated["receiverToken"] == "" || rotated["receiverToken"] == initialToken {
|
||||
t.Fatalf("rotated pairing token = %q, want new non-empty token", rotated["receiverToken"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestPluginSyncStatusReportsPersistedError(t *testing.T) {
|
||||
app := newBridgeTestApp(t)
|
||||
app.plugins = append(app.plugins,
|
||||
|
||||
Reference in New Issue
Block a user