| index 287df87..1e8dea5 100644 |
| --- a/internal/sheets/chrome_signin_test.go |
| +++ b/internal/sheets/chrome_signin_test.go |
| @@ -2,6 +2,7 @@ package sheets |
| |
| import ( |
| "net/http" |
| + "net/url" |
| "strings" |
| "testing" |
| ) |
| @@ -35,8 +36,8 @@ func TestConnectYourOficinaAccountIsHiddenForHomeSessions(t *testing.T) { |
| // hop lands on the root. Signed in, it is the list, both ways. |
| func TestTheRootStartsSignInWhenHomeIsConfigured(t *testing.T) { |
| f := newHomeFixture(t) |
| - if rec := f.request(t, "GET", "/", nil, false); rec.Code != http.StatusSeeOther || rec.Header().Get("Location") != "/signin" { |
| - t.Fatalf("signed-out / with Home = %d %q, want 303 /signin", rec.Code, rec.Header().Get("Location")) |
| + if rec := f.request(t, "GET", "/", nil, false); rec.Code != http.StatusSeeOther || rec.Header().Get("Location") != "/signin?return_to=%2F" { |
| + t.Fatalf("signed-out / with Home = %d %q, want 303 /signin?return_to=%%2F", rec.Code, rec.Header().Get("Location")) |
| } |
| homeSignedIn(t, f) |
| if rec := f.request(t, "GET", "/", nil, false); rec.Code != http.StatusSeeOther || rec.Header().Get("Location") != "/sheets" { |
| @@ -103,3 +104,48 @@ func TestSignOutLandsOnTheSignedOutPage(t *testing.T) { |
| } |
| } |
| } |
| + |
| +// A Home user whose admission is revoked while their session is live must |
| +// not bounce between "/" and "/signin" forever (Astra, #39 finding 2). |
| +// "/" sent them to /signin because identity refused them; connect's |
| +// /signin saw the still-live cookie and sent them back to "/". The root |
| +// now goes through the same guard as every signed-in page, which ends |
| +// the stale session, so the redirects terminate at a fresh sign-in. |
| +func TestARevokedHomeUserIsNotBouncedBetweenTheRootAndSignIn(t *testing.T) { |
| + f := newHomeFixture(t) |
| + member := homeSignedIn(t, f) |
| + f.app.admit = func(string) bool { return false } |
| + if err := f.app.db.Where("member_id = ?", member.ID).Delete(&Grant{}).Error; err != nil { |
| + t.Fatal(err) |
| + } |
| + path, seen := "/", map[string]int{} |
| + for hop := 0; ; hop++ { |
| + if hop == 8 { |
| + t.Fatalf("still redirecting after 8 hops: %v", seen) |
| + } |
| + seen[path]++ |
| + if seen[path] > 1 { |
| + t.Fatalf("redirect loop: %s visited twice (%v)", path, seen) |
| + } |
| + rec := f.request(t, "GET", path, nil, false) |
| + loc := rec.Header().Get("Location") |
| + if rec.Code != http.StatusSeeOther || loc == "" { |
| + break |
| + } |
| + u, err := url.Parse(loc) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + if u.Host != "" { |
| + // Off to id.* for a fresh sign-in: the loop is over. |
| + if u.Host != "id.example.com" { |
| + t.Fatalf("left for %s", loc) |
| + } |
| + break |
| + } |
| + path = u.RequestURI() |
| + } |
| + if _, ok := f.jar[f.app.homeSessions.CookieName()]; ok { |
| + t.Fatal("the revoked session survived") |
| + } |
| +} |