tokenmanager.go 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. // Copyright (C) 2024 The Syncthing Authors.
  2. //
  3. // This Source Code Form is subject to the terms of the Mozilla Public
  4. // License, v. 2.0. If a copy of the MPL was not distributed with this file,
  5. // You can obtain one at https://mozilla.org/MPL/2.0/.
  6. package api
  7. import (
  8. "net/http"
  9. "slices"
  10. "strings"
  11. "time"
  12. "google.golang.org/protobuf/proto"
  13. "github.com/syncthing/syncthing/internal/gen/apiproto"
  14. "github.com/syncthing/syncthing/lib/config"
  15. "github.com/syncthing/syncthing/lib/db"
  16. "github.com/syncthing/syncthing/lib/events"
  17. "github.com/syncthing/syncthing/lib/rand"
  18. "github.com/syncthing/syncthing/lib/sync"
  19. )
  20. type tokenManager struct {
  21. key string
  22. miscDB *db.NamespacedKV
  23. lifetime time.Duration
  24. maxItems int
  25. timeNow func() time.Time // can be overridden for testing
  26. mut sync.Mutex
  27. tokens *apiproto.TokenSet
  28. saveTimer *time.Timer
  29. }
  30. func newTokenManager(key string, miscDB *db.NamespacedKV, lifetime time.Duration, maxItems int) *tokenManager {
  31. tokens := &apiproto.TokenSet{
  32. Tokens: make(map[string]int64),
  33. }
  34. if bs, ok, _ := miscDB.Bytes(key); ok {
  35. _ = proto.Unmarshal(bs, tokens) // best effort
  36. }
  37. return &tokenManager{
  38. key: key,
  39. miscDB: miscDB,
  40. lifetime: lifetime,
  41. maxItems: maxItems,
  42. timeNow: time.Now,
  43. mut: sync.NewMutex(),
  44. tokens: tokens,
  45. }
  46. }
  47. // Check returns true if the token is valid, and updates the token's expiry
  48. // time. The token is removed if it is expired.
  49. func (m *tokenManager) Check(token string) bool {
  50. m.mut.Lock()
  51. defer m.mut.Unlock()
  52. expires, ok := m.tokens.Tokens[token]
  53. if ok {
  54. if expires < m.timeNow().UnixNano() {
  55. // The token is expired.
  56. m.saveLocked() // removes expired tokens
  57. return false
  58. }
  59. // Give the token further life.
  60. m.tokens.Tokens[token] = m.timeNow().Add(m.lifetime).UnixNano()
  61. m.saveLocked()
  62. }
  63. return ok
  64. }
  65. // New creates a new token and returns it.
  66. func (m *tokenManager) New() string {
  67. token := rand.String(randomTokenLength)
  68. m.mut.Lock()
  69. defer m.mut.Unlock()
  70. m.tokens.Tokens[token] = m.timeNow().Add(m.lifetime).UnixNano()
  71. m.saveLocked()
  72. return token
  73. }
  74. // Delete removes a token.
  75. func (m *tokenManager) Delete(token string) {
  76. m.mut.Lock()
  77. defer m.mut.Unlock()
  78. delete(m.tokens.Tokens, token)
  79. m.saveLocked()
  80. }
  81. func (m *tokenManager) saveLocked() {
  82. // Remove expired tokens.
  83. now := m.timeNow().UnixNano()
  84. for token, expiry := range m.tokens.Tokens {
  85. if expiry < now {
  86. delete(m.tokens.Tokens, token)
  87. }
  88. }
  89. // If we have a limit on the number of tokens, remove the oldest ones.
  90. if m.maxItems > 0 && len(m.tokens.Tokens) > m.maxItems {
  91. // Sort the tokens by expiry time, oldest first.
  92. type tokenExpiry struct {
  93. token string
  94. expiry int64
  95. }
  96. var tokens []tokenExpiry
  97. for token, expiry := range m.tokens.Tokens {
  98. tokens = append(tokens, tokenExpiry{token, expiry})
  99. }
  100. slices.SortFunc(tokens, func(i, j tokenExpiry) int {
  101. return int(i.expiry - j.expiry)
  102. })
  103. // Remove the oldest tokens.
  104. for _, token := range tokens[:len(tokens)-m.maxItems] {
  105. delete(m.tokens.Tokens, token.token)
  106. }
  107. }
  108. // Postpone saving until one second of inactivity.
  109. if m.saveTimer == nil {
  110. m.saveTimer = time.AfterFunc(time.Second, m.scheduledSave)
  111. } else {
  112. m.saveTimer.Reset(time.Second)
  113. }
  114. }
  115. func (m *tokenManager) scheduledSave() {
  116. m.mut.Lock()
  117. defer m.mut.Unlock()
  118. m.saveTimer = nil
  119. bs, _ := proto.Marshal(m.tokens) // can't fail
  120. _ = m.miscDB.PutBytes(m.key, bs) // can fail, but what are we going to do?
  121. }
  122. type tokenCookieManager struct {
  123. cookieName string
  124. shortID string
  125. guiCfg config.GUIConfiguration
  126. evLogger events.Logger
  127. tokens *tokenManager
  128. }
  129. func newTokenCookieManager(shortID string, guiCfg config.GUIConfiguration, evLogger events.Logger, miscDB *db.NamespacedKV) *tokenCookieManager {
  130. return &tokenCookieManager{
  131. cookieName: "sessionid-" + shortID,
  132. shortID: shortID,
  133. guiCfg: guiCfg,
  134. evLogger: evLogger,
  135. tokens: newTokenManager("sessions", miscDB, maxSessionLifetime, maxActiveSessions),
  136. }
  137. }
  138. func (m *tokenCookieManager) createSession(username string, persistent bool, w http.ResponseWriter, r *http.Request) {
  139. sessionid := m.tokens.New()
  140. // Best effort detection of whether the connection is HTTPS --
  141. // either directly to us, or as used by the client towards a reverse
  142. // proxy who sends us headers.
  143. connectionIsHTTPS := r.TLS != nil ||
  144. strings.ToLower(r.Header.Get("x-forwarded-proto")) == "https" ||
  145. strings.Contains(strings.ToLower(r.Header.Get("forwarded")), "proto=https")
  146. // If the connection is HTTPS, or *should* be HTTPS, set the Secure
  147. // bit in cookies.
  148. useSecureCookie := connectionIsHTTPS || m.guiCfg.UseTLS()
  149. maxAge := 0
  150. if persistent {
  151. maxAge = int(maxSessionLifetime.Seconds())
  152. }
  153. http.SetCookie(w, &http.Cookie{
  154. Name: m.cookieName,
  155. Value: sessionid,
  156. // In HTTP spec Max-Age <= 0 means delete immediately,
  157. // but in http.Cookie MaxAge = 0 means unspecified (session) and MaxAge < 0 means delete immediately
  158. MaxAge: maxAge,
  159. Secure: useSecureCookie,
  160. Path: "/",
  161. })
  162. emitLoginAttempt(true, username, r.RemoteAddr, m.evLogger)
  163. }
  164. func (m *tokenCookieManager) hasValidSession(r *http.Request) bool {
  165. for _, cookie := range r.Cookies() {
  166. // We iterate here since there may, historically, be multiple
  167. // cookies with the same name but different path. Any "old" ones
  168. // won't match an existing session and will be ignored, then
  169. // later removed on logout or when timing out.
  170. if cookie.Name == m.cookieName {
  171. if m.tokens.Check(cookie.Value) {
  172. return true
  173. }
  174. }
  175. }
  176. return false
  177. }
  178. func (m *tokenCookieManager) destroySession(w http.ResponseWriter, r *http.Request) {
  179. for _, cookie := range r.Cookies() {
  180. // We iterate here since there may, historically, be multiple
  181. // cookies with the same name but different path. We drop them
  182. // all.
  183. if cookie.Name == m.cookieName {
  184. m.tokens.Delete(cookie.Value)
  185. // Create a cookie deletion command
  186. http.SetCookie(w, &http.Cookie{
  187. Name: m.cookieName,
  188. Value: "",
  189. MaxAge: -1,
  190. Secure: cookie.Secure,
  191. Path: cookie.Path,
  192. })
  193. }
  194. }
  195. }