package web // R-323 — THE THIRD NAME. One secret, one name, and none of them a near-homograph of another. // // Three secrets a customer can hold, and they must be tellable apart by a hurried reader: // // three words takes control of the dashboard „Beállító kód" // ten words opens the sealed off-site backups „Helyreállítási kód" // five words proves the account owns the box being bound „Tulajdonosi jelmondat" ← this one // // The five-word phrase used to be „Visszaállító jelszó" — one word away from „Visszaállító kód", // which R-295 had just retired for colliding with „Helyreállítási kód". Found while shipping that // rename and deliberately NOT swept in with it; ruled on separately by the operator 2026-08-13. // // WHY NOT THE OBVIOUS CANDIDATES, since a test is where the reasoning survives: // // „Összekötési jelszó" — the OTHER factor on this very page is the „Párosító kód". Naming this one // after the same act would leave the two factors a customer types in one sitting separated // only by kód-versus-jelszó, which is structurally the „Visszaállító kód"/„Visszaállító // jelszó" trap being removed. // „Fiókjelszó" — there IS an account password (the dashboard login). This would collide // with a DIFFERENT real secret, i.e. trade one homograph for a worse one. // // „Tulajdonosi jelmondat" is distinct on BOTH axes — stem (Tulajdonosi vs Beállító / Helyreállítási // / Párosító) and noun (jelmondat vs kód / jelszó) — and it says what the phrase actually does. import ( "net/http" "strings" "testing" "gitea.dooplex.hu/admin/felhom-hub/internal/notify" "gitea.dooplex.hu/admin/felhom-hub/internal/store" ) // retiredThirdName is the name this change removes. It is a SUBSTRING check on the stem so that a // possessive or accusative form („visszaállító jelszavadat") cannot slip past a check written // against the nominative — the R-299 lesson, where a guard matched one inflection of a Hungarian // verb and the plural walked straight by. const retiredThirdName = "isszaállító jelsz" // The binding page names the secret „Tulajdonosi jelmondat" and nowhere carries the retired name. // Driven through the real handler, so a template that never renders is visible here. func TestSelfBind_ThirdSecretNaming(t *testing.T) { s, st := newTestServer(t) selfBindSetup(t, st, "acme", testCode) token := mintLink(t, st, "acme", selfBindTTL) body := bindGET(t, s, token).Body.String() if !strings.Contains(body, "Tulajdonosi jelmondat") { t.Errorf("the field label must name the secret „Tulajdonosi jelmondat”:\n%s", body) } if strings.Contains(body, retiredThirdName) { t.Error("the retired name „Visszaállító jelszó” is back on the binding page") } // The two factors on this page must not be near-homographs of each other. The pairing code keeps // its name; what matters is that the SECOND factor no longer differs from it by one noun. if !strings.Contains(body, "Párosító kód") { t.Error("the pairing code lost its name — the two factors must both be named") } // Neither of the OTHER two secrets may be named on this page: a customer who reads „kód" here // twice is the failure this whole arc exists to prevent. for _, other := range []string{"Beállító kód", "Helyreállítási kód"} { if strings.Contains(body, other) { t.Errorf("the binding page names a different secret (%q) — that is the collision", other) } } } // The failure banner must name the secret the same way the label does. A page that asks for a // „Tulajdonosi jelmondat" and then complains about „a jelszó" has two names for one secret again, // which is the defect in miniature. func TestSelfBind_FailureBannerUsesTheSameName(t *testing.T) { s, st := newTestServer(t) selfBindSetup(t, st, "acme", testCode) token := mintLink(t, st, "acme", selfBindTTL) rr := bindPOST(t, s, token, testCodeFmt, "wrong wrong wrong wrong wrong") body := rr.Body.String() if !strings.Contains(body, "tulajdonosi jelmondat") { t.Errorf("the failure banner does not name the secret the way the label does:\n%s", body) } if strings.Contains(body, retiredThirdName) { t.Error("the retired name is on the failure banner") } } // The e-mail that carries the link names it identically. The mail is read BEFORE the page, so a // mismatch here is the customer's first impression of two different secrets. func TestSelfBindEmail_UsesTheSameName(t *testing.T) { subject, body := notify.FormatSelfBindEmail("acme", "https://hub.example/bind/tok") if !strings.Contains(body, "tulajdonosi jelmondatodat") { t.Errorf("the self-bind mail does not name the secret „Tulajdonosi jelmondat”:\n%s", body) } if strings.Contains(subject+body, retiredThirdName) { t.Error("the retired name is still in the self-bind mail") } // It must still say what the thing IS — a rename that leaves the customer unable to recognise // what they are holding has fixed a collision and broken the delivery. if !strings.Contains(body, "5 szóból álló kifejezést") { t.Error("the mail no longer says the phrase is five words") } } // THE ACCEPTANCE PIN — the whole point of calling this naming rather than function. // // The SAME phrase, typed the same messy way a human types it (odd spacing, mixed case, mixed // separators), still binds the appliance after the rename. If this ever goes red, the rename stopped // being a rename. func TestSelfBindPassphrase_StillAcceptedAfterTheRename(t *testing.T) { s, st := newTestServer(t) id := selfBindSetup(t, st, "acme", testCode) token := mintLink(t, st, "acme", selfBindTTL) rr := bindPOST(t, s, token, testCodeFmt, " Alpha Beta gamma-delta epsilon ") if rr.Code != http.StatusOK || !strings.Contains(rr.Body.String(), "egy percen belül") { t.Fatalf("the same passphrase stopped being accepted: code=%d body=%q", rr.Code, rr.Body.String()) } if a, _ := st.GetAppliance(id); a == nil || a.Status != store.ApplianceBound { t.Fatalf("the appliance did not bind: %+v", a) } // The form field NAME is deliberately still `passphrase`: renaming customer copy must not touch // the wire. A changed field name would be an acceptance change wearing a rename's clothes. body := bindGET(t, s, mintLink(t, st, "acme", selfBindTTL)).Body.String() if !strings.Contains(body, `name="passphrase"`) { t.Error("the form field name changed — that is not a rename, that is a wire change") } }