visitor.go 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  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 "tier"
  40. Messages int64
  41. MessagesLimit int64
  42. MessagesRemaining int64
  43. MessagesExpiryDuration int64
  44. Emails int64
  45. EmailsLimit int64
  46. EmailsRemaining int64
  47. Reservations int64
  48. ReservationsLimit int64
  49. ReservationsRemaining int64
  50. AttachmentTotalSize int64
  51. AttachmentTotalSizeLimit int64
  52. AttachmentTotalSizeRemaining int64
  53. AttachmentFileSizeLimit int64
  54. AttachmentExpiryDuration int64
  55. }
  56. func newVisitor(conf *Config, messageCache *messageCache, userManager *user.Manager, ip netip.Addr, user *user.User) *visitor {
  57. var requestLimiter, emailsLimiter, accountLimiter *rate.Limiter
  58. var messages, emails int64
  59. if user != nil {
  60. messages = user.Stats.Messages
  61. emails = user.Stats.Emails
  62. } else {
  63. accountLimiter = rate.NewLimiter(rate.Every(conf.VisitorAccountCreateLimitReplenish), conf.VisitorAccountCreateLimitBurst)
  64. }
  65. if user != nil && user.Tier != nil {
  66. requestLimiter = rate.NewLimiter(dailyLimitToRate(user.Tier.MessagesLimit), conf.VisitorRequestLimitBurst)
  67. emailsLimiter = rate.NewLimiter(dailyLimitToRate(user.Tier.EmailsLimit), conf.VisitorEmailLimitBurst)
  68. } else {
  69. requestLimiter = rate.NewLimiter(rate.Every(conf.VisitorRequestLimitReplenish), conf.VisitorRequestLimitBurst)
  70. emailsLimiter = rate.NewLimiter(rate.Every(conf.VisitorEmailLimitReplenish), conf.VisitorEmailLimitBurst)
  71. }
  72. return &visitor{
  73. config: conf,
  74. messageCache: messageCache,
  75. userManager: userManager, // May be nil!
  76. ip: ip,
  77. user: user,
  78. messages: messages,
  79. emails: emails,
  80. requestLimiter: requestLimiter,
  81. emailsLimiter: emailsLimiter,
  82. subscriptionLimiter: util.NewFixedLimiter(int64(conf.VisitorSubscriptionLimit)),
  83. bandwidthLimiter: util.NewBytesLimiter(conf.VisitorAttachmentDailyBandwidthLimit, 24*time.Hour),
  84. accountLimiter: accountLimiter, // May be nil
  85. firebase: time.Unix(0, 0),
  86. seen: time.Now(),
  87. }
  88. }
  89. func (v *visitor) RequestAllowed() error {
  90. if !v.requestLimiter.Allow() {
  91. return errVisitorLimitReached
  92. }
  93. return nil
  94. }
  95. func (v *visitor) FirebaseAllowed() error {
  96. v.mu.Lock()
  97. defer v.mu.Unlock()
  98. if time.Now().Before(v.firebase) {
  99. return errVisitorLimitReached
  100. }
  101. return nil
  102. }
  103. func (v *visitor) FirebaseTemporarilyDeny() {
  104. v.mu.Lock()
  105. defer v.mu.Unlock()
  106. v.firebase = time.Now().Add(v.config.FirebaseQuotaExceededPenaltyDuration)
  107. }
  108. func (v *visitor) EmailAllowed() error {
  109. if !v.emailsLimiter.Allow() {
  110. return errVisitorLimitReached
  111. }
  112. return nil
  113. }
  114. func (v *visitor) SubscriptionAllowed() error {
  115. v.mu.Lock()
  116. defer v.mu.Unlock()
  117. if err := v.subscriptionLimiter.Allow(1); err != nil {
  118. return errVisitorLimitReached
  119. }
  120. return nil
  121. }
  122. func (v *visitor) RemoveSubscription() {
  123. v.mu.Lock()
  124. defer v.mu.Unlock()
  125. v.subscriptionLimiter.Allow(-1)
  126. }
  127. func (v *visitor) Keepalive() {
  128. v.mu.Lock()
  129. defer v.mu.Unlock()
  130. v.seen = time.Now()
  131. }
  132. func (v *visitor) BandwidthLimiter() util.Limiter {
  133. return v.bandwidthLimiter
  134. }
  135. func (v *visitor) Stale() bool {
  136. v.mu.Lock()
  137. defer v.mu.Unlock()
  138. return time.Since(v.seen) > visitorExpungeAfter
  139. }
  140. func (v *visitor) IncrMessages() {
  141. v.mu.Lock()
  142. defer v.mu.Unlock()
  143. v.messages++
  144. if v.user != nil {
  145. v.user.Stats.Messages = v.messages
  146. }
  147. }
  148. func (v *visitor) IncrEmails() {
  149. v.mu.Lock()
  150. defer v.mu.Unlock()
  151. v.emails++
  152. if v.user != nil {
  153. v.user.Stats.Emails = v.emails
  154. }
  155. }
  156. func (v *visitor) Info() (*visitorInfo, error) {
  157. v.mu.Lock()
  158. messages := v.messages
  159. emails := v.emails
  160. v.mu.Unlock()
  161. info := &visitorInfo{}
  162. if v.user != nil && v.user.Role == user.RoleAdmin {
  163. info.Basis = "role"
  164. // All limits are zero!
  165. info.MessagesExpiryDuration = 24 * 3600 // FIXME this is awful. Should be from the Unlimited plan
  166. info.AttachmentExpiryDuration = 24 * 3600 // FIXME this is awful. Should be from the Unlimited plan
  167. } else if v.user != nil && v.user.Tier != nil {
  168. info.Basis = "tier"
  169. info.MessagesLimit = v.user.Tier.MessagesLimit
  170. info.MessagesExpiryDuration = v.user.Tier.MessagesExpiryDuration
  171. info.EmailsLimit = v.user.Tier.EmailsLimit
  172. info.ReservationsLimit = v.user.Tier.ReservationsLimit
  173. info.AttachmentTotalSizeLimit = v.user.Tier.AttachmentTotalSizeLimit
  174. info.AttachmentFileSizeLimit = v.user.Tier.AttachmentFileSizeLimit
  175. info.AttachmentExpiryDuration = v.user.Tier.AttachmentExpiryDuration
  176. } else {
  177. info.Basis = "ip"
  178. info.MessagesLimit = replenishDurationToDailyLimit(v.config.VisitorRequestLimitReplenish)
  179. info.MessagesExpiryDuration = int64(v.config.CacheDuration.Seconds())
  180. info.EmailsLimit = replenishDurationToDailyLimit(v.config.VisitorEmailLimitReplenish)
  181. info.ReservationsLimit = 0 // FIXME
  182. info.AttachmentTotalSizeLimit = v.config.VisitorAttachmentTotalSizeLimit
  183. info.AttachmentFileSizeLimit = v.config.AttachmentFileSizeLimit
  184. info.AttachmentExpiryDuration = int64(v.config.AttachmentExpiryDuration.Seconds())
  185. }
  186. var attachmentsBytesUsed int64 // FIXME Maybe move this to endpoint?
  187. var err error
  188. if v.user != nil {
  189. attachmentsBytesUsed, err = v.messageCache.AttachmentBytesUsedByUser(v.user.Name)
  190. } else {
  191. attachmentsBytesUsed, err = v.messageCache.AttachmentBytesUsedBySender(v.ip.String())
  192. }
  193. if err != nil {
  194. return nil, err
  195. }
  196. var reservations int64
  197. if v.user != nil && v.userManager != nil {
  198. reservations, err = v.userManager.ReservationsCount(v.user.Name) // FIXME dup call, move this to endpoint?
  199. if err != nil {
  200. return nil, err
  201. }
  202. }
  203. info.Messages = messages
  204. info.MessagesRemaining = zeroIfNegative(info.MessagesLimit - info.Messages)
  205. info.Emails = emails
  206. info.EmailsRemaining = zeroIfNegative(info.EmailsLimit - info.Emails)
  207. info.Reservations = reservations
  208. info.ReservationsRemaining = zeroIfNegative(info.ReservationsLimit - info.Reservations)
  209. info.AttachmentTotalSize = attachmentsBytesUsed
  210. info.AttachmentTotalSizeRemaining = zeroIfNegative(info.AttachmentTotalSizeLimit - info.AttachmentTotalSize)
  211. return info, nil
  212. }
  213. func zeroIfNegative(value int64) int64 {
  214. if value < 0 {
  215. return 0
  216. }
  217. return value
  218. }
  219. func replenishDurationToDailyLimit(duration time.Duration) int64 {
  220. return int64(24 * time.Hour / duration)
  221. }
  222. func dailyLimitToRate(limit int64) rate.Limit {
  223. return rate.Limit(limit) * rate.Every(24*time.Hour)
  224. }