mirror of
https://github.com/gesellix/Bose-SoundTouch.git
synced 2026-08-18 08:36:13 +00:00
feat(alexa): stub POST /alexa/certificate with 501 and add voice.api.bose.io to DNS (#200)
Registers HandleAlexaCertificate on POST /alexa/certificate. The handler logs the device MAC from the request body and returns 501 Not Implemented with a JSON error explaining that AWS IoT integration is required to provision Alexa device certificates. Adds voice.api.bose.io to both /etc/hosts domain lists in setup.go (DNS intercept was already covered by the bose.io wildcard entry in dns.go). Relates to https://github.com/gesellix/Bose-SoundTouch/discussions/84 Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
e99c04888c
commit
16ab9dbba1
@@ -851,6 +851,7 @@ func setupRouter(server *handlers.Server) *chi.Mux {
|
||||
r.Get("/bmx-icons/*", server.HandleBmxIcons())
|
||||
r.Get("/ced/*", server.HandleCedStatic())
|
||||
r.Get("/web/*", server.HandleWeb())
|
||||
r.Post("/alexa/certificate", server.HandleAlexaCertificate)
|
||||
r.Get("/docs/*", server.HandleDocs)
|
||||
|
||||
r.Route("/bmx", func(r chi.Router) {
|
||||
|
||||
@@ -94,6 +94,7 @@ POST /accounts/{account}/devices/{device}/presets/{presetNumber} handlers.(
|
||||
POST /accounts/{account}/devices/{device}/recents handlers.(*Server).HandleMargeAddRecent-fm
|
||||
POST /accounts/{account}/group handlers.(*Server).HandleMargeAddGroup-fm
|
||||
POST /accounts/{account}/group/{groupId} handlers.(*Server).HandleMargeModifyGroup-fm
|
||||
POST /alexa/certificate handlers.(*Server).HandleAlexaCertificate-fm
|
||||
POST /bmx/core02/svc-bmx-adapter-orion/prod/orion/token handlers.(*Server).HandleOrionToken-fm
|
||||
POST /bmx/orion/v1/playback/station/{data} handlers.(*Server).HandleOrionPlayback-fm
|
||||
POST /bmx/tunein/v1/favorite/{stationID} handlers.(*Server).HandleTuneInFavorite-fm
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// HandleAlexaCertificate handles POST /alexa/certificate.
|
||||
//
|
||||
// The speaker sends a CSR (PEM, URL-form-encoded as "csr") and a JSON "data" field
|
||||
// containing a Bearer token, device MAC address, device type, and AWS region.
|
||||
// The real voice.api.bose.io endpoint forwards the CSR to AWS IoT, which signs it
|
||||
// and returns a device certificate, the account's IoT endpoint URL, and a client ID.
|
||||
// The speaker uses these to establish a persistent MQTT connection to Alexa IoT.
|
||||
//
|
||||
// Full implementation requires an AWS IoT integration:
|
||||
// - Parse the CSR from the form body
|
||||
// - Exchange it via the AWS IoT CreateKeysAndCertificate or RegisterThing API
|
||||
// - Return {"certificatePem": "...", "iot_endpoint": "...", "client_id": "..."}
|
||||
//
|
||||
// Until implemented, Alexa voice control will not work after cloud shutdown.
|
||||
func (s *Server) HandleAlexaCertificate(w http.ResponseWriter, r *http.Request) {
|
||||
device := ""
|
||||
|
||||
if err := r.ParseForm(); err == nil {
|
||||
if data := r.FormValue("data"); data != "" {
|
||||
var d struct {
|
||||
Device string `json:"device"`
|
||||
}
|
||||
if err := json.Unmarshal([]byte(data), &d); err == nil {
|
||||
device = d.Device
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("[alexa] certificate provisioning not implemented (device=%s); Alexa voice control requires AWS IoT integration", device)
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusNotImplemented)
|
||||
_, _ = w.Write([]byte(`{"error":"not_implemented","message":"Alexa IoT certificate provisioning requires AWS IoT integration. See voice.api.bose.io /alexa/certificate handler."}`))
|
||||
}
|
||||
@@ -417,7 +417,7 @@ func (m *mockSSH) Run(command string) (string, error) {
|
||||
m.runCount++
|
||||
if m.runCount > 1 {
|
||||
// Return updated hosts for verification
|
||||
return "127.0.0.1 localhost\n192.168.1.100\tstreaming.bose.com\n192.168.1.100\tupdates.bose.com\n192.168.1.100\tstats.bose.com\n192.168.1.100\tbmx.bose.com\n192.168.1.100\tcontent.api.bose.io\n192.168.1.100\tevents.api.bosecm.com\n192.168.1.100\tbose-prod.apigee.net\n192.168.1.100\tworldwide.bose.com\n192.168.1.100\tmedia.bose.io\n192.168.1.100\tdownloads.bose.com", nil
|
||||
return "127.0.0.1 localhost\n192.168.1.100\tstreaming.bose.com\n192.168.1.100\tupdates.bose.com\n192.168.1.100\tstats.bose.com\n192.168.1.100\tbmx.bose.com\n192.168.1.100\tcontent.api.bose.io\n192.168.1.100\tevents.api.bosecm.com\n192.168.1.100\tbose-prod.apigee.net\n192.168.1.100\tworldwide.bose.com\n192.168.1.100\tmedia.bose.io\n192.168.1.100\tdownloads.bose.com\n192.168.1.100\tvoice.api.bose.io", nil
|
||||
}
|
||||
return "127.0.0.1 localhost", nil
|
||||
}
|
||||
|
||||
@@ -280,6 +280,7 @@ func (m *Manager) GetMigrationSummary(deviceIP, targetURL, proxyURL string, opti
|
||||
"music.api.bose.com",
|
||||
"media.bose.io",
|
||||
"downloads.bose.com",
|
||||
"voice.api.bose.io",
|
||||
}
|
||||
|
||||
var hostsLines []string
|
||||
@@ -1065,6 +1066,7 @@ func (m *Manager) migrateViaHosts(deviceIP, targetURL string) (string, error) {
|
||||
"worldwide.bose.com",
|
||||
"media.bose.io",
|
||||
"downloads.bose.com",
|
||||
"voice.api.bose.io",
|
||||
}
|
||||
|
||||
hostsContent, err := client.Run("cat /etc/hosts")
|
||||
|
||||
@@ -54,7 +54,7 @@ func TestMigrateViaHosts(t *testing.T) {
|
||||
if command == "cat /etc/hosts" {
|
||||
// Handle both initial read and verification read
|
||||
if len(runCalls) > 2 { // Rough heuristic: verification happens after upload
|
||||
return "192.168.1.100\tstreaming.bose.com\n192.168.1.100\tupdates.bose.com\n192.168.1.100\tstats.bose.com\n192.168.1.100\tbmx.bose.com\n192.168.1.100\tcontent.api.bose.io\n192.168.1.100\tevents.api.bosecm.com\n192.168.1.100\tbose-prod.apigee.net\n192.168.1.100\tworldwide.bose.com\n192.168.1.100\tmedia.bose.io\n192.168.1.100\tdownloads.bose.com", nil
|
||||
return "192.168.1.100\tstreaming.bose.com\n192.168.1.100\tupdates.bose.com\n192.168.1.100\tstats.bose.com\n192.168.1.100\tbmx.bose.com\n192.168.1.100\tcontent.api.bose.io\n192.168.1.100\tevents.api.bosecm.com\n192.168.1.100\tbose-prod.apigee.net\n192.168.1.100\tworldwide.bose.com\n192.168.1.100\tmedia.bose.io\n192.168.1.100\tdownloads.bose.com\n192.168.1.100\tvoice.api.bose.io", nil
|
||||
}
|
||||
return "127.0.0.1 localhost", nil
|
||||
}
|
||||
@@ -132,7 +132,7 @@ func TestMigrateViaHosts_UpdateExisting(t *testing.T) {
|
||||
runCount++
|
||||
if command == "cat /etc/hosts" {
|
||||
if runCount > 1 {
|
||||
return "127.0.0.1 localhost\n192.168.1.100\tstreaming.bose.com\n192.168.1.100\tupdates.bose.com\n192.168.1.100\tstats.bose.com\n192.168.1.100\tbmx.bose.com\n192.168.1.100\tcontent.api.bose.io\n192.168.1.100\tevents.api.bosecm.com\n192.168.1.100\tbose-prod.apigee.net\n192.168.1.100\tworldwide.bose.com\n192.168.1.100\tmedia.bose.io\n192.168.1.100\tdownloads.bose.com", nil
|
||||
return "127.0.0.1 localhost\n192.168.1.100\tstreaming.bose.com\n192.168.1.100\tupdates.bose.com\n192.168.1.100\tstats.bose.com\n192.168.1.100\tbmx.bose.com\n192.168.1.100\tcontent.api.bose.io\n192.168.1.100\tevents.api.bosecm.com\n192.168.1.100\tbose-prod.apigee.net\n192.168.1.100\tworldwide.bose.com\n192.168.1.100\tmedia.bose.io\n192.168.1.100\tdownloads.bose.com\n192.168.1.100\tvoice.api.bose.io", nil
|
||||
}
|
||||
return "127.0.0.1 localhost\n1.2.3.4\tstreaming.bose.com\n1.2.3.4\tupdates.bose.com", nil
|
||||
}
|
||||
@@ -648,7 +648,7 @@ func TestMigrateViaHosts_SkipCAIfTrusted(t *testing.T) {
|
||||
if command == "cat /etc/hosts" {
|
||||
// Handle both initial read and verification read
|
||||
if len(runCalls) > 2 { // Rough heuristic: verification happens after upload
|
||||
return "192.168.1.100\tstreaming.bose.com\n192.168.1.100\tupdates.bose.com\n192.168.1.100\tstats.bose.com\n192.168.1.100\tbmx.bose.com\n192.168.1.100\tcontent.api.bose.io\n192.168.1.100\tevents.api.bosecm.com\n192.168.1.100\tbose-prod.apigee.net\n192.168.1.100\tworldwide.bose.com\n192.168.1.100\tmedia.bose.io\n192.168.1.100\tdownloads.bose.com", nil
|
||||
return "192.168.1.100\tstreaming.bose.com\n192.168.1.100\tupdates.bose.com\n192.168.1.100\tstats.bose.com\n192.168.1.100\tbmx.bose.com\n192.168.1.100\tcontent.api.bose.io\n192.168.1.100\tevents.api.bosecm.com\n192.168.1.100\tbose-prod.apigee.net\n192.168.1.100\tworldwide.bose.com\n192.168.1.100\tmedia.bose.io\n192.168.1.100\tdownloads.bose.com\n192.168.1.100\tvoice.api.bose.io", nil
|
||||
}
|
||||
return "127.0.0.1 localhost", nil
|
||||
}
|
||||
|
||||
@@ -18,11 +18,11 @@ These hosts are now in the DNS redirect list and the service handles them native
|
||||
| media.bose.io | `GET /bmx-icons/tunein/smallSvg.svg` | `HandleBmxIcons` | already in `static/media/bmx-icons/tunein/` |
|
||||
| media.bose.io | `GET /bmx-icons/tunein/top-menu/*.png` | `HandleBmxIcons` | 6 PNGs embedded (bubble, location, microphone, news, note, podcasts); speaker.png was 403 from CDN |
|
||||
|
||||
## Remaining: external host requiring action
|
||||
## Stub implemented — requires AWS IoT integration to complete
|
||||
|
||||
| host | request | notes |
|
||||
|-------------------|---------------------------|-----------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| voice.api.bose.io | `POST /alexa/certificate` | Alexa IoT cert provisioning; CSR + auth token → AWS IoT cert + endpoint; not in DNS redirect list; Alexa setup will fail after shutdown |
|
||||
| host | request | handler | notes |
|
||||
|-------------------|---------------------------|--------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| voice.api.bose.io | `POST /alexa/certificate` | `HandleAlexaCertificate` | Returns 501; logs device MAC. Full impl needs AWS IoT: parse CSR + token, call RegisterThing/CreateKeysAndCertificate, return cert + iot_endpoint. |
|
||||
|
||||
## Third-party analytics (no action needed)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user