Round session cookie Max-Age up to whole seconds
A maximum session lifetime with fractional seconds (e.g. 2500ms) was truncated to a 2-second cookie Max-Age, so the browser dropped the cookie before the server-side absolute limit. Round up instead; the stored expires_at keeps the configured duration. Fixes review finding MQ-19-2. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -91,8 +91,9 @@ func (m *SessionManager) StartSession(w http.ResponseWriter, r *http.Request, cl
|
|||||||
}
|
}
|
||||||
|
|
||||||
// The cookie outlives idle gaps so a returning device presents its ID;
|
// The cookie outlives idle gaps so a returning device presents its ID;
|
||||||
// the server decides whether the session is still live.
|
// the server decides whether the session is still live. Max-Age is whole
|
||||||
http.SetCookie(w, sessionCookie(id, int(m.max/time.Second)))
|
// seconds, rounded up so the cookie never ends before the absolute limit.
|
||||||
|
http.SetCookie(w, sessionCookie(id, int((m.max+time.Second-1)/time.Second)))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -305,6 +305,34 @@ func TestSessionAbsoluteExpiry(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A max with fractional seconds rounds the cookie Max-Age up, so the browser
|
||||||
|
// keeps the cookie until the server-side absolute limit.
|
||||||
|
func TestSessionCookieOutlivesFractionalMax(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
h := newSessionHarness(t)
|
||||||
|
h.sessions.idle, h.sessions.max = 1500*time.Millisecond, 2500*time.Millisecond
|
||||||
|
_, cookie := h.signIn(t)
|
||||||
|
if cookie.MaxAge != 3 {
|
||||||
|
t.Errorf("session cookie MaxAge = %d, want 3", cookie.MaxAge)
|
||||||
|
}
|
||||||
|
if r := h.rows(t)[0]; !r.expires.Equal(testEpoch.Add(1500 * time.Millisecond)) {
|
||||||
|
t.Errorf("expires_at = %v, want %v", r.expires, testEpoch.Add(1500*time.Millisecond))
|
||||||
|
}
|
||||||
|
|
||||||
|
h.advance(time.Second)
|
||||||
|
if _, err := h.use(cookie); err != nil {
|
||||||
|
t.Fatalf("at 1s SessionFor = %v, want the session", err)
|
||||||
|
}
|
||||||
|
h.clock = testEpoch.Add(2500*time.Millisecond - time.Millisecond)
|
||||||
|
if _, err := h.use(cookie); err != nil {
|
||||||
|
t.Fatalf("just inside the absolute limit SessionFor = %v, want the session", err)
|
||||||
|
}
|
||||||
|
h.clock = testEpoch.Add(2500 * time.Millisecond)
|
||||||
|
if _, err := h.use(cookie); !errors.Is(err, ErrNoSession) {
|
||||||
|
t.Errorf("at the absolute limit SessionFor = %v, want ErrNoSession", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// A lifetime shortened by configuration applies to existing sessions even
|
// A lifetime shortened by configuration applies to existing sessions even
|
||||||
// though their stored expires_at is later.
|
// though their stored expires_at is later.
|
||||||
func TestSessionLifetimeChecksUseCurrentConfig(t *testing.T) {
|
func TestSessionLifetimeChecksUseCurrentConfig(t *testing.T) {
|
||||||
|
|||||||
Reference in New Issue
Block a user