security: 15-day session lifetime with 7-day auto-renewal

- Reduce SESSION_MAX_DURATION from 365 days to 15 days
- Add SESSION_RENEWAL_THRESHOLD (7 days): sessions are only extended
  when fewer than 7 days remain, instead of refreshing on every request
- Align cookie MaxAge with SESSION_MAX_DURATION (derived from the constant)
- Enforce expiry in load(): expired sessions are deleted on first use
  and the caller receives an error, preventing Bearer-token replay of
  stale sessions that the securecookie age check would not catch

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
nemunaire 2026-03-07 23:46:07 +07:00
commit 41fac845eb
2 changed files with 15 additions and 5 deletions

View file

@ -50,7 +50,7 @@ func NewSessionStore(opts *happydns.Options, storage sessionUC.SessionStorage, k
Codecs: securecookie.CodecsFromPairs(keyPairs...),
options: &sessions.Options{
Path: opts.BasePath + "/",
MaxAge: 86400 * 30,
MaxAge: int(sessionUC.SESSION_MAX_DURATION.Seconds()),
Secure: opts.DevProxy == "" && opts.ExternalURL.Scheme != "http",
HttpOnly: true,
SameSite: http.SameSiteLaxMode,
@ -171,6 +171,11 @@ func (s *SessionStore) load(session *sessions.Session) error {
session.Values["created_on"] = mysession.IssuedAt
}
if !mysession.ExpiresOn.IsZero() {
if mysession.ExpiresOn.Before(time.Now()) {
// Session has expired; delete it and treat this as a new session.
_ = s.storage.DeleteSession(session.ID)
return fmt.Errorf("session has expired")
}
session.Values["expires_on"] = mysession.ExpiresOn
}
@ -210,11 +215,12 @@ func (s *SessionStore) save(session *sessions.Session, ua string) error {
}
if exOn == nil {
expiresOn = time.Now().Add(time.Second * time.Duration(session.Options.MaxAge))
expiresOn = time.Now().Add(sessionUC.SESSION_MAX_DURATION)
} else {
expiresOn = exOn.(time.Time)
if expiresOn.Sub(time.Now().Add(time.Second*time.Duration(session.Options.MaxAge))) < 0 {
expiresOn = time.Now().Add(time.Second * time.Duration(session.Options.MaxAge))
// Auto-renew if the session expires within the renewal window.
if time.Until(expiresOn) < sessionUC.SESSION_RENEWAL_THRESHOLD {
expiresOn = time.Now().Add(sessionUC.SESSION_MAX_DURATION)
}
}

View file

@ -33,7 +33,11 @@ import (
"git.happydns.org/happyDomain/model"
)
const SESSION_MAX_DURATION = 24 * 365 * time.Hour
const SESSION_MAX_DURATION = 15 * 24 * time.Hour
// SESSION_RENEWAL_THRESHOLD is the remaining lifetime below which a session
// is automatically renewed to SESSION_MAX_DURATION on the next request.
const SESSION_RENEWAL_THRESHOLD = 7 * 24 * time.Hour
// Service handles all session-related operations.
// This consolidates what were previously separate usecase structs into a single service.