e2e/accountweb_test.go

v1.13.0
gitbay/e2e/accountweb_test.go history · blame · raw

121 lines · 4007 bytes

  1package e2e
  2
  3import (
  4	"encoding/json"
  5	"net/url"
  6	"os"
  7	"strings"
  8	"testing"
  9)
 10
 11// TestAccountSettingsWeb covers managing your own keys and addresses from a
 12// browser session. Public keys are the only credential-shaped input the web
 13// accepts; secrets and token minting stay on SSH.
 14func TestAccountSettingsWeb(t *testing.T) {
 15	inst := startInstanceWith(t, "[web]\nmode = \"accounts\"\n")
 16	aliceKey := inst.newKey(t, "alice")
 17	inst.admin(t, "admin", "user", "create", "alice",
 18		"--key", aliceKey+".pub", "--email", "alice@example.test", "--verified")
 19
 20	out, _, code := inst.ssh(t, aliceKey, "", "web", "login", "--json")
 21	if code != 0 {
 22		t.Fatal("web login failed")
 23	}
 24	var env struct {
 25		Data struct {
 26			URL string `json:"url"`
 27		} `json:"data"`
 28	}
 29	json.Unmarshal([]byte(out), &env)
 30	browser := newBrowser(t)
 31	browserGet(t, browser, inst.base()+env.Data.URL[strings.Index(env.Data.URL, "/login"):])
 32
 33	status, body := browserGet(t, browser, inst.base()+"/settings")
 34	if status != 200 {
 35		t.Fatalf("account settings: %d", status)
 36	}
 37	// The key that signed us in is listed, and its address shows verified.
 38	if !strings.Contains(body, "SHA256:") {
 39		t.Error("no SSH key fingerprint listed")
 40	}
 41	// Keys are stored in wire format, which holds no comment; anything
 42	// pulled out of it and printed would be binary noise.
 43	if strings.Contains(body, "\ufffd") {
 44		t.Error("key row is rendering raw blob bytes")
 45	}
 46	if !strings.Contains(body, "alice@example.test") || !strings.Contains(body, "verified") {
 47		t.Error("verified address not shown")
 48	}
 49
 50	// Add a second key through the form, then confirm it over SSH — the
 51	// web write must land in the same place the CLI reads.
 52	second := inst.newKey(t, "alice2")
 53	raw, err := os.ReadFile(second + ".pub")
 54	if err != nil {
 55		t.Fatal(err)
 56	}
 57	pub := string(raw)
 58	if status, _ := browserPost(t, browser, inst.base()+"/settings", url.Values{
 59		"field": {"key-add"}, "key": {pub}, "scope": {"git"},
 60	}); status != 303 && status != 200 {
 61		t.Fatalf("key add: %d", status)
 62	}
 63	out, _, _ = inst.ssh(t, aliceKey, "", "keys", "list", "--json")
 64	if strings.Count(out, "SHA256:") != 2 || !strings.Contains(out, `"scope":"git"`) {
 65		t.Fatalf("key not registered with its scope: %s", out)
 66	}
 67
 68	// A git-scoped key can move git data but cannot run commands, so the
 69	// scope the form set is really enforced.
 70	if _, _, code := inst.ssh(t, second, "", "whoami"); code == 0 {
 71		t.Error("git-scoped key ran a control command")
 72	}
 73
 74	// Removing it through the form removes it for SSH too.
 75	fp := gitScopedFingerprint(t, out)
 76	if status, _ := browserPost(t, browser, inst.base()+"/settings", url.Values{
 77		"field": {"key-remove"}, "fingerprint": {fp},
 78	}); status != 303 && status != 200 {
 79		t.Fatalf("key remove: %d", status)
 80	}
 81	out, _, _ = inst.ssh(t, aliceKey, "", "keys", "list", "--json")
 82	if strings.Count(out, "SHA256:") != 1 {
 83		t.Fatalf("key not removed: %s", out)
 84	}
 85
 86	// Garbage is refused by the same validation the CLI uses, and says so.
 87	// The redirect carries the message, so the followed page shows it.
 88	_, body = browserPost(t, browser, inst.base()+"/settings", url.Values{
 89		"field": {"key-add"}, "key": {"not a key"},
 90	})
 91	if !strings.Contains(body, `class="error"`) {
 92		t.Error("invalid key accepted without an error")
 93	}
 94
 95	// Token minting is SSHOnly and has no web form to reach it.
 96	if strings.Contains(body, `value="token-mint"`) {
 97		t.Error("token minting exposed on the web")
 98	}
 99}
100
101// gitScopedFingerprint pulls the fingerprint of the git-scoped key out of
102// "auth keys list --json".
103func gitScopedFingerprint(t *testing.T, blob string) string {
104	t.Helper()
105	var env struct {
106		Data []struct {
107			Fingerprint string `json:"fingerprint"`
108			Scope       string `json:"scope"`
109		} `json:"data"`
110	}
111	if err := json.Unmarshal([]byte(blob), &env); err != nil {
112		t.Fatalf("keys list JSON: %v\n%s", err, blob)
113	}
114	for _, k := range env.Data {
115		if k.Scope == "git" {
116			return k.Fingerprint
117		}
118	}
119	t.Fatalf("no git-scoped key in %s", blob)
120	return ""
121}