mirror of
https://github.com/gesellix/Bose-SoundTouch.git
synced 2026-08-19 00:56:16 +00:00
fix(tts): move TTS endpoints from /mgmt to /setup (no Basic Auth)
The TTS speak/config endpoints were under /mgmt (Basic-Auth protected),
but the soundtouch-web proxy and CLI authenticated with their own
mgmt-password default (empty) while the service defaults to "change_me!",
so speaking from -web returned 401.
This was also inconsistent: the Google API key is configured via the
unauthenticated /setup/settings, and Play URL already proxies to /setup,
so gating only TTS playback behind mgmt auth made no sense. Move
/mgmt/tts/{speak,config} to /setup/tts/{speak,config} (LAN-trust, like
the rest of the setup surface), rename the handlers accordingly, and drop
the now-unused mgmt-credential plumbing from soundtouch-web and the CLI
tts command.
Verified: POST /setup/tts/speak now reaches the handler without auth
(502 only because the test speaker IP is unreachable; previously 401).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
8f2939a9a6
commit
169c1c5b9f
@@ -25,9 +25,9 @@ type ttsSpeakRequest struct {
|
||||
Volume *int `json:"volume,omitempty"`
|
||||
}
|
||||
|
||||
// HandleMgmtTTSSpeak synthesizes the requested text (or builds a direct URL),
|
||||
// HandleTTSSpeak synthesizes the requested text (or builds a direct URL),
|
||||
// then tells the target speaker to play it via the /speaker endpoint.
|
||||
func (s *Server) HandleMgmtTTSSpeak(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *Server) HandleTTSSpeak(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
svc := s.ttsSvc()
|
||||
@@ -147,8 +147,8 @@ func (s *Server) HandleTTSMedia(w http.ResponseWriter, r *http.Request) {
|
||||
_, _ = w.Write(audio)
|
||||
}
|
||||
|
||||
// HandleMgmtTTSConfig reports the active TTS configuration (no secrets).
|
||||
func (s *Server) HandleMgmtTTSConfig(w http.ResponseWriter, _ *http.Request) {
|
||||
// HandleTTSConfig reports the active TTS configuration (no secrets).
|
||||
func (s *Server) HandleTTSConfig(w http.ResponseWriter, _ *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
svc := s.ttsSvc()
|
||||
|
||||
@@ -23,8 +23,8 @@ func ttsTestRouter(t *testing.T, baseURL string) (*chi.Mux, *Server) {
|
||||
|
||||
r := chi.NewRouter()
|
||||
r.Get("/media/tts/{id}", server.HandleTTSMedia)
|
||||
r.Post("/mgmt/tts/speak", server.HandleMgmtTTSSpeak)
|
||||
r.Get("/mgmt/tts/config", server.HandleMgmtTTSConfig)
|
||||
r.Post("/setup/tts/speak", server.HandleTTSSpeak)
|
||||
r.Get("/setup/tts/config", server.HandleTTSConfig)
|
||||
|
||||
return r, server
|
||||
}
|
||||
@@ -40,10 +40,10 @@ func mockCloudTTS(t *testing.T, audio string) *httptest.Server {
|
||||
}))
|
||||
}
|
||||
|
||||
func TestHandleMgmtTTSConfigNotConfigured(t *testing.T) {
|
||||
func TestHandleTTSConfigNotConfigured(t *testing.T) {
|
||||
r, _ := ttsTestRouter(t, "http://localhost:8001")
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/mgmt/tts/config", nil)
|
||||
req := httptest.NewRequest(http.MethodGet, "/setup/tts/config", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
r.ServeHTTP(rec, req)
|
||||
|
||||
@@ -61,11 +61,11 @@ func TestHandleMgmtTTSConfigNotConfigured(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleMgmtTTSConfigConfigured(t *testing.T) {
|
||||
func TestHandleTTSConfigConfigured(t *testing.T) {
|
||||
r, server := ttsTestRouter(t, "http://localhost:8001")
|
||||
server.SetTTSService(tts.NewService(tts.NewTranslateProvider(), tts.Config{AppKey: "k", DefaultLanguage: "EN"}))
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/mgmt/tts/config", nil)
|
||||
req := httptest.NewRequest(http.MethodGet, "/setup/tts/config", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
r.ServeHTTP(rec, req)
|
||||
|
||||
@@ -139,10 +139,10 @@ func TestHandleTTSMediaMissingClip(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleMgmtTTSSpeakNotConfigured(t *testing.T) {
|
||||
func TestHandleTTSSpeakNotConfigured(t *testing.T) {
|
||||
r, _ := ttsTestRouter(t, "http://localhost:8001")
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/mgmt/tts/speak", strings.NewReader(`{"host":"192.0.2.10","text":"hi"}`))
|
||||
req := httptest.NewRequest(http.MethodPost, "/setup/tts/speak", strings.NewReader(`{"host":"192.0.2.10","text":"hi"}`))
|
||||
rec := httptest.NewRecorder()
|
||||
r.ServeHTTP(rec, req)
|
||||
|
||||
@@ -151,7 +151,7 @@ func TestHandleMgmtTTSSpeakNotConfigured(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleMgmtTTSSpeakValidation(t *testing.T) {
|
||||
func TestHandleTTSSpeakValidation(t *testing.T) {
|
||||
r, server := ttsTestRouter(t, "http://localhost:8001")
|
||||
server.SetTTSService(tts.NewService(tts.NewTranslateProvider(), tts.Config{AppKey: "k"}))
|
||||
|
||||
@@ -167,7 +167,7 @@ func TestHandleMgmtTTSSpeakValidation(t *testing.T) {
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodPost, "/mgmt/tts/speak", strings.NewReader(tc.body))
|
||||
req := httptest.NewRequest(http.MethodPost, "/setup/tts/speak", strings.NewReader(tc.body))
|
||||
rec := httptest.NewRecorder()
|
||||
r.ServeHTTP(rec, req)
|
||||
|
||||
|
||||
@@ -42,11 +42,6 @@ type WebApp struct {
|
||||
RepoURL string
|
||||
ServiceURL string
|
||||
|
||||
// Management API credentials for proxying to the AfterTouch service's
|
||||
// Basic-Auth-protected /mgmt endpoints (e.g. TTS synthesis).
|
||||
MgmtUsername string
|
||||
MgmtPassword string
|
||||
|
||||
discoveryStatus atomic.Value // stores *webtypes.DiscoveryStatus
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
// HandleAPISpeakText synthesizes and plays text on a device. The Web UI talks
|
||||
// to speakers directly for most controls, but TTS synthesis (Google Cloud) and
|
||||
// the Bose app_key live in the AfterTouch service, so this proxies to the
|
||||
// service's /mgmt/tts/speak endpoint, targeting the device by its IP/host.
|
||||
// service's /setup/tts/speak endpoint, targeting the device by its IP/host.
|
||||
func (app *WebApp) HandleAPISpeakText(w http.ResponseWriter, r *http.Request) {
|
||||
deviceID := chi.URLParam(r, "id")
|
||||
|
||||
@@ -83,7 +83,7 @@ func (app *WebApp) HandleAPISpeakText(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
upstream, err := http.NewRequestWithContext(r.Context(), http.MethodPost, serviceURL+"/mgmt/tts/speak", bytes.NewReader(body))
|
||||
upstream, err := http.NewRequestWithContext(r.Context(), http.MethodPost, serviceURL+"/setup/tts/speak", bytes.NewReader(body))
|
||||
if err != nil {
|
||||
app.sendError(w, "Failed to build TTS request", http.StatusInternalServerError)
|
||||
return
|
||||
@@ -91,10 +91,6 @@ func (app *WebApp) HandleAPISpeakText(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
upstream.Header.Set("Content-Type", "application/json")
|
||||
|
||||
if app.MgmtUsername != "" || app.MgmtPassword != "" {
|
||||
upstream.SetBasicAuth(app.MgmtUsername, app.MgmtPassword)
|
||||
}
|
||||
|
||||
resp, err := http.DefaultClient.Do(upstream)
|
||||
if err != nil {
|
||||
app.sendError(w, fmt.Sprintf("TTS service request failed: %v", err), http.StatusBadGateway)
|
||||
|
||||
@@ -79,7 +79,7 @@ func (app *WebApp) Mount(r chi.Router, discoveryService *discovery.UnifiedDiscov
|
||||
// Custom URL playback
|
||||
r.Post("/api/play-url/{id}", app.HandlePlayURL)
|
||||
|
||||
// Text-to-speech (proxied to the AfterTouch service's /mgmt/tts/speak)
|
||||
// Text-to-speech (proxied to the AfterTouch service's /setup/tts/speak)
|
||||
r.Post("/api/device-speak/{id}", app.HandleAPISpeakText)
|
||||
|
||||
// SPA routes — serve index.html for client-side routing
|
||||
|
||||
Reference in New Issue
Block a user