visitor.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411
  1. package server
  2. import (
  3. "errors"
  4. "fmt"
  5. "heckel.io/ntfy/log"
  6. "heckel.io/ntfy/user"
  7. "net/netip"
  8. "sync"
  9. "time"
  10. "golang.org/x/time/rate"
  11. "heckel.io/ntfy/util"
  12. )
  13. const (
  14. // oneDay is an approximation of a day as a time.Duration
  15. oneDay = 24 * time.Hour
  16. // visitorExpungeAfter defines how long a visitor is active before it is removed from memory. This number
  17. // has to be very high to prevent e-mail abuse, but it doesn't really affect the other limits anyway, since
  18. // they are replenished faster (typically).
  19. visitorExpungeAfter = oneDay
  20. // visitorDefaultReservationsLimit is the amount of topic names a user without a tier is allowed to reserve.
  21. // This number is zero, and changing it may have unintended consequences in the web app, or otherwise
  22. visitorDefaultReservationsLimit = int64(0)
  23. )
  24. // Constants used to convert a tier-user's MessageLimit (see user.Tier) into adequate request limiter
  25. // values (token bucket).
  26. //
  27. // Example: Assuming a user.Tier's MessageLimit is 10,000:
  28. // - the allowed burst is 500 (= 10,000 * 5%), which is < 1000 (the max)
  29. // - the replenish rate is 2 * 10,000 / 24 hours
  30. const (
  31. visitorMessageToRequestLimitBurstRate = 0.05
  32. visitorMessageToRequestLimitBurstMax = 1000
  33. visitorMessageToRequestLimitReplenishFactor = 2
  34. )
  35. // Constants used to convert a tier-user's EmailLimit (see user.Tier) into adequate email limiter
  36. // values (token bucket). Example: Assuming a user.Tier's EmailLimit is 200, the allowed burst is
  37. // 40 (= 200 * 20%), which is <150 (the max).
  38. const (
  39. visitorEmailLimitBurstRate = 0.2
  40. visitorEmailLimitBurstMax = 150
  41. )
  42. var (
  43. errVisitorLimitReached = errors.New("limit reached")
  44. )
  45. // visitor represents an API user, and its associated rate.Limiter used for rate limiting
  46. type visitor struct {
  47. config *Config
  48. messageCache *messageCache
  49. userManager *user.Manager // May be nil
  50. ip netip.Addr // Visitor IP address
  51. user *user.User // Only set if authenticated user, otherwise nil
  52. messages int64 // Number of messages sent, reset every day
  53. emails int64 // Number of emails sent, reset every day
  54. requestLimiter *rate.Limiter // Rate limiter for (almost) all requests (including messages)
  55. messagesLimiter *util.FixedLimiter // Rate limiter for messages, may be nil
  56. emailsLimiter *rate.Limiter // Rate limiter for emails
  57. subscriptionLimiter util.Limiter // Fixed limiter for active subscriptions (ongoing connections)
  58. bandwidthLimiter util.Limiter // Limiter for attachment bandwidth downloads
  59. accountLimiter *rate.Limiter // Rate limiter for account creation, may be nil
  60. firebase time.Time // Next allowed Firebase message
  61. seen time.Time // Last seen time of this visitor (needed for removal of stale visitors)
  62. mu sync.Mutex
  63. }
  64. type visitorInfo struct {
  65. Limits *visitorLimits
  66. Stats *visitorStats
  67. }
  68. type visitorLimits struct {
  69. Basis visitorLimitBasis
  70. RequestLimitBurst int
  71. RequestLimitReplenish rate.Limit
  72. MessageLimit int64
  73. MessageExpiryDuration time.Duration
  74. EmailLimit int64
  75. EmailLimitBurst int
  76. EmailLimitReplenish rate.Limit
  77. ReservationsLimit int64
  78. AttachmentTotalSizeLimit int64
  79. AttachmentFileSizeLimit int64
  80. AttachmentExpiryDuration time.Duration
  81. AttachmentBandwidthLimit int64
  82. }
  83. type visitorStats struct {
  84. Messages int64
  85. MessagesRemaining int64
  86. Emails int64
  87. EmailsRemaining int64
  88. Reservations int64
  89. ReservationsRemaining int64
  90. AttachmentTotalSize int64
  91. AttachmentTotalSizeRemaining int64
  92. }
  93. // visitorLimitBasis describes how the visitor limits were derived, either from a user's
  94. // IP address (default config), or from its tier
  95. type visitorLimitBasis string
  96. const (
  97. visitorLimitBasisIP = visitorLimitBasis("ip")
  98. visitorLimitBasisTier = visitorLimitBasis("tier")
  99. )
  100. func newVisitor(conf *Config, messageCache *messageCache, userManager *user.Manager, ip netip.Addr, user *user.User) *visitor {
  101. var messages, emails int64
  102. if user != nil {
  103. messages = user.Stats.Messages
  104. emails = user.Stats.Emails
  105. }
  106. v := &visitor{
  107. config: conf,
  108. messageCache: messageCache,
  109. userManager: userManager, // May be nil
  110. ip: ip,
  111. user: user,
  112. messages: messages,
  113. emails: emails,
  114. firebase: time.Unix(0, 0),
  115. seen: time.Now(),
  116. subscriptionLimiter: util.NewFixedLimiter(int64(conf.VisitorSubscriptionLimit)),
  117. requestLimiter: nil, // Set in resetLimiters
  118. messagesLimiter: nil, // Set in resetLimiters, may be nil
  119. emailsLimiter: nil, // Set in resetLimiters
  120. bandwidthLimiter: nil, // Set in resetLimiters
  121. accountLimiter: nil, // Set in resetLimiters, may be nil
  122. }
  123. v.resetLimiters()
  124. return v
  125. }
  126. func (v *visitor) String() string {
  127. v.mu.Lock()
  128. defer v.mu.Unlock()
  129. return v.stringNoLock()
  130. }
  131. func (v *visitor) stringNoLock() string {
  132. if v.user != nil && v.user.Billing.StripeCustomerID != "" {
  133. return fmt.Sprintf("%s/%s/%s", v.ip.String(), v.user.ID, v.user.Billing.StripeCustomerID)
  134. } else if v.user != nil {
  135. return fmt.Sprintf("%s/%s", v.ip.String(), v.user.ID)
  136. }
  137. return v.ip.String()
  138. }
  139. func (v *visitor) RequestAllowed() error {
  140. if !v.requestLimiter.Allow() {
  141. return errVisitorLimitReached
  142. }
  143. return nil
  144. }
  145. func (v *visitor) FirebaseAllowed() error {
  146. v.mu.Lock()
  147. defer v.mu.Unlock()
  148. if time.Now().Before(v.firebase) {
  149. return errVisitorLimitReached
  150. }
  151. return nil
  152. }
  153. func (v *visitor) FirebaseTemporarilyDeny() {
  154. v.mu.Lock()
  155. defer v.mu.Unlock()
  156. v.firebase = time.Now().Add(v.config.FirebaseQuotaExceededPenaltyDuration)
  157. }
  158. func (v *visitor) MessageAllowed() error {
  159. if v.messagesLimiter != nil && v.messagesLimiter.Allow(1) != nil {
  160. return errVisitorLimitReached
  161. }
  162. return nil
  163. }
  164. func (v *visitor) EmailAllowed() error {
  165. if !v.emailsLimiter.Allow() {
  166. return errVisitorLimitReached
  167. }
  168. return nil
  169. }
  170. func (v *visitor) SubscriptionAllowed() error {
  171. v.mu.Lock()
  172. defer v.mu.Unlock()
  173. if err := v.subscriptionLimiter.Allow(1); err != nil {
  174. return errVisitorLimitReached
  175. }
  176. return nil
  177. }
  178. func (v *visitor) AccountCreationAllowed() error {
  179. if v.accountLimiter == nil || (v.accountLimiter != nil && !v.accountLimiter.Allow()) {
  180. return errVisitorLimitReached
  181. }
  182. return nil
  183. }
  184. func (v *visitor) RemoveSubscription() {
  185. v.mu.Lock()
  186. defer v.mu.Unlock()
  187. v.subscriptionLimiter.Allow(-1)
  188. }
  189. func (v *visitor) Keepalive() {
  190. v.mu.Lock()
  191. defer v.mu.Unlock()
  192. v.seen = time.Now()
  193. }
  194. func (v *visitor) BandwidthLimiter() util.Limiter {
  195. return v.bandwidthLimiter
  196. }
  197. func (v *visitor) Stale() bool {
  198. v.mu.Lock()
  199. defer v.mu.Unlock()
  200. return time.Since(v.seen) > visitorExpungeAfter
  201. }
  202. func (v *visitor) IncrementMessages() {
  203. v.mu.Lock()
  204. defer v.mu.Unlock()
  205. v.messages++
  206. if v.user != nil {
  207. v.user.Stats.Messages = v.messages
  208. }
  209. }
  210. func (v *visitor) IncrementEmails() {
  211. v.mu.Lock()
  212. defer v.mu.Unlock()
  213. v.emails++
  214. if v.user != nil {
  215. v.user.Stats.Emails = v.emails
  216. }
  217. }
  218. func (v *visitor) ResetStats() {
  219. v.mu.Lock()
  220. defer v.mu.Unlock()
  221. v.messages = 0
  222. v.emails = 0
  223. if v.messagesLimiter != nil {
  224. v.messagesLimiter.Reset()
  225. }
  226. if v.user != nil {
  227. v.user.Stats.Messages = 0
  228. v.user.Stats.Emails = 0
  229. }
  230. }
  231. // SetUser sets the visitors user to the given value
  232. func (v *visitor) SetUser(u *user.User) {
  233. v.mu.Lock()
  234. defer v.mu.Unlock()
  235. shouldResetLimiters := v.user.TierID() != u.TierID() // TierID works with nil receiver
  236. v.user = u
  237. if shouldResetLimiters {
  238. v.resetLimiters()
  239. }
  240. }
  241. // MaybeUserID returns the user ID of the visitor (if any). If this is an anonymous visitor,
  242. // an empty string is returned.
  243. func (v *visitor) MaybeUserID() string {
  244. v.mu.Lock()
  245. defer v.mu.Unlock()
  246. if v.user != nil {
  247. return v.user.ID
  248. }
  249. return ""
  250. }
  251. func (v *visitor) resetLimiters() {
  252. log.Debug("%s Resetting limiters for visitor", v.stringNoLock())
  253. limits := v.limitsNoLock()
  254. v.requestLimiter = rate.NewLimiter(limits.RequestLimitReplenish, limits.RequestLimitBurst)
  255. v.messagesLimiter = util.NewFixedLimiterWithValue(limits.MessageLimit, v.messages)
  256. v.emailsLimiter = rate.NewLimiter(limits.EmailLimitReplenish, limits.EmailLimitBurst)
  257. v.bandwidthLimiter = util.NewBytesLimiter(int(limits.AttachmentBandwidthLimit), oneDay)
  258. if v.user == nil {
  259. v.accountLimiter = rate.NewLimiter(rate.Every(v.config.VisitorAccountCreationLimitReplenish), v.config.VisitorAccountCreationLimitBurst)
  260. } else {
  261. v.accountLimiter = nil // Users cannot create accounts when logged in
  262. }
  263. }
  264. func (v *visitor) Limits() *visitorLimits {
  265. v.mu.Lock()
  266. defer v.mu.Unlock()
  267. return v.limitsNoLock()
  268. }
  269. func (v *visitor) limitsNoLock() *visitorLimits {
  270. if v.user != nil && v.user.Tier != nil {
  271. return tierBasedVisitorLimits(v.config, v.user.Tier)
  272. }
  273. return configBasedVisitorLimits(v.config)
  274. }
  275. func tierBasedVisitorLimits(conf *Config, tier *user.Tier) *visitorLimits {
  276. return &visitorLimits{
  277. Basis: visitorLimitBasisTier,
  278. RequestLimitBurst: util.MinMax(int(float64(tier.MessageLimit)*visitorMessageToRequestLimitBurstRate), conf.VisitorRequestLimitBurst, visitorMessageToRequestLimitBurstMax),
  279. RequestLimitReplenish: dailyLimitToRate(tier.MessageLimit * visitorMessageToRequestLimitReplenishFactor),
  280. MessageLimit: tier.MessageLimit,
  281. MessageExpiryDuration: tier.MessageExpiryDuration,
  282. EmailLimit: tier.EmailLimit,
  283. EmailLimitBurst: util.MinMax(int(float64(tier.EmailLimit)*visitorEmailLimitBurstRate), conf.VisitorEmailLimitBurst, visitorEmailLimitBurstMax),
  284. EmailLimitReplenish: dailyLimitToRate(tier.EmailLimit),
  285. ReservationsLimit: tier.ReservationLimit,
  286. AttachmentTotalSizeLimit: tier.AttachmentTotalSizeLimit,
  287. AttachmentFileSizeLimit: tier.AttachmentFileSizeLimit,
  288. AttachmentExpiryDuration: tier.AttachmentExpiryDuration,
  289. AttachmentBandwidthLimit: tier.AttachmentBandwidthLimit,
  290. }
  291. }
  292. func configBasedVisitorLimits(conf *Config) *visitorLimits {
  293. messagesLimit := replenishDurationToDailyLimit(conf.VisitorRequestLimitReplenish) // Approximation!
  294. if conf.VisitorMessageDailyLimit > 0 {
  295. messagesLimit = int64(conf.VisitorMessageDailyLimit)
  296. }
  297. return &visitorLimits{
  298. Basis: visitorLimitBasisIP,
  299. RequestLimitBurst: conf.VisitorRequestLimitBurst,
  300. RequestLimitReplenish: rate.Every(conf.VisitorRequestLimitReplenish),
  301. MessageLimit: messagesLimit,
  302. MessageExpiryDuration: conf.CacheDuration,
  303. EmailLimit: replenishDurationToDailyLimit(conf.VisitorEmailLimitReplenish), // Approximation!
  304. EmailLimitBurst: conf.VisitorEmailLimitBurst,
  305. EmailLimitReplenish: rate.Every(conf.VisitorEmailLimitReplenish),
  306. ReservationsLimit: visitorDefaultReservationsLimit,
  307. AttachmentTotalSizeLimit: conf.VisitorAttachmentTotalSizeLimit,
  308. AttachmentFileSizeLimit: conf.AttachmentFileSizeLimit,
  309. AttachmentExpiryDuration: conf.AttachmentExpiryDuration,
  310. AttachmentBandwidthLimit: conf.VisitorAttachmentDailyBandwidthLimit,
  311. }
  312. }
  313. func (v *visitor) Info() (*visitorInfo, error) {
  314. v.mu.Lock()
  315. messages := v.messages
  316. emails := v.emails
  317. v.mu.Unlock()
  318. var attachmentsBytesUsed int64
  319. var err error
  320. if v.user != nil {
  321. attachmentsBytesUsed, err = v.messageCache.AttachmentBytesUsedByUser(v.user.ID)
  322. } else {
  323. attachmentsBytesUsed, err = v.messageCache.AttachmentBytesUsedBySender(v.ip.String())
  324. }
  325. if err != nil {
  326. return nil, err
  327. }
  328. var reservations int64
  329. if v.user != nil && v.userManager != nil {
  330. reservations, err = v.userManager.ReservationsCount(v.user.Name)
  331. if err != nil {
  332. return nil, err
  333. }
  334. }
  335. limits := v.Limits()
  336. stats := &visitorStats{
  337. Messages: messages,
  338. MessagesRemaining: zeroIfNegative(limits.MessageLimit - messages),
  339. Emails: emails,
  340. EmailsRemaining: zeroIfNegative(limits.EmailLimit - emails),
  341. Reservations: reservations,
  342. ReservationsRemaining: zeroIfNegative(limits.ReservationsLimit - reservations),
  343. AttachmentTotalSize: attachmentsBytesUsed,
  344. AttachmentTotalSizeRemaining: zeroIfNegative(limits.AttachmentTotalSizeLimit - attachmentsBytesUsed),
  345. }
  346. return &visitorInfo{
  347. Limits: limits,
  348. Stats: stats,
  349. }, nil
  350. }
  351. func zeroIfNegative(value int64) int64 {
  352. if value < 0 {
  353. return 0
  354. }
  355. return value
  356. }
  357. func replenishDurationToDailyLimit(duration time.Duration) int64 {
  358. return int64(oneDay / duration)
  359. }
  360. func dailyLimitToRate(limit int64) rate.Limit {
  361. return rate.Limit(limit) * rate.Every(oneDay)
  362. }
  363. func visitorID(ip netip.Addr, u *user.User) string {
  364. if u != nil && u.Tier != nil {
  365. return fmt.Sprintf("user:%s", u.ID)
  366. }
  367. return fmt.Sprintf("ip:%s", ip.String())
  368. }