visitor.go 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. package server
  2. import (
  3. "errors"
  4. "heckel.io/ntfy/auth"
  5. "net/netip"
  6. "sync"
  7. "time"
  8. "golang.org/x/time/rate"
  9. "heckel.io/ntfy/util"
  10. )
  11. const (
  12. // visitorExpungeAfter defines how long a visitor is active before it is removed from memory. This number
  13. // has to be very high to prevent e-mail abuse, but it doesn't really affect the other limits anyway, since
  14. // they are replenished faster (typically).
  15. visitorExpungeAfter = 24 * time.Hour
  16. )
  17. var (
  18. errVisitorLimitReached = errors.New("limit reached")
  19. )
  20. // visitor represents an API user, and its associated rate.Limiter used for rate limiting
  21. type visitor struct {
  22. config *Config
  23. messageCache *messageCache
  24. ip netip.Addr
  25. user *auth.User
  26. messages int64
  27. emails int64
  28. requestLimiter *rate.Limiter
  29. emailsLimiter *rate.Limiter
  30. subscriptionLimiter util.Limiter
  31. bandwidthLimiter util.Limiter
  32. firebase time.Time // Next allowed Firebase message
  33. seen time.Time
  34. mu sync.Mutex
  35. }
  36. type visitorStats struct {
  37. Basis string // "ip", "role" or "plan"
  38. Messages int64
  39. MessagesLimit int64
  40. MessagesRemaining int64
  41. Emails int64
  42. EmailsLimit int64
  43. EmailsRemaining int64
  44. AttachmentTotalSize int64
  45. AttachmentTotalSizeLimit int64
  46. AttachmentTotalSizeRemaining int64
  47. AttachmentFileSizeLimit int64
  48. }
  49. func newVisitor(conf *Config, messageCache *messageCache, ip netip.Addr, user *auth.User) *visitor {
  50. var requestLimiter, emailsLimiter *rate.Limiter
  51. if user != nil && user.Plan != nil {
  52. requestLimiter = rate.NewLimiter(dailyLimitToRate(user.Plan.MessagesLimit), conf.VisitorRequestLimitBurst)
  53. emailsLimiter = rate.NewLimiter(dailyLimitToRate(user.Plan.EmailsLimit), conf.VisitorEmailLimitBurst)
  54. } else {
  55. requestLimiter = rate.NewLimiter(rate.Every(conf.VisitorRequestLimitReplenish), conf.VisitorRequestLimitBurst)
  56. emailsLimiter = rate.NewLimiter(rate.Every(conf.VisitorEmailLimitReplenish), conf.VisitorEmailLimitBurst)
  57. }
  58. return &visitor{
  59. config: conf,
  60. messageCache: messageCache,
  61. ip: ip,
  62. user: user,
  63. messages: 0, // TODO
  64. emails: 0, // TODO
  65. requestLimiter: requestLimiter,
  66. emailsLimiter: emailsLimiter,
  67. subscriptionLimiter: util.NewFixedLimiter(int64(conf.VisitorSubscriptionLimit)),
  68. bandwidthLimiter: util.NewBytesLimiter(conf.VisitorAttachmentDailyBandwidthLimit, 24*time.Hour),
  69. firebase: time.Unix(0, 0),
  70. seen: time.Now(),
  71. }
  72. }
  73. func (v *visitor) RequestAllowed() error {
  74. if !v.requestLimiter.Allow() {
  75. return errVisitorLimitReached
  76. }
  77. return nil
  78. }
  79. func (v *visitor) FirebaseAllowed() error {
  80. v.mu.Lock()
  81. defer v.mu.Unlock()
  82. if time.Now().Before(v.firebase) {
  83. return errVisitorLimitReached
  84. }
  85. return nil
  86. }
  87. func (v *visitor) FirebaseTemporarilyDeny() {
  88. v.mu.Lock()
  89. defer v.mu.Unlock()
  90. v.firebase = time.Now().Add(v.config.FirebaseQuotaExceededPenaltyDuration)
  91. }
  92. func (v *visitor) EmailAllowed() error {
  93. if !v.emailsLimiter.Allow() {
  94. return errVisitorLimitReached
  95. }
  96. return nil
  97. }
  98. func (v *visitor) SubscriptionAllowed() error {
  99. v.mu.Lock()
  100. defer v.mu.Unlock()
  101. if err := v.subscriptionLimiter.Allow(1); err != nil {
  102. return errVisitorLimitReached
  103. }
  104. return nil
  105. }
  106. func (v *visitor) RemoveSubscription() {
  107. v.mu.Lock()
  108. defer v.mu.Unlock()
  109. v.subscriptionLimiter.Allow(-1)
  110. }
  111. func (v *visitor) Keepalive() {
  112. v.mu.Lock()
  113. defer v.mu.Unlock()
  114. v.seen = time.Now()
  115. }
  116. func (v *visitor) BandwidthLimiter() util.Limiter {
  117. return v.bandwidthLimiter
  118. }
  119. func (v *visitor) Stale() bool {
  120. v.mu.Lock()
  121. defer v.mu.Unlock()
  122. return time.Since(v.seen) > visitorExpungeAfter
  123. }
  124. func (v *visitor) IncrMessages() {
  125. v.mu.Lock()
  126. defer v.mu.Unlock()
  127. v.messages++
  128. }
  129. func (v *visitor) IncrEmails() {
  130. v.mu.Lock()
  131. defer v.mu.Unlock()
  132. v.emails++
  133. }
  134. func (v *visitor) Stats() (*visitorStats, error) {
  135. v.mu.Lock()
  136. messages := v.messages
  137. emails := v.emails
  138. v.mu.Unlock()
  139. stats := &visitorStats{}
  140. if v.user != nil && v.user.Role == auth.RoleAdmin {
  141. stats.Basis = "role"
  142. stats.MessagesLimit = 0
  143. stats.EmailsLimit = 0
  144. stats.AttachmentTotalSizeLimit = 0
  145. stats.AttachmentFileSizeLimit = 0
  146. } else if v.user != nil && v.user.Plan != nil {
  147. stats.Basis = "plan"
  148. stats.MessagesLimit = v.user.Plan.MessagesLimit
  149. stats.EmailsLimit = v.user.Plan.EmailsLimit
  150. stats.AttachmentTotalSizeLimit = v.user.Plan.AttachmentTotalSizeLimit
  151. stats.AttachmentFileSizeLimit = v.user.Plan.AttachmentFileSizeLimit
  152. } else {
  153. stats.Basis = "ip"
  154. stats.MessagesLimit = replenishDurationToDailyLimit(v.config.VisitorRequestLimitReplenish)
  155. stats.EmailsLimit = replenishDurationToDailyLimit(v.config.VisitorEmailLimitReplenish)
  156. stats.AttachmentTotalSizeLimit = v.config.VisitorAttachmentTotalSizeLimit
  157. stats.AttachmentFileSizeLimit = v.config.AttachmentFileSizeLimit
  158. }
  159. var attachmentsBytesUsed int64
  160. var err error
  161. if v.user != nil {
  162. attachmentsBytesUsed, err = v.messageCache.AttachmentBytesUsedByUser(v.user.Name)
  163. } else {
  164. attachmentsBytesUsed, err = v.messageCache.AttachmentBytesUsedBySender(v.ip.String())
  165. }
  166. if err != nil {
  167. return nil, err
  168. }
  169. stats.Messages = messages
  170. stats.MessagesRemaining = zeroIfNegative(stats.MessagesLimit - stats.MessagesLimit)
  171. stats.Emails = emails
  172. stats.EmailsRemaining = zeroIfNegative(stats.EmailsLimit - stats.EmailsRemaining)
  173. stats.AttachmentTotalSize = attachmentsBytesUsed
  174. stats.AttachmentTotalSizeRemaining = zeroIfNegative(stats.AttachmentTotalSizeLimit - stats.AttachmentTotalSize)
  175. return stats, nil
  176. }
  177. func zeroIfNegative(value int64) int64 {
  178. if value < 0 {
  179. return 0
  180. }
  181. return value
  182. }
  183. func replenishDurationToDailyLimit(duration time.Duration) int64 {
  184. return int64(24 * time.Hour / duration)
  185. }
  186. func dailyLimitToRate(limit int64) rate.Limit {
  187. return rate.Limit(limit) * rate.Every(24*time.Hour)
  188. }