Go · 2930 bytes Raw Blame History
1 // SPDX-License-Identifier: AGPL-3.0-or-later
2
3 package auth_test
4
5 import (
6 "io"
7 "net/http"
8 "net/url"
9 "strings"
10 "testing"
11 )
12
13 func loginSessionsUser(t *testing.T, name string) (cli *client, loginAgain func() *client) {
14 t.Helper()
15 httpsrv, captor := newTestServer(t, false)
16 cli = newClient(t, httpsrv)
17 mustSignup(t, cli, name, name+"@example.com", "correct horse battery staple")
18 tok := extractTokenFromMessage(t, captor.all()[0], "/verify-email")
19 _ = cli.get(t, "/verify-email/"+tok).Body.Close()
20
21 doLogin := func(c *client) {
22 csrf := c.extractCSRF(t, "/login")
23 resp := c.post(t, "/login", url.Values{
24 "csrf_token": {csrf},
25 "username": {name},
26 "password": {"correct horse battery staple"},
27 })
28 if resp.StatusCode != http.StatusSeeOther {
29 t.Fatalf("login: %d", resp.StatusCode)
30 }
31 _ = resp.Body.Close()
32 }
33 doLogin(cli)
34 // loginAgain returns a fresh client that has signed in as the same
35 // user — useful for testing log-out-everywhere across two browsers.
36 loginAgain = func() *client {
37 c := newClient(t, httpsrv)
38 doLogin(c)
39 return c
40 }
41 return cli, loginAgain
42 }
43
44 func TestSessions_PageRenders(t *testing.T) {
45 t.Parallel()
46 cli, _ := loginSessionsUser(t, "sa")
47 resp := cli.get(t, "/settings/sessions")
48 defer func() { _ = resp.Body.Close() }()
49 body, _ := io.ReadAll(resp.Body)
50 if !strings.Contains(string(body), "Sessions") {
51 t.Fatalf("missing heading: %s", body)
52 }
53 if !strings.Contains(string(body), "UA=Go-http-client") {
54 t.Fatalf("expected User-Agent surfaced; got: %s", body)
55 }
56 }
57
58 func TestSessions_LogoutEverywhereInvalidatesOthers(t *testing.T) {
59 t.Parallel()
60 cliA, loginAgain := loginSessionsUser(t, "sb")
61 cliB := loginAgain() // a second "browser"
62
63 // Both browsers can hit the protected page initially.
64 resp := cliB.get(t, "/settings/profile")
65 if resp.StatusCode != http.StatusOK {
66 t.Fatalf("cliB before bump: status=%d", resp.StatusCode)
67 }
68 _ = resp.Body.Close()
69
70 // cliA bumps the epoch.
71 csrf := cliA.extractCSRF(t, "/settings/sessions")
72 resp = cliA.post(t, "/settings/sessions/logout-everywhere", url.Values{
73 "csrf_token": {csrf},
74 })
75 body, _ := io.ReadAll(resp.Body)
76 _ = resp.Body.Close()
77 if !strings.Contains(string(body), "Signed out of every other session") {
78 t.Fatalf("expected success message, got: %s", body)
79 }
80
81 // cliA itself stays signed in.
82 resp = cliA.get(t, "/settings/profile")
83 if resp.StatusCode != http.StatusOK {
84 body, _ := io.ReadAll(resp.Body)
85 t.Fatalf("cliA after bump should still be signed in: %d %s", resp.StatusCode, body)
86 }
87 _ = resp.Body.Close()
88
89 // cliB should now be bounced to /login on the next protected hit
90 // because its session carries the stale epoch.
91 resp = cliB.get(t, "/settings/profile")
92 defer func() { _ = resp.Body.Close() }()
93 if resp.StatusCode != http.StatusSeeOther && resp.StatusCode != http.StatusFound {
94 t.Fatalf("cliB after bump expected redirect, got %d", resp.StatusCode)
95 }
96 }
97