internal/httpd/reauth_test.go

v1.42.0
gitbay/internal/httpd/reauth_test.go history · blame · raw

159 lines · 4908 bytes

  1package httpd
  2
  3import (
  4	"net/http"
  5	"net/http/httptest"
  6	"net/url"
  7	"strings"
  8	"testing"
  9	"time"
 10
 11	"gitbay.org/gitbay/internal/config"
 12	"gitbay.org/gitbay/internal/control"
 13	"gitbay.org/gitbay/internal/store"
 14)
 15
 16// A session signed in longer ago than ReauthWindow cannot mint from the
 17// settings page: the form comes back with the refusal and a sign-in
 18// link, and the sign-in returns to /settings (#297).
 19func TestWebMintNeedsRecentSignIn(t *testing.T) {
 20	s, st, u := newTokenTestServer(t)
 21	stale := u
 22	stale.SignedInAt = time.Now().Add(-control.ReauthWindow - time.Minute)
 23	rr := submitAccountForm(t, s, stale, url.Values{"field": {"token-create"}, "name": {"laptop"}, "scope": {"full"}})
 24	if rr.Code != http.StatusSeeOther {
 25		t.Fatalf("status %d, body %s", rr.Code, rr.Body.String())
 26	}
 27	if list, err := st.ListAPITokens(u.ID); err != nil || len(list) != 0 {
 28		t.Fatalf("a stale session minted %+v (%v)", list, err)
 29	}
 30
 31	req := httptest.NewRequest("GET", "/settings", nil)
 32	for _, c := range rr.Result().Cookies() {
 33		req.AddCookie(c)
 34	}
 35	page := httptest.NewRecorder()
 36	s.accountPage(page, req, stale)
 37	body := page.Body.String()
 38	if !strings.Contains(body, control.ReauthRefusal) {
 39		t.Fatalf("refusal not shown: %s", body)
 40	}
 41	if !strings.Contains(body, `<a href="/login">Sign in again</a>`) {
 42		t.Fatalf("no sign-in link: %s", body)
 43	}
 44	var next string
 45	for _, c := range page.Result().Cookies() {
 46		if c.Name == nextCookie {
 47			next = c.Value
 48		}
 49	}
 50	if next != url.QueryEscape("/settings") {
 51		t.Fatalf("gitbay_next = %q, want /settings", next)
 52	}
 53}
 54
 55// An API token has no browser session: minting through the API is not
 56// held to the sign-in window.
 57func TestAPIMintIgnoresTheSignInWindow(t *testing.T) {
 58	s, st, u := newTokenTestServer(t)
 59	if err := st.CreateAPIToken(u.ID, "ci", store.HashToken("gb_reauthtest"), "full", nil, 0); err != nil {
 60		t.Fatal(err)
 61	}
 62	req := httptest.NewRequest("POST", "/api/v1/cmd",
 63		strings.NewReader(`{"argv":["token","create","--name","second","--scope","read"]}`))
 64	req.Header.Set("Authorization", "Bearer gb_reauthtest")
 65	rr := httptest.NewRecorder()
 66	s.apiCmd(rr, req)
 67	if rr.Code != http.StatusOK {
 68		t.Fatalf("status %d: %s", rr.Code, rr.Body.String())
 69	}
 70}
 71
 72// A fresh session mints without any refusal.
 73func TestWebMintFreshSessionSucceeds(t *testing.T) {
 74	s, st, u := newTokenTestServer(t)
 75	rr := submitAccountForm(t, s, u, url.Values{"field": {"token-create"}, "name": {"laptop"}, "scope": {"full"}})
 76	if rr.Code != http.StatusOK {
 77		t.Fatalf("status %d, body %s", rr.Code, rr.Body.String())
 78	}
 79	if list, err := st.ListAPITokens(u.ID); err != nil || len(list) != 1 {
 80		t.Fatalf("token not minted: %+v (%v)", list, err)
 81	}
 82}
 83
 84// A stale session posting a grant form (org members add, on the
 85// organization's people page) also sees the refusal and the sign-in
 86// link, and the membership is not created.
 87func TestWebGrantNeedsRecentSignIn(t *testing.T) {
 88	st, err := store.Open(":memory:")
 89	if err != nil {
 90		t.Fatal(err)
 91	}
 92	defer st.Close()
 93	if err := st.MigrateUp(); err != nil {
 94		t.Fatal(err)
 95	}
 96	uid, err := st.CreateUser("alice", false)
 97	if err != nil {
 98		t.Fatal(err)
 99	}
100	if _, err := st.CreateUser("bob", false); err != nil {
101		t.Fatal(err)
102	}
103	fresh := store.User{ID: uid, Username: "alice", SignedInAt: time.Now()}
104	stale := fresh
105	stale.SignedInAt = time.Now().Add(-control.ReauthWindow - time.Minute)
106
107	cfg := config.Default()
108	cfg.Web.Mode = "accounts"
109	s := New(cfg, st, nil)
110	if _, msg, ok := s.runControl(fresh, []string{"org", "create", "krz"}); !ok {
111		t.Fatalf("org create: %s", msg)
112	}
113
114	req := httptest.NewRequest("POST", "/krz",
115		strings.NewReader(url.Values{"field": {"member-add"}, "user": {"bob"}}.Encode()))
116	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
117	req.SetPathValue("owner", "krz")
118	rr := httptest.NewRecorder()
119	s.orgSubmit(rr, req, stale)
120	if rr.Code != http.StatusSeeOther {
121		t.Fatalf("status %d, body %s", rr.Code, rr.Body.String())
122	}
123
124	req2 := httptest.NewRequest("GET", "/krz/-/people", nil)
125	req2.SetPathValue("owner", "krz")
126	for _, c := range rr.Result().Cookies() {
127		req2.AddCookie(c)
128	}
129	req2.AddCookie(sessionCookieFor(t, s, st, uid))
130	page := httptest.NewRecorder()
131	s.ownerProfile(page, req2)
132	body := page.Body.String()
133	if !strings.Contains(body, control.ReauthRefusal) {
134		t.Fatalf("refusal not shown: %s", body)
135	}
136	if !strings.Contains(body, `<a href="/login">Sign in again</a>`) {
137		t.Fatalf("no sign-in link: %s", body)
138	}
139	var next string
140	for _, c := range page.Result().Cookies() {
141		if c.Name == nextCookie {
142			next = c.Value
143		}
144	}
145	if next != url.QueryEscape("/krz/-/people") {
146		t.Fatalf("gitbay_next = %q, want /krz/-/people", next)
147	}
148
149	org, err := st.OrgByName("krz")
150	if err != nil {
151		t.Fatal(err)
152	}
153	members, _ := st.OrgMembers(org.ID)
154	for _, m := range members {
155		if m.Username == "bob" {
156			t.Fatalf("a stale session added bob to the org")
157		}
158	}
159}