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:
1 parent
ffaba616ce
commit
cc7a50c621
4 files changed
+111
-14
No files matched your search
@@ -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}`
|
||||
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
|
||||
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)
|
||||
|
||||
@@ -153,8 +155,11 @@ Zwei Gründe, trotzdem mit der gemeinsamen anzufangen:
|
||||
Standardnutzers, während der Schreibtisch die eigene zeigt — derselbe Bruch
|
||||
wie heute bei den Farben.
|
||||
|
||||
→ **Zusammen mit „Persönliche Apps je Nutzer" zu bauen, und beides hängt an
|
||||
derselben Bedingung: ein zweiter Nutzer oder alles unter einer Domain.**
|
||||
→ **Zusammen mit „Persönliche Apps je Nutzer" zu bauen.** Die Bedingung dafür
|
||||
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
|
||||
|
||||
@@ -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
|
||||
Listen an zwei Stellen ist schlechter als eine.
|
||||
|
||||
**Liegt alles unter derselben Domain** (`os.beispiel.duckdns.org` +
|
||||
`cloud.beispiel.duckdns.org`), fällt die Grenze weg: die Sitzung wird
|
||||
mitgeschickt, die HoneyBar weiß, wer da ist. Seit dem Umzug auf
|
||||
`os.asianxpolish.duckdns.org` ist genau das der Fall — persönliche Apps sind
|
||||
damit machbar geworden.
|
||||
**Liegt alles unter derselben Domain**, fällt die Grenze weg: die Sitzung wird
|
||||
mitgeschickt, die HoneyBar weiß, wer da ist. **Das ist seit dem 29.08.2026 der
|
||||
Fall** — Aether liegt unter `cloud.honeyos.duckdns.org`, HoneyOS unter
|
||||
`honeyos.duckdns.org`, und `/api/apps` sieht erst nach der Sitzung und dann
|
||||
erst nach dem Schlüssel. Persönliche Apps sind damit machbar geworden.
|
||||
|
||||
→ **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
|
||||
|
||||
@@ -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
|
||||
also an CORS, selbst mit richtigem Schlüssel.
|
||||
|
||||
**Was auf zwei Sites nicht geht:** der Anmeldestand auf den Kacheln des
|
||||
Schreibtischs (fragt über die Domaingrenze). Der Name *in der HoneyBar* geht
|
||||
weiterhin — den holt sie bei der App selbst, gleicher Ursprung.
|
||||
**Erst die Sitzung, dann der Schlüssel.** Bringt die Anfrage ein gültiges
|
||||
HoneyOS-Cookie mit, antwortet der Server mit den Einstellungen **dieses**
|
||||
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…`),
|
||||
entfällt der Schlüssel und alles funktioniert ohne Einschränkung.
|
||||
Ob das Cookie ankommt, entscheidet die **Site**, nicht die Adresse: Weil
|
||||
`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
|
||||
|
||||
|
||||
@@ -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.
|
||||
func TestSchlüsselDarfNichtSchreiben(t *testing.T) {
|
||||
idp := newFakeIdP(t, "honeyos")
|
||||
|
||||
@@ -250,6 +250,22 @@ func noCache(next http.Handler) http.Handler {
|
||||
// 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 {
|
||||
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 != "" {
|
||||
given := r.Header.Get("X-HoneyOS-Key")
|
||||
if given == "" {
|
||||
|
||||
Reference in new issue
Block a user