Round session cookie Max-Age without overflowing
Adding time.Second-1 to MEALQUEST_SESSION_MAX overflowed time.Duration near its largest accepted value, giving a negative MaxAge that net/http sends as Max-Age=0. Divide by time.Second and increment the quotient when there is a remainder instead. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -93,10 +93,21 @@ 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. Max-Age is whole
|
// the server decides whether the session is still live. Max-Age is whole
|
||||||
// seconds, rounded up so the cookie never ends before the absolute limit.
|
// 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)))
|
http.SetCookie(w, sessionCookie(id, cookieMaxAge(m.max)))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// cookieMaxAge is lifetime in whole seconds, rounded up. It rounds the
|
||||||
|
// quotient rather than adding to lifetime, which would overflow near the
|
||||||
|
// largest time.Duration.
|
||||||
|
func cookieMaxAge(lifetime time.Duration) int {
|
||||||
|
seconds := lifetime / time.Second
|
||||||
|
if lifetime%time.Second != 0 {
|
||||||
|
seconds++
|
||||||
|
}
|
||||||
|
return int(seconds)
|
||||||
|
}
|
||||||
|
|
||||||
// SessionFor returns the live session r carries and slides its last use to
|
// SessionFor returns the live session r carries and slides its last use to
|
||||||
// now. It returns ErrNoSession when there is none; an expired session is
|
// now. It returns ErrNoSession when there is none; an expired session is
|
||||||
// deleted.
|
// deleted.
|
||||||
|
|||||||
@@ -333,6 +333,22 @@ func TestSessionCookieOutlivesFractionalMax(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A lifetime near the largest accepted duration still yields a positive,
|
||||||
|
// rounded-up cookie Max-Age rather than overflowing.
|
||||||
|
func TestSessionCookieMaxAgeNearLargestMax(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
largest, err := time.ParseDuration("2562047h47m16.854s")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
h := newSessionHarness(t)
|
||||||
|
h.sessions.max = largest
|
||||||
|
_, cookie := h.signIn(t)
|
||||||
|
if want := 9223372037; cookie.MaxAge != want {
|
||||||
|
t.Errorf("session cookie MaxAge = %d, want %d", cookie.MaxAge, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 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