package main
// What this machine has given away, driven through the real handler.
//
// NOT tagged !windows, for the same reason ui_test.go and uiauth_test.go are
// not: this whole UI exists because of a Windows desktop, and a page about what
// a host may do to your machine has to be proven on the machine it was built
// for. That constraint is why nothing here borrows setupBox / signTerms /
// grantTerms / tAnchor / tMember from anchor_test.go — those live behind
// //go:build !windows and would take this file with them (buildtags_test.go
// checks exactly that and prints the fix). The fixtures below are local and
// platform-neutral: writeAnchor, pinAnchorVerifyKey, savePairingGrant and
// dcnSaveFile are all ordinary package functions with no build tag.
import (
"crypto/ed25519"
"encoding/base64"
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"net/url"
"os"
"path/filepath"
"regexp"
"strings"
"testing"
"time"
)
const (
gAnchorHost = "anchor.host.privatae.ai"
gMemberHost = "member.host.privatae.ai"
)
// givenPairing writes one pairing file the way `pair` does, with its DCN dir
// pinned to a temp path so a test can put a real grants.json where the page
// will look for it.
func givenPairing(t *testing.T, host, state string) string {
t.Helper()
d, err := pairingsDir()
if err != nil {
t.Fatalf("pairingsDir: %v", err)
}
dcn := filepath.Join(t.TempDir(), "dcn")
if err := os.MkdirAll(dcn, 0o700); err != nil {
t.Fatal(err)
}
b, err := json.Marshal(&Config{
HostName: host,
// Loopback and nothing listening, exactly as ui_test.go writes it: a
// pairing's control port IS loopback-only by design (config.go:39) and
// this one is deliberately dead, so liveness reads "not answering".
ListenAddr: "127.0.0.1:59999",
BastionTunnelIP: "fd00:9::a:1",
DCNDir: dcn,
State: state,
FirstSeen: "2026-09-01T00:00:00Z",
})
if err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(d, host+".json"), b, 0o600); err != nil {
t.Fatalf("write pairing: %v", err)
}
return dcn
}
// givenAnchoredBox is a NETWORKED, ANCHORED machine with the anchor host's
// signing key pinned — the shape in which a caps grant means anything at all.
// Returns the key the "anchor host" signs its grants with.
func givenAnchoredBox(t *testing.T) ed25519.PrivateKey {
t.Helper()
uiTestConfigDir(t)
p, err := modePath()
if err != nil {
t.Fatal(err)
}
if err := os.WriteFile(p, []byte(`{"mode":"networked"}`), 0o600); err != nil {
t.Fatal(err)
}
givenPairing(t, gAnchorHost, pairingActive)
if err := writeAnchor("Acme IT", gAnchorHost); err != nil {
t.Fatalf("writeAnchor: %v", err)
}
pub, priv, err := ed25519.GenerateKey(nil)
if err != nil {
t.Fatal(err)
}
if err := pinAnchorVerifyKey(base64.StdEncoding.EncodeToString(pub)); err != nil {
t.Fatalf("pinAnchorVerifyKey: %v", err)
}
return priv
}
// givenSignedGrant installs the anchor's signed pairing grant for host. The
// canonical bytes are a marshalled map, which is what the host side sends and
// what verifyAnchorRecord parses (anchor.go:280).
func givenSignedGrant(t *testing.T, priv ed25519.PrivateKey, host string, caps []string) {
t.Helper()
hn, _ := os.Hostname()
canonical, err := json.Marshal(map[string]any{
"kind": "pairing_grant", "server": hn, "anchor": gAnchorHost,
"subject": host, "subject_handle": handleOf(host), "caps": caps,
"issued_at": time.Now().UTC().Format(time.RFC3339),
})
if err != nil {
t.Fatal(err)
}
if err := savePairingGrant(host, &pairingGrantFile{
TermsCanonical: base64.StdEncoding.EncodeToString(canonical),
Signature: base64.StdEncoding.EncodeToString(ed25519.Sign(priv, canonical)),
SyncedAt: time.Now().UTC().Format(time.RFC3339),
}); err != nil {
t.Fatalf("savePairingGrant: %v", err)
}
}
// givenStoreGrants writes a grants.json holding one signed store grant per
// terms map, keyed by store id, under a freshly generated pinned key.
func givenStoreGrants(t *testing.T, dir string, stores map[string]map[string]any) ed25519.PrivateKey {
t.Helper()
pub, priv, err := ed25519.GenerateKey(nil)
if err != nil {
t.Fatal(err)
}
f := &dcnGrantFile{
VerifyPubkey: base64.StdEncoding.EncodeToString(pub),
Stores: map[string]dcnGrantRec{},
}
for id, terms := range stores {
canonical, err := json.Marshal(terms)
if err != nil {
t.Fatal(err)
}
f.Stores[id] = dcnGrantRec{
TermsCanonical: base64.StdEncoding.EncodeToString(canonical),
Signature: base64.StdEncoding.EncodeToString(ed25519.Sign(priv, canonical)),
}
}
if err := dcnSaveFile(dir, f); err != nil {
t.Fatalf("dcnSaveFile: %v", err)
}
return priv
}
// givenMachineStore is the shape of a real machine-store grant, so a test only
// has to say the part it is about.
func givenMachineStore(root string, mask, excludes []string, expires string) map[string]any {
hn, _ := os.Hostname()
m := map[string]any{
"kind": "dcn_store_grant", "class": "machine", "server": hn,
"root": root, "mask": mask, "expires": expires,
}
if excludes != nil {
m["excludes"] = excludes
}
return m
}
// givenPage drives a signed-in GET through the whole mux — the Host check, the
// Origin check, the session check and the template — and returns the body.
func givenPage(t *testing.T, path string) string {
t.Helper()
u := uiTestServer(t)
tok, err := u.sessions.mint()
if err != nil {
t.Fatal(err)
}
r := uiReq(http.MethodGet, path)
r.Header.Set("Cookie", uiCookieName+"="+tok)
w := httptest.NewRecorder()
u.handler().ServeHTTP(w, r)
if w.Code != http.StatusOK {
t.Fatalf("%s = %d: %s", path, w.Code, w.Body.String())
}
return w.Body.String()
}
// givenCapsFor slices out ONE host's capability section. The page lists every
// pairing, so an assertion on the whole body would pass on the wrong host's
// row — which is the only mistake this page could make that would be worse
// than not existing.
func givenCapsFor(t *testing.T, body, host string) string {
t.Helper()
open := "What " + host + " may do to this machine"
shut := "Folders of yours that " + host + " can reach"
i := strings.Index(body, open)
if i < 0 {
t.Fatalf("the page has no capability section for %s", host)
}
j := strings.Index(body[i:], shut)
if j < 0 {
t.Fatalf("the capability section for %s is not closed by its folder section", host)
}
return body[i : i+j]
}
// givenFoldersFor slices out ONE host's folder list: from its own heading to
// the next host's capability heading, or to the end of the page.
func givenFoldersFor(t *testing.T, body, host string) string {
t.Helper()
open := "Folders of yours that " + host + " can reach"
i := strings.Index(body, open)
if i < 0 {
t.Fatalf("the page has no folder section for %s", host)
}
rest := body[i+len(open):]
if j := strings.Index(rest, "may do to this machine"); j >= 0 {
// Back up past the next host's
, which carries that host's name.
if k := strings.LastIndex(rest[:j], "
"); k >= 0 {
return rest[:k]
}
return rest[:j]
}
return rest
}
func givenHolds(t *testing.T, section, host string, families ...string) {
t.Helper()
for _, f := range families {
row := givenRow(t, section, f)
if !strings.Contains(row, "granted") || strings.Contains(row, "refused here") {
t.Errorf("%s: %q must read as GRANTED and does not:\n%s", host, f, row)
}
}
}
func givenRefuses(t *testing.T, section, host string, families ...string) {
t.Helper()
for _, f := range families {
row := givenRow(t, section, f)
if !strings.Contains(row, "refused here") || strings.Contains(row, ">granted<") {
t.Errorf("%s: %q must read as REFUSED and does not:\n%s", host, f, row)
}
}
}
// givenRow is one family's card. The family id is the last thing in the card
// (the ops line), so a card runs from the previous card boundary to it.
func givenRow(t *testing.T, section, family string) string {
t.Helper()
marker := `
` + family + "\n"
i := strings.Index(section, marker)
if i < 0 {
t.Fatalf("no card for the %q family on this page", family)
}
start := strings.LastIndex(section[:i], `