e2e/websessions_test.go
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}