visitor.go 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. package server
  2. import (
  3. "errors"
  4. "net/netip"
  5. "sync"
  6. "time"
  7. "golang.org/x/time/rate"
  8. "heckel.io/ntfy/util"
  9. )
  10. const (
  11. // visitorExpungeAfter defines how long a visitor is active before it is removed from memory. This number
  12. // has to be very high to prevent e-mail abuse, but it doesn't really affect the other limits anyway, since
  13. // they are replenished faster (typically).
  14. visitorExpungeAfter = 24 * time.Hour
  15. )
  16. var (
  17. errVisitorLimitReached = errors.New("limit reached")
  18. )
  19. // visitor represents an API user, and its associated rate.Limiter used for rate limiting
  20. type visitor struct {
  21. config *Config
  22. messageCache *messageCache
  23. ip netip.Addr
  24. requests *rate.Limiter
  25. emails *rate.Limiter
  26. subscriptions util.Limiter
  27. bandwidth util.Limiter
  28. firebase time.Time // Next allowed Firebase message
  29. seen time.Time
  30. mu sync.Mutex
  31. }
  32. type visitorStats struct {
  33. AttachmentFileSizeLimit int64 `json:"attachmentFileSizeLimit"`
  34. VisitorAttachmentBytesTotal int64 `json:"visitorAttachmentBytesTotal"`
  35. VisitorAttachmentBytesUsed int64 `json:"visitorAttachmentBytesUsed"`
  36. VisitorAttachmentBytesRemaining int64 `json:"visitorAttachmentBytesRemaining"`
  37. }
  38. func newVisitor(conf *Config, messageCache *messageCache, ip netip.Addr) *visitor {
  39. return &visitor{
  40. config: conf,
  41. messageCache: messageCache,
  42. ip: ip,
  43. requests: rate.NewLimiter(rate.Every(conf.VisitorRequestLimitReplenish), conf.VisitorRequestLimitBurst),
  44. emails: rate.NewLimiter(rate.Every(conf.VisitorEmailLimitReplenish), conf.VisitorEmailLimitBurst),
  45. subscriptions: util.NewFixedLimiter(int64(conf.VisitorSubscriptionLimit)),
  46. bandwidth: util.NewBytesLimiter(conf.VisitorAttachmentDailyBandwidthLimit, 24*time.Hour),
  47. firebase: time.Unix(0, 0),
  48. seen: time.Now(),
  49. }
  50. }
  51. func (v *visitor) RequestAllowed() error {
  52. if !v.requests.Allow() {
  53. return errVisitorLimitReached
  54. }
  55. return nil
  56. }
  57. func (v *visitor) FirebaseAllowed() error {
  58. v.mu.Lock()
  59. defer v.mu.Unlock()
  60. if time.Now().Before(v.firebase) {
  61. return errVisitorLimitReached
  62. }
  63. return nil
  64. }
  65. func (v *visitor) FirebaseTemporarilyDeny() {
  66. v.mu.Lock()
  67. defer v.mu.Unlock()
  68. v.firebase = time.Now().Add(v.config.FirebaseQuotaExceededPenaltyDuration)
  69. }
  70. func (v *visitor) EmailAllowed() error {
  71. if !v.emails.Allow() {
  72. return errVisitorLimitReached
  73. }
  74. return nil
  75. }
  76. func (v *visitor) SubscriptionAllowed() error {
  77. v.mu.Lock()
  78. defer v.mu.Unlock()
  79. if err := v.subscriptions.Allow(1); err != nil {
  80. return errVisitorLimitReached
  81. }
  82. return nil
  83. }
  84. func (v *visitor) RemoveSubscription() {
  85. v.mu.Lock()
  86. defer v.mu.Unlock()
  87. v.subscriptions.Allow(-1)
  88. }
  89. func (v *visitor) Keepalive() {
  90. v.mu.Lock()
  91. defer v.mu.Unlock()
  92. v.seen = time.Now()
  93. }
  94. func (v *visitor) BandwidthLimiter() util.Limiter {
  95. return v.bandwidth
  96. }
  97. func (v *visitor) Stale() bool {
  98. v.mu.Lock()
  99. defer v.mu.Unlock()
  100. return time.Since(v.seen) > visitorExpungeAfter
  101. }
  102. func (v *visitor) Stats() (*visitorStats, error) {
  103. attachmentsBytesUsed, err := v.messageCache.AttachmentBytesUsed(v.ip.String())
  104. if err != nil {
  105. return nil, err
  106. }
  107. attachmentsBytesRemaining := v.config.VisitorAttachmentTotalSizeLimit - attachmentsBytesUsed
  108. if attachmentsBytesRemaining < 0 {
  109. attachmentsBytesRemaining = 0
  110. }
  111. return &visitorStats{
  112. AttachmentFileSizeLimit: v.config.AttachmentFileSizeLimit,
  113. VisitorAttachmentBytesTotal: v.config.VisitorAttachmentTotalSizeLimit,
  114. VisitorAttachmentBytesUsed: attachmentsBytesUsed,
  115. VisitorAttachmentBytesRemaining: attachmentsBytesRemaining,
  116. }, nil
  117. }