From b4dcb744043390e36427db103023c3c08d85aca7 Mon Sep 17 00:00:00 2001 From: marc Date: Mon, 24 Aug 2026 22:47:19 +0200 Subject: [PATCH] =?UTF-8?q?fix:=20das=20Aussehen=20erreicht=20die=20Leiste?= =?UTF-8?q?=20auch=20=C3=BCber=20die=20Site-Grenze?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Marcs Frage: gilt die Einstellung vom Rechner auch für die Leiste am Handy? Sie hätte es nicht getan, sobald HONEYOS_USER_HEADER gesetzt ist: die Einstellungen liegen dann unter "proxy:marc", die Leiste fragt aber ohne Sitzung und landet beim Standardnutzer — dessen Satz ist leer, die Leiste wäre bernsteinfarben geblieben, während der Schreibtisch grün leuchtet. SettingsFor() hilft nach: gibt es genau einen Nutzer mit Einstellungen, gelten dessen. Bei mehreren bleibt es beim Standard, dann ist nicht zu erraten, wer da schaut. Nachgestellt mit einem Pixel-7-Profil ohne jedes Cookie: die Leiste kommt grün an. Co-Authored-By: Claude Opus 5 --- auth_test.go | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ main.go | 4 ++-- store.go | 35 +++++++++++++++++++++++++++++++++++ 3 files changed, 88 insertions(+), 2 deletions(-) diff --git a/auth_test.go b/auth_test.go index a206509..e28d582 100644 --- a/auth_test.go +++ b/auth_test.go @@ -595,3 +595,54 @@ func TestVersionKommtVomServer(t *testing.T) { t.Error("os.js trägt wieder eine eigene Version — die läuft mit den Image-Tags auseinander") } } + +// Der Fall, nach dem Marc gefragt hat: Einstellungen am Rechner setzen, und die +// Leiste am Handy trägt dieselbe Farbe. Sie fragt über die Site-Grenze, landet +// also beim Standardnutzer — die Einstellungen können aber unter einem Namen +// aus der Proxy-Kopfzeile liegen. +func TestLeisteFindetDasAussehenAuchÜberDieSiteGrenze(t *testing.T) { + s := proxyServer(t, Config{ProxyAuth: true, UserHeader: "Remote-User", AppsKey: "geheim123"}) + h := s.routes() + + // Am Rechner, durch den Proxy: grün. + req := httptest.NewRequest("PUT", "/api/settings", strings.NewReader(`{"phosphor":"green"}`)) + req.Header.Set("Remote-User", "marc") + rec := httptest.NewRecorder() + h.ServeHTTP(rec, req) + if rec.Code != http.StatusNoContent { + t.Fatalf("speichern: %d", rec.Code) + } + + // Am Handy fragt die Leiste nur mit dem Schlüssel — ohne Namen. + req = httptest.NewRequest("GET", "/api/apps", nil) + req.Header.Set("X-HoneyOS-Key", "geheim123") + rec = httptest.NewRecorder() + h.ServeHTTP(rec, req) + + var got struct{ Settings map[string]string } + _ = json.Unmarshal(rec.Body.Bytes(), &got) + if got.Settings["phosphor"] != "green" { + t.Fatalf("die Leiste bekäme %q statt grün — auf dem Handy bliebe sie bernstein", got.Settings["phosphor"]) + } +} + +// Bei mehreren Nutzern darf nicht geraten werden, wessen Farbe gilt. +func TestBeiMehrerenNutzernWirdNichtGeraten(t *testing.T) { + s := proxyServer(t, Config{ProxyAuth: true, UserHeader: "Remote-User", AppsKey: "geheim123"}) + for name, farbe := range map[string]string{"marc": "green", "gast": "ice"} { + if err := s.store.SetSettings("proxy:"+name, map[string]string{"phosphor": farbe}); err != nil { + t.Fatal(err) + } + } + + req := httptest.NewRequest("GET", "/api/apps", nil) + req.Header.Set("X-HoneyOS-Key", "geheim123") + rec := httptest.NewRecorder() + s.routes().ServeHTTP(rec, req) + + var got struct{ Settings map[string]string } + _ = json.Unmarshal(rec.Body.Bytes(), &got) + if got.Settings["phosphor"] != "" { + t.Errorf("es wurde geraten: %q", got.Settings["phosphor"]) + } +} diff --git a/main.go b/main.go index 5eb4060..1028b99 100644 --- a/main.go +++ b/main.go @@ -27,7 +27,7 @@ const localSub = "local" // Version ist die einzige Stelle, an der sie steht. build.sh liest sie hier aus // und vergibt denselben Docker-Tag; die Oberfläche holt sie über /api/public. // Vorher stand sie zusätzlich in os.js und lief mit den Image-Tags auseinander. -const Version = "0.5" +const Version = "0.6" type Config struct { Addr string @@ -335,7 +335,7 @@ func (s *Server) handleSetSettings(w http.ResponseWriter, r *http.Request, sub s func (s *Server) handleApps(w http.ResponseWriter, r *http.Request, sub string) { writeJSON(w, http.StatusOK, map[string]any{ "apps": s.store.Apps(), - "settings": s.store.User(sub).Settings, + "settings": s.store.SettingsFor(sub), }) } diff --git a/store.go b/store.go index c0463ac..7c55ef6 100644 --- a/store.go +++ b/store.go @@ -208,6 +208,41 @@ func (s *Store) SetSettings(sub string, in map[string]string) error { return s.save() } +// SettingsFor liefert das Aussehen für sub — und hilft nach, wenn die Frage +// über eine Site-Grenze kam. +// +// Die Leiste in einer fremden App weist sich nur mit dem Schlüssel aus, nicht +// mit einer Sitzung: der Browser schickt das Cookie dorthin nicht mit. Sie +// landet deshalb beim Standardnutzer. Sind die Einstellungen aber unter einem +// Namen aus der Proxy-Kopfzeile gespeichert, wäre der leer — und die Leiste +// bliebe bernsteinfarben, während der Schreibtisch grün leuchtet. +// +// Gibt es genau einen Nutzer mit Einstellungen, gilt dessen Aussehen. Bei +// mehreren bleibt es beim Standard: dann ist nicht zu erraten, wer da schaut. +func (s *Store) SettingsFor(sub string) map[string]string { + if own := s.User(sub).Settings; len(own) > 0 { + return own + } + + s.mu.RLock() + defer s.mu.RUnlock() + var einziger map[string]string + for _, u := range s.d.Users { + if len(u.Settings) == 0 { + continue + } + if einziger != nil { + return map[string]string{} // mehrere — kein Rateweg + } + einziger = u.Settings + } + out := map[string]string{} + for k, v := range einziger { + out[k] = v + } + return out +} + // AppOrigins nennt die Ursprünge der eingetragenen Apps. Nur sie dürfen die // Leiste mit Anmeldedaten abfragen — die Liste ist damit ihre eigene Freigabe. func (s *Store) AppOrigins() map[string]bool {