fix(bmx): restore the Authorization gate on /core02/.../orion/station

f3a4658 dropped the auth check on HandleOrionPlayback while moving
the orion routes to their registry-advertised paths. The rationale at
the time was "data is the speaker's own input, nothing privileged"
and parity with soundcork's reference impl.

On reflection, requiring the Authorization header is the right
default here for two reasons:

  1. Parity with the rest of our BMX playback surface (TuneIn
     variants — see TestBMXUnauthorized's table — all gate on a
     non-empty Authorization header). Orion being the lone unguarded
     exception was a footgun, not a feature.
  2. Real speakers obtain a Bearer token via the orion
     /token endpoint before they follow a LOCAL_INTERNET_RADIO
     preset, so the gate doesn't cost any legitimate caller. A
     callerless GET (curl, scraper, casual probe) gets a clean 401
     instead of a working playback resolver.

The check itself is the same shape as the other BMX handlers:
empty Authorization header → s.writeBMXUnauthorized → 401. Token
contents are not validated, only presence — sufficient for the
parity contract.

Test side:

  - TestOrionPlayback regains its Bearer header (it had one before
    the GET-method switch in f3a4658).
  - TestBMXUnauthorized's table regains a sibling row for the orion
    station endpoint with the GET + query-string shape.
  - TestIssue218_OrionStationResolvesPresetStreamURL sends a Bearer
    header on the loop-closing GET — added with a doc comment
    naming the orion /token bootstrap a real speaker would do.

No route-table changes; the registry advertisement and route paths
from f3a4658 stay as they are.

Refs #218.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Tobias Gesellchen
2026-05-15 14:13:24 +02:00
co-authored by Claude Opus 4.7
parent 0e10bfcb14
commit 673be16f4f
3 changed files with 24 additions and 9 deletions
+11 -3
View File
@@ -182,10 +182,18 @@ func (s *Server) HandleOrionToken(w http.ResponseWriter, _ *http.Request) {
// rewrap it into the Bose BmxPlaybackResponse shape via
// bmx.PlayCustomStream.
//
// No auth check: the data is the speaker's own input, nothing
// privileged is being protected, and soundcork's reference impl
// behaves the same way.
// Requires a Bearer token in the `Authorization` header — same as
// the rest of the BMX playback surface (TuneIn variants and the
// orion token endpoint). Real speakers obtain the token via
// POST /core02/svc-bmx-adapter-orion/prod/orion/token (HandleOrionToken)
// before they ever follow a LOCAL_INTERNET_RADIO preset, so this
// check shouldn't cost any legitimate caller.
func (s *Server) HandleOrionPlayback(w http.ResponseWriter, r *http.Request) {
if r.Header.Get("Authorization") == "" {
s.writeBMXUnauthorized(w)
return
}
data := r.URL.Query().Get("data")
resp, err := bmx.PlayCustomStream(data)
+2 -5
View File
@@ -94,6 +94,7 @@ func TestOrionPlayback(t *testing.T) {
// emits (Go's url package re-encodes any `=` padding for transport).
req, _ := http.NewRequest("GET",
ts.URL+"/core02/svc-bmx-adapter-orion/prod/orion/station?data="+url.QueryEscape(data), nil)
req.Header.Set("Authorization", "Bearer mock-token")
res, err := http.DefaultClient.Do(req)
if err != nil {
t.Fatal(err)
@@ -170,11 +171,7 @@ func TestBMXUnauthorized(t *testing.T) {
{"GET", "/bmx/tunein/v1/playback/station/s123"},
{"GET", "/bmx/tunein/v1/playback/episodes/p123"},
{"GET", "/bmx/tunein/v1/playback/episode/p123"},
// Note: /core02/.../prod/orion/station is intentionally NOT in this
// list — orion playback takes its `data` blob from the speaker's
// own preset payload, there's no privileged material to gate, and
// soundcork's reference impl makes the same call (no auth on the
// station endpoint). See HandleOrionPlayback's doc comment.
{"GET", "/core02/svc-bmx-adapter-orion/prod/orion/station?data=AAAA"},
}
for _, tc := range paths {
@@ -79,7 +79,17 @@ func TestIssue218_OrionStationResolvesPresetStreamURL(t *testing.T) {
resolved := ts.URL + parsedLocation.RequestURI()
resp, err := http.Get(resolved) //nolint:noctx
// Real speakers retrieve an orion token from
// POST /core02/svc-bmx-adapter-orion/prod/orion/token before they
// ever follow a LOCAL_INTERNET_RADIO preset; the playback handler
// rejects an empty Authorization header for parity with the other
// BMX playback routes. Use a sentinel Bearer token to match that
// shape — HandleOrionPlayback doesn't validate the token contents,
// only its presence.
req, _ := http.NewRequest("GET", resolved, nil)
req.Header.Set("Authorization", "Bearer mock-token")
resp, err := http.DefaultClient.Do(req)
if err != nil {
t.Fatalf("GET %s: %v", resolved, err)
}