visitor.go 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  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 // Number of messages sent
  27. emails int64 // Number of emails sent
  28. requestLimiter *rate.Limiter // Rate limiter for (almost) all requests (including messages)
  29. emailsLimiter *rate.Limiter // Rate limiter for emails
  30. subscriptionLimiter util.Limiter // Fixed limiter for active subscriptions (ongoing connections)
  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. var messages, emails int64
  52. if user != nil {
  53. messages = user.Stats.Messages
  54. emails = user.Stats.Emails
  55. }
  56. if user != nil && user.Plan != nil {
  57. requestLimiter = rate.NewLimiter(dailyLimitToRate(user.Plan.MessagesLimit), conf.VisitorRequestLimitBurst)
  58. emailsLimiter = rate.NewLimiter(dailyLimitToRate(user.Plan.EmailsLimit), conf.VisitorEmailLimitBurst)
  59. } else {
  60. requestLimiter = rate.NewLimiter(rate.Every(conf.VisitorRequestLimitReplenish), conf.VisitorRequestLimitBurst)
  61. emailsLimiter = rate.NewLimiter(rate.Every(conf.VisitorEmailLimitReplenish), conf.VisitorEmailLimitBurst)
  62. }
  63. return &visitor{
  64. config: conf,
  65. messageCache: messageCache,
  66. ip: ip,
  67. user: user,
  68. messages: messages,
  69. emails: emails,
  70. requestLimiter: requestLimiter,
  71. emailsLimiter: emailsLimiter,
  72. subscriptionLimiter: util.NewFixedLimiter(int64(conf.VisitorSubscriptionLimit)),
  73. bandwidthLimiter: util.NewBytesLimiter(conf.VisitorAttachmentDailyBandwidthLimit, 24*time.Hour),
  74. firebase: time.Unix(0, 0),
  75. seen: time.Now(),
  76. }
  77. }
  78. func (v *visitor) RequestAllowed() error {
  79. if !v.requestLimiter.Allow() {
  80. return errVisitorLimitReached
  81. }
  82. return nil
  83. }
  84. func (v *visitor) FirebaseAllowed() error {
  85. v.mu.Lock()
  86. defer v.mu.Unlock()
  87. if time.Now().Before(v.firebase) {
  88. return errVisitorLimitReached
  89. }
  90. return nil
  91. }
  92. func (v *visitor) FirebaseTemporarilyDeny() {
  93. v.mu.Lock()
  94. defer v.mu.Unlock()
  95. v.firebase = time.Now().Add(v.config.FirebaseQuotaExceededPenaltyDuration)
  96. }
  97. func (v *visitor) EmailAllowed() error {
  98. if !v.emailsLimiter.Allow() {
  99. return errVisitorLimitReached
  100. }
  101. return nil
  102. }
  103. func (v *visitor) SubscriptionAllowed() error {
  104. v.mu.Lock()
  105. defer v.mu.Unlock()
  106. if err := v.subscriptionLimiter.Allow(1); err != nil {
  107. return errVisitorLimitReached
  108. }
  109. return nil
  110. }
  111. func (v *visitor) RemoveSubscription() {
  112. v.mu.Lock()
  113. defer v.mu.Unlock()
  114. v.subscriptionLimiter.Allow(-1)
  115. }
  116. func (v *visitor) Keepalive() {
  117. v.mu.Lock()
  118. defer v.mu.Unlock()
  119. v.seen = time.Now()
  120. }
  121. func (v *visitor) BandwidthLimiter() util.Limiter {
  122. return v.bandwidthLimiter
  123. }
  124. func (v *visitor) Stale() bool {
  125. v.mu.Lock()
  126. defer v.mu.Unlock()
  127. return time.Since(v.seen) > visitorExpungeAfter
  128. }
  129. func (v *visitor) IncrMessages() {
  130. v.mu.Lock()
  131. defer v.mu.Unlock()
  132. v.messages++
  133. if v.user != nil {
  134. v.user.Stats.Messages = v.messages
  135. }
  136. }
  137. func (v *visitor) IncrEmails() {
  138. v.mu.Lock()
  139. defer v.mu.Unlock()
  140. v.emails++
  141. if v.user != nil {
  142. v.user.Stats.Emails = v.emails
  143. }
  144. }
  145. func (v *visitor) Stats() (*visitorStats, error) {
  146. v.mu.Lock()
  147. messages := v.messages
  148. emails := v.emails
  149. v.mu.Unlock()
  150. stats := &visitorStats{}
  151. if v.user != nil && v.user.Role == auth.RoleAdmin {
  152. stats.Basis = "role"
  153. stats.MessagesLimit = 0
  154. stats.EmailsLimit = 0
  155. stats.AttachmentTotalSizeLimit = 0
  156. stats.AttachmentFileSizeLimit = 0
  157. } else if v.user != nil && v.user.Plan != nil {
  158. stats.Basis = "plan"
  159. stats.MessagesLimit = v.user.Plan.MessagesLimit
  160. stats.EmailsLimit = v.user.Plan.EmailsLimit
  161. stats.AttachmentTotalSizeLimit = v.user.Plan.AttachmentTotalSizeLimit
  162. stats.AttachmentFileSizeLimit = v.user.Plan.AttachmentFileSizeLimit
  163. } else {
  164. stats.Basis = "ip"
  165. stats.MessagesLimit = replenishDurationToDailyLimit(v.config.VisitorRequestLimitReplenish)
  166. stats.EmailsLimit = replenishDurationToDailyLimit(v.config.VisitorEmailLimitReplenish)
  167. stats.AttachmentTotalSizeLimit = v.config.VisitorAttachmentTotalSizeLimit
  168. stats.AttachmentFileSizeLimit = v.config.AttachmentFileSizeLimit
  169. }
  170. var attachmentsBytesUsed int64
  171. var err error
  172. if v.user != nil {
  173. attachmentsBytesUsed, err = v.messageCache.AttachmentBytesUsedByUser(v.user.Name)
  174. } else {
  175. attachmentsBytesUsed, err = v.messageCache.AttachmentBytesUsedBySender(v.ip.String())
  176. }
  177. if err != nil {
  178. return nil, err
  179. }
  180. stats.Messages = messages
  181. stats.MessagesRemaining = zeroIfNegative(stats.MessagesLimit - stats.Messages)
  182. stats.Emails = emails
  183. stats.EmailsRemaining = zeroIfNegative(stats.EmailsLimit - stats.Emails)
  184. stats.AttachmentTotalSize = attachmentsBytesUsed
  185. stats.AttachmentTotalSizeRemaining = zeroIfNegative(stats.AttachmentTotalSizeLimit - stats.AttachmentTotalSize)
  186. return stats, nil
  187. }
  188. func zeroIfNegative(value int64) int64 {
  189. if value < 0 {
  190. return 0
  191. }
  192. return value
  193. }
  194. func replenishDurationToDailyLimit(duration time.Duration) int64 {
  195. return int64(24 * time.Hour / duration)
  196. }
  197. func dailyLimitToRate(limit int64) rate.Limit {
  198. return rate.Limit(limit) * rate.Every(24*time.Hour)
  199. }