package web import ( "net/http" "net/http/httptest" "strings" "testing" "time" "gitea.dooplex.hu/admin/felhom-hub/internal/i18n" "gitea.dooplex.hu/admin/felhom-hub/internal/store" ) // The public bind page in the household's language (R-558), and the property that constrains it. func bindFixture(t *testing.T, lang string) (*Server, string) { t.Helper() s, st := newTestServer(t) if err := st.SaveCustomerConfig(&store.CustomerConfig{ CustomerID: "acme", CustomerName: "Acme", Domain: "acme.example", RetrievalPassword: "p", APIKey: "k", ConfigJSON: "{}", Language: lang, }); err != nil { t.Fatal(err) } token := "0123456789abcdef0123456789abcdef" if err := st.MintSelfBindToken("acme", selfBindHash(token), 7*24*time.Hour); err != nil { t.Fatal(err) } return s, token } func getBind(t *testing.T, s *Server, token string) string { t.Helper() rec := httptest.NewRecorder() s.handleBind(rec, httptest.NewRequest(http.MethodGet, "/bind/"+token, nil)) return rec.Body.String() } // The page follows the CUSTOMER'S CREATION-TIME language, because no box has reported at bind time. func TestBindPageFollowsTheCreationLanguage(t *testing.T) { b := i18n.Shared() sHU, tokHU := bindFixture(t, "hu") huPage := getBind(t, sHU, tokHU) if !strings.Contains(huPage, b.Msg("hu", "bind.label.pairing")) { t.Errorf("the Hungarian bind page does not carry the Hungarian pairing label") } if !strings.Contains(huPage, ``) { t.Error("the Hungarian page does not declare lang=hu") } sEN, tokEN := bindFixture(t, "en") enPage := getBind(t, sEN, tokEN) if !strings.Contains(enPage, b.Msg("en", "bind.label.pairing")) { t.Errorf("the English bind page does not carry the English pairing label:\n%s", enPage) } if !strings.Contains(enPage, ``) { t.Error("the English page does not declare lang=en — a screen reader would read English aloud in Hungarian") } // NEGATIVE CONTROL: the English page must not still carry the Hungarian sentence. if strings.Contains(enPage, b.Msg("hu", "bind.lead")) { t.Error("the English page still carries the Hungarian lead") } if huPage == enPage { t.Fatal("both languages rendered the same page — the control proves nothing") } } // THE NO-ORACLE PROPERTY. This page folds an unknown token into "expired" so a stranger cannot learn // whether a link was ever real. The LANGUAGE must not answer what the TEXT refuses to: if a real // English customer's expired token rendered in English while an unknown token rendered in Hungarian, // the page would confirm the token for every non-Hungarian customer. func TestBindExpiredIsAlwaysDefaultLanguage(t *testing.T) { s, _ := bindFixture(t, "en") // A real token belonging to an ENGLISH customer, expired. expiredTok := "ffffffffffffffffffffffffffffffff" if err := mintExpired(t, s, "acme", expiredTok); err != nil { t.Fatal(err) } realExpired := getBind(t, s, expiredTok) // A token that was never real. unknown := getBind(t, s, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") if realExpired != unknown { t.Errorf("an expired REAL token and an unknown token render differently — the page is an "+ "oracle for whether a link existed.\n--- real ---\n%s\n--- unknown ---\n%s", realExpired, unknown) } b := i18n.Shared() if !strings.Contains(realExpired, b.Msg(i18n.Default, "bind.invalid.lead")) { t.Errorf("the expired page is not in the default language:\n%s", realExpired) } if strings.Contains(realExpired, b.Msg("en", "bind.invalid.lead")) { t.Error("the expired page rendered in the customer's language — it leaks that the token is real") } } func mintExpired(t *testing.T, s *Server, customerID, token string) error { t.Helper() // A negative TTL mints a token that is already past its expiry. return s.store.MintSelfBindToken(customerID, selfBindHash(token), -time.Hour) } // Every state of the page renders in both languages without a marker or a blank escaping into it. func TestBindPageHasNoUnsubstitutedMarkers(t *testing.T) { for _, lang := range i18n.Supported { s, tok := bindFixture(t, lang) page := getBind(t, s, tok) if strings.Contains(page, "{{T ") || strings.Contains(page, "!bind.") { t.Errorf("%s page carries an unsubstituted marker or a missing key:\n%s", lang, page) } if strings.Contains(page, "