mirror of
https://github.com/gesellix/Bose-SoundTouch.git
synced 2026-09-07 15:07:17 +00:00
fix(bmx): correct misleading docs, restore TuneIn token request validation
HandleTuneInToken's docstring described the minted token as "fresh",
but datastore.GenerateSerialSecret("tunein") is a pure function of a
hardcoded literal -- it returns the identical value for every device
and every call, not a per-session secret. Correct the framing and
document why the constant value is safe today (Authorization gate
disabled for all TuneIn handlers, nothing validates uniqueness), so a
future change relying on per-device uniqueness doesn't get misled.
Also restore request-body validation dropped when the handler stopped
using the body's values: a genuine bootstrap call is still well-formed
JSON (confirmed against a captured real request), just with an empty
refresh_token, so decoding-but-discarding the body still rejects only
truly malformed requests with 400, without reintroducing the original
echo bug.
Also fixes 4 pre-existing wsl_v5 lint findings in the reordered
children-check in bmx/tunein.go (whitespace only, no behavior change).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
22d77c23f3
commit
cff94d5d96
@@ -214,15 +214,18 @@ func tuneInSectionsAshx(tuneInURI string, subsection *int) ([]models.BmxNavSecti
|
||||
|
||||
if children, ok := m["children"].([]interface{}); ok && len(children) > 0 {
|
||||
name, _ := m["text"].(string)
|
||||
|
||||
section := models.BmxNavSection{
|
||||
Name: name,
|
||||
Items: make([]models.BmxNavItem, 0, len(children)),
|
||||
}
|
||||
|
||||
for _, child := range children {
|
||||
cm, ok := child.(map[string]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
childType, _ := cm["type"].(string)
|
||||
if childType == "audio" {
|
||||
section.Items = append(section.Items, tuneInNavigatePlayItem(cm))
|
||||
@@ -230,7 +233,9 @@ func tuneInSectionsAshx(tuneInURI string, subsection *int) ([]models.BmxNavSecti
|
||||
section.Items = append(section.Items, tuneInNavigateLink(cm))
|
||||
}
|
||||
}
|
||||
|
||||
sections = append(sections, section)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
@@ -204,7 +204,7 @@ func TestHandleTuneInToken(t *testing.T) {
|
||||
defer ts.Close()
|
||||
|
||||
// Even when the speaker presents a refresh_token from a prior session,
|
||||
// the handler always mints a fresh token rather than echoing the input
|
||||
// the handler always mints its own token rather than echoing the input
|
||||
// back verbatim.
|
||||
payload := `{"grant_type":"refresh_token","refresh_token":"test-refresh-token"}`
|
||||
res, err := http.Post(ts.URL+"/bmx/tunein/v1/token", "application/json", strings.NewReader(payload))
|
||||
@@ -235,7 +235,7 @@ func TestHandleTuneInToken(t *testing.T) {
|
||||
t.Errorf("Expected access_token and refresh_token to match, got %q and %q", accessToken, refreshToken)
|
||||
}
|
||||
if accessToken == "test-refresh-token" {
|
||||
t.Error("Expected a freshly generated token, not an echo of the request's refresh_token")
|
||||
t.Error("Expected a minted token, not an echo of the request's refresh_token")
|
||||
}
|
||||
|
||||
embedded, ok := resp["_embedded"].(map[string]interface{})
|
||||
@@ -289,6 +289,29 @@ func TestHandleTuneInToken_Bootstrap(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestHandleTuneInToken_MalformedBodyRejected covers the request-validation
|
||||
// path that stayed in place alongside the unconditional-mint fix: a body
|
||||
// that isn't even valid JSON is not a normal bootstrap call (which is still
|
||||
// well-formed JSON, just with an empty/absent refresh_token — see
|
||||
// TestHandleTuneInToken_Bootstrap), so it should be rejected rather than
|
||||
// silently minting a token anyway.
|
||||
func TestHandleTuneInToken_MalformedBodyRejected(t *testing.T) {
|
||||
r, _ := setupRouter("http://localhost:8001", nil)
|
||||
|
||||
ts := httptest.NewServer(r)
|
||||
defer ts.Close()
|
||||
|
||||
res, err := http.Post(ts.URL+"/bmx/tunein/v1/token", "application/json", strings.NewReader("not json"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
if res.StatusCode != http.StatusBadRequest {
|
||||
t.Errorf("Expected status 400 for a malformed body, got %v", res.Status)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleTuneInPlayback_Authorized(t *testing.T) {
|
||||
r, _ := setupRouter("http://localhost:8001", nil)
|
||||
|
||||
|
||||
@@ -131,8 +131,30 @@ func (s *Server) HandleTuneInPlaybackPodcast(w http.ResponseWriter, r *http.Requ
|
||||
// ContentItem selection with INVALID_SOURCE, even though /sources reported
|
||||
// TUNEIN as READY (READY only reflects registry presence, not a live
|
||||
// account). Match HandleOrionToken's unconditional-generation shape
|
||||
// instead: always mint a fresh token, regardless of what the speaker sent.
|
||||
func (s *Server) HandleTuneInToken(w http.ResponseWriter, _ *http.Request) {
|
||||
// instead: always mint a token, regardless of what the speaker sent.
|
||||
//
|
||||
// The token itself is a stable, constant value (datastore.GenerateSerialSecret
|
||||
// is a pure function of the hardcoded "tunein" literal), not a fresh or
|
||||
// per-device secret — it's the same value for every device and every call.
|
||||
// That's fine today only because the Authorization gate is disabled for all
|
||||
// TuneIn handlers (see HandleTuneInReport below) and nothing validates the
|
||||
// token's uniqueness; if either of those ever changes, this would need a
|
||||
// real per-device/per-session token instead.
|
||||
func (s *Server) HandleTuneInToken(w http.ResponseWriter, r *http.Request) {
|
||||
// The unconditional mint above means we never use the decoded values,
|
||||
// but we still decode the body so a genuinely malformed request (not a
|
||||
// normal bootstrap call, which is valid JSON with an empty/absent
|
||||
// refresh_token) gets a 400 instead of silently succeeding.
|
||||
var req struct {
|
||||
GrantType string `json:"grant_type"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
}
|
||||
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
http.Error(w, "Invalid request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
token := datastore.GenerateSerialSecret("tunein")
|
||||
|
||||
resp := map[string]interface{}{
|
||||
|
||||
Reference in New Issue
Block a user