e2e/websessions_test.go

v1.9.0
gitbay/e2e/websessions_test.go history · blame · raw

87 lines · 3082 bytes

 1package e2e
 2
 3import (
 4	"encoding/json"
 5	"net/http"
 6	"strings"
 7	"testing"
 8)
 9
10// A browser session can be listed and ended from SSH, one at a time or
11// all at once, and only its owner sees it.
12func TestWebSessionsListRevoke(t *testing.T) {
13	inst := startInstanceWith(t, "[web]\nmode = \"accounts\"\n")
14	aliceKey := inst.newKey(t, "alice")
15	bobKey := inst.newKey(t, "bob")
16	inst.admin(t, "admin", "user", "create", "alice", "--key", aliceKey+".pub")
17	inst.admin(t, "admin", "user", "create", "bob", "--key", bobKey+".pub")
18
19	if out, _, code := inst.ssh(t, aliceKey, "", "web", "sessions", "list", "--json"); code != 0 || !strings.Contains(out, `"data":[]`) {
20		t.Fatalf("no sessions yet: exit %d %s", code, out)
21	}
22	first := inst.login(t, aliceKey)
23	second := inst.login(t, aliceKey)
24	list := func() []struct {
25		ID string `json:"id"`
26	} {
27		t.Helper()
28		out, errOut, code := inst.ssh(t, aliceKey, "", "web", "sessions", "list", "--json")
29		if code != 0 {
30			t.Fatalf("list: %s", errOut)
31		}
32		var env struct {
33			Data []struct {
34				ID string `json:"id"`
35			} `json:"data"`
36		}
37		if err := json.Unmarshal([]byte(out), &env); err != nil {
38			t.Fatalf("list json: %v\n%s", err, out)
39		}
40		return env.Data
41	}
42	sessions := list()
43	if len(sessions) != 2 || len(sessions[0].ID) != 12 {
44		t.Fatalf("two sessions expected: %+v", sessions)
45	}
46	// Bob sees none of them, and cannot revoke one by id.
47	if out, _, _ := inst.ssh(t, bobKey, "", "web", "sessions", "list", "--json"); !strings.Contains(out, `"data":[]`) {
48		t.Fatalf("bob sees alice's sessions:\n%s", out)
49	}
50	if _, _, code := inst.ssh(t, bobKey, "", "web", "sessions", "revoke", sessions[0].ID); code != 3 {
51		t.Fatalf("bob revoked alice's session: exit %d", code)
52	}
53	// Both browsers work; revoking the newest logs that one out.
54	// The client follows the logged-out redirect to /login, so the page
55	// body tells the two apart, not the status.
56	loggedIn := func(c *http.Client) bool {
57		_, body := browserGet(t, c, inst.base()+"/settings")
58		return strings.Contains(body, "SSH keys")
59	}
60	if !loggedIn(first) || !loggedIn(second) {
61		t.Fatal("both browsers should be logged in")
62	}
63	if out, _, code := inst.ssh(t, aliceKey, "", "web", "sessions", "revoke", sessions[0].ID); code != 0 || !strings.Contains(out, "revoked browser session") {
64		t.Fatalf("revoke: exit %d %s", code, out)
65	}
66	if got := list(); len(got) != 1 {
67		t.Fatalf("one session left expected: %+v", got)
68	}
69	okCount := 0
70	for _, c := range []*http.Client{first, second} {
71		if loggedIn(c) {
72			okCount++
73		}
74	}
75	if okCount != 1 {
76		t.Fatalf("exactly one browser should still be logged in, got %d", okCount)
77	}
78	if out, _, code := inst.ssh(t, aliceKey, "", "web", "sessions", "revoke", "--all"); code != 0 || !strings.Contains(out, "revoked 1 browser sessions") {
79		t.Fatalf("revoke --all: exit %d %s", code, out)
80	}
81	if loggedIn(first) || loggedIn(second) {
82		t.Fatal("a browser is still logged in after revoke --all")
83	}
84	if _, _, code := inst.ssh(t, aliceKey, "", "web", "sessions", "revoke", "abcdefabcdef"); code != 3 {
85		t.Fatal("unknown id accepted")
86	}
87}