visitor.go 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. package server
  2. import (
  3. "errors"
  4. "heckel.io/ntfy/user"
  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. userManager *user.Manager // May be nil!
  25. ip netip.Addr
  26. user *user.User
  27. messages int64 // Number of messages sent
  28. emails int64 // Number of emails sent
  29. requestLimiter *rate.Limiter // Rate limiter for (almost) all requests (including messages)
  30. emailsLimiter *rate.Limiter // Rate limiter for emails
  31. subscriptionLimiter util.Limiter // Fixed limiter for active subscriptions (ongoing connections)
  32. bandwidthLimiter util.Limiter
  33. accountLimiter *rate.Limiter // Rate limiter for account creation
  34. firebase time.Time // Next allowed Firebase message
  35. seen time.Time
  36. mu sync.Mutex
  37. }
  38. type visitorInfo struct {
  39. Basis string // "ip", "role" or "plan"
  40. Messages int64
  41. MessagesLimit int64
  42. MessagesRemaining int64
  43. Emails int64
  44. EmailsLimit int64
  45. EmailsRemaining int64
  46. Topics int64
  47. TopicsLimit int64
  48. TopicsRemaining int64
  49. AttachmentTotalSize int64
  50. AttachmentTotalSizeLimit int64
  51. AttachmentTotalSizeRemaining int64
  52. AttachmentFileSizeLimit int64
  53. }
  54. func newVisitor(conf *Config, messageCache *messageCache, userManager *user.Manager, ip netip.Addr, user *user.User) *visitor {
  55. var requestLimiter, emailsLimiter, accountLimiter *rate.Limiter
  56. var messages, emails int64
  57. if user != nil {
  58. messages = user.Stats.Messages
  59. emails = user.Stats.Emails
  60. } else {
  61. accountLimiter = rate.NewLimiter(rate.Every(conf.VisitorAccountCreateLimitReplenish), conf.VisitorAccountCreateLimitBurst)
  62. }
  63. if user != nil && user.Plan != nil {
  64. requestLimiter = rate.NewLimiter(dailyLimitToRate(user.Plan.MessagesLimit), conf.VisitorRequestLimitBurst)
  65. emailsLimiter = rate.NewLimiter(dailyLimitToRate(user.Plan.EmailsLimit), conf.VisitorEmailLimitBurst)
  66. } else {
  67. requestLimiter = rate.NewLimiter(rate.Every(conf.VisitorRequestLimitReplenish), conf.VisitorRequestLimitBurst)
  68. emailsLimiter = rate.NewLimiter(rate.Every(conf.VisitorEmailLimitReplenish), conf.VisitorEmailLimitBurst)
  69. }
  70. return &visitor{
  71. config: conf,
  72. messageCache: messageCache,
  73. userManager: userManager, // May be nil!
  74. ip: ip,
  75. user: user,
  76. messages: messages,
  77. emails: emails,
  78. requestLimiter: requestLimiter,
  79. emailsLimiter: emailsLimiter,
  80. subscriptionLimiter: util.NewFixedLimiter(int64(conf.VisitorSubscriptionLimit)),
  81. bandwidthLimiter: util.NewBytesLimiter(conf.VisitorAttachmentDailyBandwidthLimit, 24*time.Hour),
  82. accountLimiter: accountLimiter, // May be nil
  83. firebase: time.Unix(0, 0),
  84. seen: time.Now(),
  85. }
  86. }
  87. func (v *visitor) RequestAllowed() error {
  88. if !v.requestLimiter.Allow() {
  89. return errVisitorLimitReached
  90. }
  91. return nil
  92. }
  93. func (v *visitor) FirebaseAllowed() error {
  94. v.mu.Lock()
  95. defer v.mu.Unlock()
  96. if time.Now().Before(v.firebase) {
  97. return errVisitorLimitReached
  98. }
  99. return nil
  100. }
  101. func (v *visitor) FirebaseTemporarilyDeny() {
  102. v.mu.Lock()
  103. defer v.mu.Unlock()
  104. v.firebase = time.Now().Add(v.config.FirebaseQuotaExceededPenaltyDuration)
  105. }
  106. func (v *visitor) EmailAllowed() error {
  107. if !v.emailsLimiter.Allow() {
  108. return errVisitorLimitReached
  109. }
  110. return nil
  111. }
  112. func (v *visitor) SubscriptionAllowed() error {
  113. v.mu.Lock()
  114. defer v.mu.Unlock()
  115. if err := v.subscriptionLimiter.Allow(1); err != nil {
  116. return errVisitorLimitReached
  117. }
  118. return nil
  119. }
  120. func (v *visitor) RemoveSubscription() {
  121. v.mu.Lock()
  122. defer v.mu.Unlock()
  123. v.subscriptionLimiter.Allow(-1)
  124. }
  125. func (v *visitor) Keepalive() {
  126. v.mu.Lock()
  127. defer v.mu.Unlock()
  128. v.seen = time.Now()
  129. }
  130. func (v *visitor) BandwidthLimiter() util.Limiter {
  131. return v.bandwidthLimiter
  132. }
  133. func (v *visitor) Stale() bool {
  134. v.mu.Lock()
  135. defer v.mu.Unlock()
  136. return time.Since(v.seen) > visitorExpungeAfter
  137. }
  138. func (v *visitor) IncrMessages() {
  139. v.mu.Lock()
  140. defer v.mu.Unlock()
  141. v.messages++
  142. if v.user != nil {
  143. v.user.Stats.Messages = v.messages
  144. }
  145. }
  146. func (v *visitor) IncrEmails() {
  147. v.mu.Lock()
  148. defer v.mu.Unlock()
  149. v.emails++
  150. if v.user != nil {
  151. v.user.Stats.Emails = v.emails
  152. }
  153. }
  154. func (v *visitor) Info() (*visitorInfo, error) {
  155. v.mu.Lock()
  156. messages := v.messages
  157. emails := v.emails
  158. v.mu.Unlock()
  159. info := &visitorInfo{}
  160. if v.user != nil && v.user.Role == user.RoleAdmin {
  161. info.Basis = "role"
  162. // All limits are zero!
  163. } else if v.user != nil && v.user.Plan != nil {
  164. info.Basis = "plan"
  165. info.MessagesLimit = v.user.Plan.MessagesLimit
  166. info.EmailsLimit = v.user.Plan.EmailsLimit
  167. info.TopicsLimit = v.user.Plan.TopicsLimit
  168. info.AttachmentTotalSizeLimit = v.user.Plan.AttachmentTotalSizeLimit
  169. info.AttachmentFileSizeLimit = v.user.Plan.AttachmentFileSizeLimit
  170. } else {
  171. info.Basis = "ip"
  172. info.MessagesLimit = replenishDurationToDailyLimit(v.config.VisitorRequestLimitReplenish)
  173. info.EmailsLimit = replenishDurationToDailyLimit(v.config.VisitorEmailLimitReplenish)
  174. info.TopicsLimit = 0 // FIXME
  175. info.AttachmentTotalSizeLimit = v.config.VisitorAttachmentTotalSizeLimit
  176. info.AttachmentFileSizeLimit = v.config.AttachmentFileSizeLimit
  177. }
  178. var attachmentsBytesUsed int64 // FIXME Maybe move this to endpoint?
  179. var err error
  180. if v.user != nil {
  181. attachmentsBytesUsed, err = v.messageCache.AttachmentBytesUsedByUser(v.user.Name)
  182. } else {
  183. attachmentsBytesUsed, err = v.messageCache.AttachmentBytesUsedBySender(v.ip.String())
  184. }
  185. if err != nil {
  186. return nil, err
  187. }
  188. var topics int64
  189. if v.user != nil && v.userManager != nil {
  190. reservations, err := v.userManager.Reservations(v.user.Name) // FIXME dup call, move this to endpoint?
  191. if err != nil {
  192. return nil, err
  193. }
  194. topics = int64(len(reservations))
  195. }
  196. info.Messages = messages
  197. info.MessagesRemaining = zeroIfNegative(info.MessagesLimit - info.Messages)
  198. info.Emails = emails
  199. info.EmailsRemaining = zeroIfNegative(info.EmailsLimit - info.Emails)
  200. info.Topics = topics
  201. info.TopicsRemaining = zeroIfNegative(info.TopicsLimit - info.Topics)
  202. info.AttachmentTotalSize = attachmentsBytesUsed
  203. info.AttachmentTotalSizeRemaining = zeroIfNegative(info.AttachmentTotalSizeLimit - info.AttachmentTotalSize)
  204. return info, nil
  205. }
  206. func zeroIfNegative(value int64) int64 {
  207. if value < 0 {
  208. return 0
  209. }
  210. return value
  211. }
  212. func replenishDurationToDailyLimit(duration time.Duration) int64 {
  213. return int64(24 * time.Hour / duration)
  214. }
  215. func dailyLimitToRate(limit int64) rate.Limit {
  216. return rate.Limit(limit) * rate.Every(24*time.Hour)
  217. }