visitor.go 6.9 KB

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