package api import ( "net/http" "net/http/httptest" "strings" "testing" "time" ) func TestRefreshLostResponseRecovery(t *testing.T) { a, req := authFixture(t) w := req("POST", "/api/login", `{"username":"alice","password":"password-123"}`, "", nil) old := w.Result().Cookies() csrf, sid := cookieValue(old, "dsh_csrf"), cookieValue(old, "dsh_session") w = req("POST", "/api/refresh", "", csrf, old) if w.Code != 200 { t.Fatal(w.Code) } successor := cookieValue(w.Result().Cookies(), "dsh_refresh") // Lose the response, expire access and restart the server: recovery is durable // and does not depend on an active access cookie or a short grace timer. for _, c := range old { if c.Name == "dsh_access" { c.Value = a.accessJWT(sid, "alice", time.Now().Add(-time.Hour)) } } b := New(a.s, a.root) b.origin = a.origin r := httptest.NewRequest("POST", "/api/refresh", strings.NewReader("")) r.Header.Set("Origin", a.origin) r.Header.Set("X-CSRF-Token", csrf) for _, c := range old { r.AddCookie(c) } w = httptest.NewRecorder() b.Handler().ServeHTTP(w, r) if w.Code != 200 || cookieValue(w.Result().Cookies(), "dsh_refresh") != successor { t.Fatal("lost response recovery failed", w.Code) } current := w.Result().Cookies() w = req("POST", "/api/refresh", "", csrf, current) if w.Code != 200 { t.Fatal(w.Code) } if w = req("POST", "/api/refresh", "", csrf, old); w.Code != 401 || len(w.Result().Cookies()) != 0 { t.Fatal("two-generation-old token accepted", w.Code) } bad := []*http.Cookie{{Name: "dsh_session", Value: sid}, {Name: "dsh_refresh", Value: "invalid"}} if w = req("POST", "/api/refresh", "", csrf, bad); w.Code != 401 { t.Fatal(w.Code) } if _, err := a.s.DB.Exec("UPDATE sessions SET expires=? WHERE id=?", time.Now().Unix(), sid); err != nil { t.Fatal(err) } if w = req("POST", "/api/refresh", "", csrf, current); w.Code == 200 || len(w.Result().Cookies()) != 0 { t.Fatal("expired session recovered") } }