Erst die Sitzung, dann der Schluessel

Der Schluessel kennt keinen Nutzer und landete beim Standardnutzer — auch
dann, wenn die App unter derselben Site liegt und der Browser das
HoneyOS-Cookie laengst mitschickt (cloud.honeyos.… und honeyos.… sind
dieselbe Site, duckdns.org steht auf der Public-Suffix-Liste). Damit trug
die HoneyBar in Aether die Farben eines fremden Nutzers.

Jetzt sieht /api/apps erst nach der Sitzung, und nur bei eigener
Anmeldung: Im offenen Betrieb und hinter einem Anmelde-Proxy gilt jede
Anfrage als angemeldet, dort bleibt der Schluessel das letzte Wort.
Test dazu, mit zwei Nutzern, damit der Rueckfall auf den einzigen Nutzer
das Ergebnis nicht zufaellig richtig aussehen laesst.
This commit is contained in:
marc committed 2026-08-29 23:29:53 +02:00
1 parent ffaba616ce
commit cc7a50c621
4 files changed
+111 -14

No files matched your search

+14 -9
View File
@@ -26,9 +26,11 @@ hatte notiert, das scheitere daran, dass der Browser die Sitzung nicht mitschick
das Aussehen gleich mit. Deshalb liefert `/api/apps` jetzt `{apps, settings}` das Aussehen gleich mit. Deshalb liefert `/api/apps` jetzt `{apps, settings}`
statt nur einer Liste. statt nur einer Liste.
Offen bleibt: Bei mehreren Nutzern trägt die HoneyBar die Farbe des Offen war: Bei mehreren Nutzern trägt die HoneyBar die Farbe des
Standardnutzers, weil ihr über die Site-Grenze niemand sagen kann, wer da Standardnutzers, weil ihr über die Site-Grenze niemand sagen kann, wer da
gerade schaut. gerade schaut. **Für Apps unter derselben Site erledigt seit dem 29.08.2026** —
`/api/apps` sieht jetzt erst nach der Sitzung und erst dann nach dem Schlüssel.
Über eine echte Site-Grenze bleibt es, wie es war.
### 1.2 Systemmenü im Logo — ✅ gebaut (29.08.2026) ### 1.2 Systemmenü im Logo — ✅ gebaut (29.08.2026)
@@ -153,8 +155,11 @@ Zwei Gründe, trotzdem mit der gemeinsamen anzufangen:
Standardnutzers, während der Schreibtisch die eigene zeigt — derselbe Bruch Standardnutzers, während der Schreibtisch die eigene zeigt — derselbe Bruch
wie heute bei den Farben. wie heute bei den Farben.
→ **Zusammen mit „Persönliche Apps je Nutzer" zu bauen, und beides hängt an → **Zusammen mit „Persönliche Apps je Nutzer" zu bauen.** Die Bedingung dafür
derselben Bedingung: ein zweiter Nutzer oder alles unter einer Domain.** ist am 29.08.2026 weggefallen: Aether und InkNote liegen längst unter
`*.honeyos.duckdns.org`, also unter derselben Site, und `/api/apps` sieht seither
erst nach der Sitzung. **Die HoneyBar weiß dort jetzt, wer schaut.** Was bleibt,
ist die Frage, ob es sich bei einem Nutzer lohnt.
### 1.4 HoneyOS selbst als PWA ### 1.4 HoneyOS selbst als PWA
@@ -511,11 +516,11 @@ Schlüssel für die App-Liste weiß nicht, wer schaut. Die HoneyBar zeigte also
gemeinsamen Apps, der Schreibtisch zusätzlich die eigenen. Zwei verschiedene gemeinsamen Apps, der Schreibtisch zusätzlich die eigenen. Zwei verschiedene
Listen an zwei Stellen ist schlechter als eine. Listen an zwei Stellen ist schlechter als eine.
**Liegt alles unter derselben Domain** (`os.beispiel.duckdns.org` + **Liegt alles unter derselben Domain**, fällt die Grenze weg: die Sitzung wird
`cloud.beispiel.duckdns.org`), fällt die Grenze weg: die Sitzung wird mitgeschickt, die HoneyBar weiß, wer da ist. **Das ist seit dem 29.08.2026 der
mitgeschickt, die HoneyBar weiß, wer da ist. Seit dem Umzug auf Fall** — Aether liegt unter `cloud.honeyos.duckdns.org`, HoneyOS unter
`os.asianxpolish.duckdns.org` ist genau das der Fall — persönliche Apps sind `honeyos.duckdns.org`, und `/api/apps` sieht erst nach der Sitzung und dann
damit machbar geworden. erst nach dem Schlüssel. Persönliche Apps sind damit machbar geworden.
→ **Vorschlag: zurückstellen, bis es mehr als ein Konto gibt.** Bei einer → **Vorschlag: zurückstellen, bis es mehr als ein Konto gibt.** Bei einer
Person ist „persönlich" und „für alle" dasselbe. Der Weg ist aber offen und Person ist „persönlich" und „für alle" dasselbe. Der Weg ist aber offen und
+18 -5
View File
@@ -90,12 +90,25 @@ Zweite Schranke, unabhängig davon: HoneyOS gibt die Liste nur an Ursprünge
heraus, die **selbst als App eingetragen** sind. Eine fremde Seite scheitert heraus, die **selbst als App eingetragen** sind. Eine fremde Seite scheitert
also an CORS, selbst mit richtigem Schlüssel. also an CORS, selbst mit richtigem Schlüssel.
**Was auf zwei Sites nicht geht:** der Anmeldestand auf den Kacheln des **Erst die Sitzung, dann der Schlüssel.** Bringt die Anfrage ein gültiges
Schreibtischs (fragt über die Domaingrenze). Der Name *in der HoneyBar* geht HoneyOS-Cookie mit, antwortet der Server mit den Einstellungen **dieses**
weiterhin — den holt sie bei der App selbst, gleicher Ursprung. Nutzers; der Schlüssel wird dann gar nicht erst angesehen. Er ist die Krücke
für den Fall, dass das Cookie nicht ankommen kann — und darf eine echte
Anmeldung nicht überstimmen, denn er kennt keinen Nutzer und landet beim
Standardnutzer.
Liegen alle Adressen unter derselben Domain (`os.honeyy…`, `cloud.honeyy…`), Ob das Cookie ankommt, entscheidet die **Site**, nicht die Adresse: Weil
entfällt der Schlüssel und alles funktioniert ohne Einschränkung. `duckdns.org` auf der Public-Suffix-Liste steht, sind `honeyos.duckdns.org` und
`cloud.honeyos.duckdns.org` **dieselbe** Site (Cookie fließt),
`honeyy.duckdns.org` dagegen eine fremde (Cookie fließt nicht).
| | Cookie | Folge |
|---|---|---|
| App unter derselben Site | kommt an | eigene Röhre, eigenes Wetter, `data-key` überflüssig |
| App auf fremder Site | bleibt weg | Schlüssel nötig, HoneyBar trägt die Farben des Standardnutzers |
Zweiteres gilt auch für den Anmeldestand auf den Kacheln des Schreibtischs: Der
fragt über die Domaingrenze und bleibt dort leer.
## Apps anlegen ## Apps anlegen
+63
View File
@@ -483,6 +483,69 @@ func TestAppListeBrauchtDenSchlüssel(t *testing.T) {
} }
} }
// Liegt die App unter derselben Site, kommt die Sitzung mit — dann muss die
// HoneyBar die Einstellungen des Angemeldeten bekommen und nicht die des
// Standardnutzers. Der Schlüssel darf dabei ruhig mitgeschickt werden: Er ist
// die Krücke für den fremden Fall und darf die Sitzung nicht überstimmen.
func TestSitzungSchlaegtSchluessel(t *testing.T) {
idp := newFakeIdP(t, "honeyos")
s := newTestServer(t, idp.URL)
s.cfg.AppsKey = "geheim123"
s.auth = auth.New(s.cfg)
h := s.routes()
sess, csrf := signIn(t, h)
// Der Angemeldete stellt seine Röhre auf grün.
req := httptest.NewRequest("PUT", "/api/settings", strings.NewReader(`{"phosphor":"green"}`))
req.Header.Set("X-CSRF-Token", csrf.Value)
req.AddCookie(sess)
req.AddCookie(csrf)
rec := httptest.NewRecorder()
h.ServeHTTP(rec, req)
if rec.Code != http.StatusNoContent {
t.Fatalf("Einstellungen speichern: %d", rec.Code)
}
// Ein zweiter Nutzer mit anderer Röhre — sonst könnte der Rückfall auf den
// „einzigen Nutzer" das Ergebnis zufällig richtig aussehen lassen.
if err := s.store.SetSettings("jemand-anders", map[string]string{"phosphor": "ice"}); err != nil {
t.Fatal(err)
}
hole := func(mod func(*http.Request)) map[string]any {
req := httptest.NewRequest("GET", "/api/apps", nil)
mod(req)
rec := httptest.NewRecorder()
h.ServeHTTP(rec, req)
if rec.Code != 200 {
t.Fatalf("/api/apps: %d", rec.Code)
}
var out map[string]any
if err := json.Unmarshal(rec.Body.Bytes(), &out); err != nil {
t.Fatal(err)
}
return out
}
mitBeidem := hole(func(r *http.Request) {
r.AddCookie(sess)
r.Header.Set("X-HoneyOS-Key", "geheim123")
})
if got := mitBeidem["settings"].(map[string]any)["phosphor"]; got != "green" {
t.Errorf("mit Sitzung und Schlüssel: %v, erwartet green (die des Angemeldeten)", got)
}
// Ohne Sitzung bleibt der Schlüssel der Weg — und landet beim
// Standardnutzer. Bei zwei Nutzern heißt das: kein Rateweg, leere Menge.
nurSchluessel := hole(func(r *http.Request) {
r.Header.Set("X-HoneyOS-Key", "geheim123")
})
if got := nurSchluessel["settings"].(map[string]any)["phosphor"]; got == "green" {
t.Errorf("nur mit Schlüssel: %v — der Schlüssel kennt keinen Nutzer und darf keinen raten", got)
}
}
// Der Schlüssel öffnet nur das Lesen der Liste, nicht das Schreiben. // Der Schlüssel öffnet nur das Lesen der Liste, nicht das Schreiben.
func TestSchlüsselDarfNichtSchreiben(t *testing.T) { func TestSchlüsselDarfNichtSchreiben(t *testing.T) {
idp := newFakeIdP(t, "honeyos") idp := newFakeIdP(t, "honeyos")
+16
View File
@@ -250,6 +250,22 @@ func noCache(next http.Handler) http.Handler {
// damit nur für den lesbar, der ohnehin an die App herankommt. // damit nur für den lesbar, der ohnehin an die App herankommt.
func (s *Server) appsReadable(next func(http.ResponseWriter, *http.Request, string)) http.HandlerFunc { func (s *Server) appsReadable(next func(http.ResponseWriter, *http.Request, string)) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
// Erst die Sitzung, dann der Schlüssel. Umgekehrt wäre bequemer zu
// lesen, aber falsch: Der Schlüssel kennt keinen Nutzer und landet
// beim Standardnutzer. Liegt die App unter derselben Site wie HoneyOS
// (`cloud.honeyos.…` und `honeyos.…` sind dieselbe), schickt der
// Browser die Sitzung längst mit — dann soll die HoneyBar dort auch
// die Farben, das Wetter und später die Reihenfolge **dieses** Nutzers
// bekommen und nicht die eines fremden.
//
// Nur bei eigener Anmeldung: Ohne sie gilt jede Anfrage als angemeldet
// (offener Betrieb, Proxy davor), und das hier wäre eine offene Tür.
if s.auth.Enabled() {
if sub, ok := s.auth.SubFromRequest(r); ok {
next(w, r, sub)
return
}
}
if key := s.cfg.AppsKey; key != "" { if key := s.cfg.AppsKey; key != "" {
given := r.Header.Get("X-HoneyOS-Key") given := r.Header.Get("X-HoneyOS-Key")
if given == "" { if given == "" {