tokenmanager.go 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  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. "time"
  9. "github.com/syncthing/syncthing/lib/db"
  10. "github.com/syncthing/syncthing/lib/rand"
  11. "github.com/syncthing/syncthing/lib/sync"
  12. "golang.org/x/exp/slices"
  13. )
  14. type tokenManager struct {
  15. key string
  16. miscDB *db.NamespacedKV
  17. lifetime time.Duration
  18. maxItems int
  19. timeNow func() time.Time // can be overridden for testing
  20. mut sync.Mutex
  21. tokens *TokenSet
  22. saveTimer *time.Timer
  23. }
  24. func newTokenManager(key string, miscDB *db.NamespacedKV, lifetime time.Duration, maxItems int) *tokenManager {
  25. tokens := &TokenSet{
  26. Tokens: make(map[string]int64),
  27. }
  28. if bs, ok, _ := miscDB.Bytes(key); ok {
  29. _ = tokens.Unmarshal(bs) // best effort
  30. }
  31. return &tokenManager{
  32. key: key,
  33. miscDB: miscDB,
  34. lifetime: lifetime,
  35. maxItems: maxItems,
  36. timeNow: time.Now,
  37. mut: sync.NewMutex(),
  38. tokens: tokens,
  39. }
  40. }
  41. // Check returns true if the token is valid, and updates the token's expiry
  42. // time. The token is removed if it is expired.
  43. func (m *tokenManager) Check(token string) bool {
  44. m.mut.Lock()
  45. defer m.mut.Unlock()
  46. expires, ok := m.tokens.Tokens[token]
  47. if ok {
  48. if expires < m.timeNow().UnixNano() {
  49. // The token is expired.
  50. m.saveLocked() // removes expired tokens
  51. return false
  52. }
  53. // Give the token further life.
  54. m.tokens.Tokens[token] = m.timeNow().Add(m.lifetime).UnixNano()
  55. m.saveLocked()
  56. }
  57. return ok
  58. }
  59. // New creates a new token and returns it.
  60. func (m *tokenManager) New() string {
  61. token := rand.String(randomTokenLength)
  62. m.mut.Lock()
  63. defer m.mut.Unlock()
  64. m.tokens.Tokens[token] = m.timeNow().Add(m.lifetime).UnixNano()
  65. m.saveLocked()
  66. return token
  67. }
  68. // Delete removes a token.
  69. func (m *tokenManager) Delete(token string) {
  70. m.mut.Lock()
  71. defer m.mut.Unlock()
  72. delete(m.tokens.Tokens, token)
  73. m.saveLocked()
  74. }
  75. func (m *tokenManager) saveLocked() {
  76. // Remove expired tokens.
  77. now := m.timeNow().UnixNano()
  78. for token, expiry := range m.tokens.Tokens {
  79. if expiry < now {
  80. delete(m.tokens.Tokens, token)
  81. }
  82. }
  83. // If we have a limit on the number of tokens, remove the oldest ones.
  84. if m.maxItems > 0 && len(m.tokens.Tokens) > m.maxItems {
  85. // Sort the tokens by expiry time, oldest first.
  86. type tokenExpiry struct {
  87. token string
  88. expiry int64
  89. }
  90. var tokens []tokenExpiry
  91. for token, expiry := range m.tokens.Tokens {
  92. tokens = append(tokens, tokenExpiry{token, expiry})
  93. }
  94. slices.SortFunc(tokens, func(i, j tokenExpiry) int {
  95. return int(i.expiry - j.expiry)
  96. })
  97. // Remove the oldest tokens.
  98. for _, token := range tokens[:len(tokens)-m.maxItems] {
  99. delete(m.tokens.Tokens, token.token)
  100. }
  101. }
  102. // Postpone saving until one second of inactivity.
  103. if m.saveTimer == nil {
  104. m.saveTimer = time.AfterFunc(time.Second, m.scheduledSave)
  105. } else {
  106. m.saveTimer.Reset(time.Second)
  107. }
  108. }
  109. func (m *tokenManager) scheduledSave() {
  110. m.mut.Lock()
  111. defer m.mut.Unlock()
  112. m.saveTimer = nil
  113. bs, _ := m.tokens.Marshal() // can't fail
  114. _ = m.miscDB.PutBytes(m.key, bs) // can fail, but what are we going to do?
  115. }