This commit is contained in:
Saksham Goyal 2026-08-12 23:06:01 +00:00 committed by GitHub
commit f1ab0de40e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 100 additions and 0 deletions

View File

@ -8,6 +8,8 @@
## Changes since v7.15.3
- fix: clear stale cookie session parts after session size changes
# V7.15.3
## Release Highlights

View File

@ -121,6 +121,35 @@ func (s *SessionStore) setSessionCookie(rw http.ResponseWriter, req *http.Reques
if err != nil {
return err
}
newCookieNames := make(map[string]struct{}, len(cookies))
for _, c := range cookies {
newCookieNames[c.Name] = struct{}{}
}
sessionCookieName := regexp.QuoteMeta(s.Cookie.Name)
sessionCookiePattern := regexp.MustCompile(fmt.Sprintf("^%s(_\\d+)?$", sessionCookieName))
for _, c := range req.Cookies() {
if !sessionCookiePattern.MatchString(c.Name) {
continue
}
if _, ok := newCookieNames[c.Name]; ok {
continue
}
clearCookieOptions := &pkgcookies.CookieOptions{
Name: c.Name,
Value: "",
Domains: s.Cookie.Domains,
Expiration: time.Hour * -1,
SameSite: s.Cookie.SameSite,
Path: s.Cookie.Path,
HTTPOnly: s.Cookie.HTTPOnly,
Secure: s.Cookie.Secure,
}
http.SetCookie(rw, pkgcookies.MakeCookieFromOptions(req, clearCookieOptions))
}
for _, c := range cookies {
http.SetCookie(rw, c)
}

View File

@ -4,6 +4,7 @@ import (
"fmt"
mathrand "math/rand"
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"
@ -140,6 +141,74 @@ func Test_splitCookieName(t *testing.T) {
}
}
func Test_setSessionCookieClearsStaleCookies(t *testing.T) {
store := &SessionStore{
Cookie: &options.Cookie{
Name: "_oauth2_proxy",
Secret: "0123456789abcdef",
Path: "/",
Expire: time.Hour,
},
}
now := time.Now()
testCases := map[string]struct {
value []byte
existingCookies []string
wantSessionParts int
wantClearedCookie []string
}{
"split session becomes a single cookie": {
value: []byte("small session"),
existingCookies: []string{"_oauth2_proxy_0", "_oauth2_proxy_1", "_oauth2_proxy_csrf"},
wantSessionParts: 1,
wantClearedCookie: []string{"_oauth2_proxy_0", "_oauth2_proxy_1"},
},
"split session uses fewer parts": {
value: []byte(strings.Repeat("v", 4500)),
existingCookies: []string{"_oauth2_proxy_0", "_oauth2_proxy_1", "_oauth2_proxy_2", "_oauth2_proxy_3"},
wantSessionParts: 2,
wantClearedCookie: []string{"_oauth2_proxy_2", "_oauth2_proxy_3"},
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "https://example.com/", nil)
for _, cookieName := range tc.existingCookies {
req.AddCookie(&http.Cookie{Name: cookieName, Value: "old"})
}
newCookies, err := store.makeSessionCookie(req, tc.value, now)
assert.NoError(t, err)
assert.Len(t, newCookies, tc.wantSessionParts)
rw := httptest.NewRecorder()
err = store.setSessionCookie(rw, req, tc.value, now)
assert.NoError(t, err)
responseCookies := make(map[string]*http.Cookie)
for _, cookie := range rw.Result().Cookies() {
responseCookies[cookie.Name] = cookie
}
for _, cookieName := range tc.wantClearedCookie {
cookie := responseCookies[cookieName]
if assert.NotNil(t, cookie, "stale cookie %q was not cleared", cookieName) {
assert.Equal(t, -1, cookie.MaxAge)
}
}
for _, wantCookie := range newCookies {
cookie := responseCookies[wantCookie.Name]
if assert.NotNil(t, cookie, "current cookie %q was not set", wantCookie.Name) {
assert.Greater(t, cookie.MaxAge, 0)
}
}
assert.Len(t, responseCookies, len(newCookies)+len(tc.wantClearedCookie))
assert.NotContains(t, responseCookies, "_oauth2_proxy_csrf")
})
}
}
func Test_splitCookie_joinCookies(t *testing.T) {
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"