visitor.go 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  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. requests *rate.Limiter
  27. emails *rate.Limiter
  28. subscriptions util.Limiter
  29. bandwidth util.Limiter
  30. firebase time.Time // Next allowed Firebase message
  31. seen time.Time
  32. mu sync.Mutex
  33. }
  34. type visitorStats struct {
  35. AttachmentFileSizeLimit int64 `json:"attachmentFileSizeLimit"`
  36. VisitorAttachmentBytesTotal int64 `json:"visitorAttachmentBytesTotal"`
  37. VisitorAttachmentBytesUsed int64 `json:"visitorAttachmentBytesUsed"`
  38. VisitorAttachmentBytesRemaining int64 `json:"visitorAttachmentBytesRemaining"`
  39. }
  40. func newVisitor(conf *Config, messageCache *messageCache, ip netip.Addr, user *auth.User) *visitor {
  41. return &visitor{
  42. config: conf,
  43. messageCache: messageCache,
  44. ip: ip,
  45. user: user,
  46. requests: rate.NewLimiter(rate.Every(conf.VisitorRequestLimitReplenish), conf.VisitorRequestLimitBurst),
  47. emails: rate.NewLimiter(rate.Every(conf.VisitorEmailLimitReplenish), conf.VisitorEmailLimitBurst),
  48. subscriptions: util.NewFixedLimiter(int64(conf.VisitorSubscriptionLimit)),
  49. bandwidth: util.NewBytesLimiter(conf.VisitorAttachmentDailyBandwidthLimit, 24*time.Hour),
  50. firebase: time.Unix(0, 0),
  51. seen: time.Now(),
  52. }
  53. }
  54. func (v *visitor) RequestAllowed() error {
  55. if !v.requests.Allow() {
  56. return errVisitorLimitReached
  57. }
  58. return nil
  59. }
  60. func (v *visitor) FirebaseAllowed() error {
  61. v.mu.Lock()
  62. defer v.mu.Unlock()
  63. if time.Now().Before(v.firebase) {
  64. return errVisitorLimitReached
  65. }
  66. return nil
  67. }
  68. func (v *visitor) FirebaseTemporarilyDeny() {
  69. v.mu.Lock()
  70. defer v.mu.Unlock()
  71. v.firebase = time.Now().Add(v.config.FirebaseQuotaExceededPenaltyDuration)
  72. }
  73. func (v *visitor) EmailAllowed() error {
  74. if !v.emails.Allow() {
  75. return errVisitorLimitReached
  76. }
  77. return nil
  78. }
  79. func (v *visitor) SubscriptionAllowed() error {
  80. v.mu.Lock()
  81. defer v.mu.Unlock()
  82. if err := v.subscriptions.Allow(1); err != nil {
  83. return errVisitorLimitReached
  84. }
  85. return nil
  86. }
  87. func (v *visitor) RemoveSubscription() {
  88. v.mu.Lock()
  89. defer v.mu.Unlock()
  90. v.subscriptions.Allow(-1)
  91. }
  92. func (v *visitor) Keepalive() {
  93. v.mu.Lock()
  94. defer v.mu.Unlock()
  95. v.seen = time.Now()
  96. }
  97. func (v *visitor) BandwidthLimiter() util.Limiter {
  98. return v.bandwidth
  99. }
  100. func (v *visitor) Stale() bool {
  101. v.mu.Lock()
  102. defer v.mu.Unlock()
  103. return time.Since(v.seen) > visitorExpungeAfter
  104. }
  105. func (v *visitor) Stats() (*visitorStats, error) {
  106. attachmentsBytesUsed, err := v.messageCache.AttachmentBytesUsed(v.ip.String())
  107. if err != nil {
  108. return nil, err
  109. }
  110. attachmentsBytesRemaining := v.config.VisitorAttachmentTotalSizeLimit - attachmentsBytesUsed
  111. if attachmentsBytesRemaining < 0 {
  112. attachmentsBytesRemaining = 0
  113. }
  114. return &visitorStats{
  115. AttachmentFileSizeLimit: v.config.AttachmentFileSizeLimit,
  116. VisitorAttachmentBytesTotal: v.config.VisitorAttachmentTotalSizeLimit,
  117. VisitorAttachmentBytesUsed: attachmentsBytesUsed,
  118. VisitorAttachmentBytesRemaining: attachmentsBytesRemaining,
  119. }, nil
  120. }