diff options
Diffstat (limited to 'lib/api/tokenmanager.go')
-rw-r--r-- | lib/api/tokenmanager.go | 87 |
1 files changed, 87 insertions, 0 deletions
diff --git a/lib/api/tokenmanager.go b/lib/api/tokenmanager.go index 73ba8425e..adccda2e9 100644 --- a/lib/api/tokenmanager.go +++ b/lib/api/tokenmanager.go @@ -7,10 +7,14 @@ package api import ( + "net/http" "slices" + "strings" "time" + "github.com/syncthing/syncthing/lib/config" "github.com/syncthing/syncthing/lib/db" + "github.com/syncthing/syncthing/lib/events" "github.com/syncthing/syncthing/lib/rand" "github.com/syncthing/syncthing/lib/sync" ) @@ -135,3 +139,86 @@ func (m *tokenManager) scheduledSave() { bs, _ := m.tokens.Marshal() // can't fail _ = m.miscDB.PutBytes(m.key, bs) // can fail, but what are we going to do? } + +type tokenCookieManager struct { + cookieName string + shortID string + guiCfg config.GUIConfiguration + evLogger events.Logger + tokens *tokenManager +} + +func newTokenCookieManager(shortID string, guiCfg config.GUIConfiguration, evLogger events.Logger, miscDB *db.NamespacedKV) *tokenCookieManager { + return &tokenCookieManager{ + cookieName: "sessionid-" + shortID, + shortID: shortID, + guiCfg: guiCfg, + evLogger: evLogger, + tokens: newTokenManager("sessions", miscDB, maxSessionLifetime, maxActiveSessions), + } +} + +func (m *tokenCookieManager) createSession(username string, persistent bool, w http.ResponseWriter, r *http.Request) { + sessionid := m.tokens.New() + + // Best effort detection of whether the connection is HTTPS -- + // either directly to us, or as used by the client towards a reverse + // proxy who sends us headers. + connectionIsHTTPS := r.TLS != nil || + strings.ToLower(r.Header.Get("x-forwarded-proto")) == "https" || + strings.Contains(strings.ToLower(r.Header.Get("forwarded")), "proto=https") + // If the connection is HTTPS, or *should* be HTTPS, set the Secure + // bit in cookies. + useSecureCookie := connectionIsHTTPS || m.guiCfg.UseTLS() + + maxAge := 0 + if persistent { + maxAge = int(maxSessionLifetime.Seconds()) + } + http.SetCookie(w, &http.Cookie{ + Name: m.cookieName, + Value: sessionid, + // In HTTP spec Max-Age <= 0 means delete immediately, + // but in http.Cookie MaxAge = 0 means unspecified (session) and MaxAge < 0 means delete immediately + MaxAge: maxAge, + Secure: useSecureCookie, + Path: "/", + }) + + emitLoginAttempt(true, username, r.RemoteAddr, m.evLogger) +} + +func (m *tokenCookieManager) hasValidSession(r *http.Request) bool { + for _, cookie := range r.Cookies() { + // We iterate here since there may, historically, be multiple + // cookies with the same name but different path. Any "old" ones + // won't match an existing session and will be ignored, then + // later removed on logout or when timing out. + if cookie.Name == m.cookieName { + if m.tokens.Check(cookie.Value) { + return true + } + } + } + return false +} + +func (m *tokenCookieManager) destroySession(w http.ResponseWriter, r *http.Request) { + for _, cookie := range r.Cookies() { + // We iterate here since there may, historically, be multiple + // cookies with the same name but different path. We drop them + // all. + if cookie.Name == m.cookieName { + m.tokens.Delete(cookie.Value) + + // Create a cookie deletion command + http.SetCookie(w, &http.Cookie{ + Name: m.cookieName, + Value: "", + MaxAge: -1, + Secure: cookie.Secure, + Path: cookie.Path, + }) + } + } +} |