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:
parent
0803b3af61
commit
41fac845eb
2 changed files with 15 additions and 5 deletions
|
|
@ -50,7 +50,7 @@ func NewSessionStore(opts *happydns.Options, storage sessionUC.SessionStorage, k
|
||||||
Codecs: securecookie.CodecsFromPairs(keyPairs...),
|
Codecs: securecookie.CodecsFromPairs(keyPairs...),
|
||||||
options: &sessions.Options{
|
options: &sessions.Options{
|
||||||
Path: opts.BasePath + "/",
|
Path: opts.BasePath + "/",
|
||||||
MaxAge: 86400 * 30,
|
MaxAge: int(sessionUC.SESSION_MAX_DURATION.Seconds()),
|
||||||
Secure: opts.DevProxy == "" && opts.ExternalURL.Scheme != "http",
|
Secure: opts.DevProxy == "" && opts.ExternalURL.Scheme != "http",
|
||||||
HttpOnly: true,
|
HttpOnly: true,
|
||||||
SameSite: http.SameSiteLaxMode,
|
SameSite: http.SameSiteLaxMode,
|
||||||
|
|
@ -171,6 +171,11 @@ func (s *SessionStore) load(session *sessions.Session) error {
|
||||||
session.Values["created_on"] = mysession.IssuedAt
|
session.Values["created_on"] = mysession.IssuedAt
|
||||||
}
|
}
|
||||||
if !mysession.ExpiresOn.IsZero() {
|
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
|
session.Values["expires_on"] = mysession.ExpiresOn
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -210,11 +215,12 @@ func (s *SessionStore) save(session *sessions.Session, ua string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if exOn == nil {
|
if exOn == nil {
|
||||||
expiresOn = time.Now().Add(time.Second * time.Duration(session.Options.MaxAge))
|
expiresOn = time.Now().Add(sessionUC.SESSION_MAX_DURATION)
|
||||||
} else {
|
} else {
|
||||||
expiresOn = exOn.(time.Time)
|
expiresOn = exOn.(time.Time)
|
||||||
if expiresOn.Sub(time.Now().Add(time.Second*time.Duration(session.Options.MaxAge))) < 0 {
|
// Auto-renew if the session expires within the renewal window.
|
||||||
expiresOn = time.Now().Add(time.Second * time.Duration(session.Options.MaxAge))
|
if time.Until(expiresOn) < sessionUC.SESSION_RENEWAL_THRESHOLD {
|
||||||
|
expiresOn = time.Now().Add(sessionUC.SESSION_MAX_DURATION)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,11 @@ import (
|
||||||
"git.happydns.org/happyDomain/model"
|
"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.
|
// Service handles all session-related operations.
|
||||||
// This consolidates what were previously separate usecase structs into a single service.
|
// This consolidates what were previously separate usecase structs into a single service.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue