visitor.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455
  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. requestLimiter *rate.Limiter // Rate limiter for (almost) all requests (including messages)
  53. messagesLimiter *util.FixedLimiter // Rate limiter for messages
  54. emailsLimiter *util.RateLimiter // Rate limiter for emails
  55. subscriptionLimiter *util.FixedLimiter // Fixed limiter for active subscriptions (ongoing connections)
  56. bandwidthLimiter *util.RateLimiter // Limiter for attachment bandwidth downloads
  57. accountLimiter *rate.Limiter // Rate limiter for account creation, may be nil
  58. firebase time.Time // Next allowed Firebase message
  59. seen time.Time // Last seen time of this visitor (needed for removal of stale visitors)
  60. mu sync.Mutex
  61. }
  62. type visitorInfo struct {
  63. Limits *visitorLimits
  64. Stats *visitorStats
  65. }
  66. type visitorLimits struct {
  67. Basis visitorLimitBasis
  68. RequestLimitBurst int
  69. RequestLimitReplenish rate.Limit
  70. MessageLimit int64
  71. MessageExpiryDuration time.Duration
  72. EmailLimit int64
  73. EmailLimitBurst int
  74. EmailLimitReplenish rate.Limit
  75. ReservationsLimit int64
  76. AttachmentTotalSizeLimit int64
  77. AttachmentFileSizeLimit int64
  78. AttachmentExpiryDuration time.Duration
  79. AttachmentBandwidthLimit int64
  80. }
  81. type visitorStats struct {
  82. Messages int64
  83. MessagesRemaining int64
  84. Emails int64
  85. EmailsRemaining int64
  86. Reservations int64
  87. ReservationsRemaining int64
  88. AttachmentTotalSize int64
  89. AttachmentTotalSizeRemaining int64
  90. }
  91. // visitorLimitBasis describes how the visitor limits were derived, either from a user's
  92. // IP address (default config), or from its tier
  93. type visitorLimitBasis string
  94. const (
  95. visitorLimitBasisIP = visitorLimitBasis("ip")
  96. visitorLimitBasisTier = visitorLimitBasis("tier")
  97. )
  98. func newVisitor(conf *Config, messageCache *messageCache, userManager *user.Manager, ip netip.Addr, user *user.User) *visitor {
  99. var messages, emails int64
  100. if user != nil {
  101. messages = user.Stats.Messages
  102. emails = user.Stats.Emails
  103. }
  104. v := &visitor{
  105. config: conf,
  106. messageCache: messageCache,
  107. userManager: userManager, // May be nil
  108. ip: ip,
  109. user: user,
  110. firebase: time.Unix(0, 0),
  111. seen: time.Now(),
  112. subscriptionLimiter: util.NewFixedLimiter(int64(conf.VisitorSubscriptionLimit)),
  113. requestLimiter: nil, // Set in resetLimiters
  114. messagesLimiter: nil, // Set in resetLimiters, may be nil
  115. emailsLimiter: nil, // Set in resetLimiters
  116. bandwidthLimiter: nil, // Set in resetLimiters
  117. accountLimiter: nil, // Set in resetLimiters, may be nil
  118. }
  119. v.resetLimitersNoLock(messages, emails, false)
  120. return v
  121. }
  122. func (v *visitor) String() string {
  123. v.mu.Lock()
  124. defer v.mu.Unlock()
  125. return v.stringNoLock()
  126. }
  127. func (v *visitor) stringNoLock() string {
  128. if v.user != nil && v.user.Billing.StripeCustomerID != "" {
  129. return fmt.Sprintf("%s/%s/%s", v.ip.String(), v.user.ID, v.user.Billing.StripeCustomerID)
  130. } else if v.user != nil {
  131. return fmt.Sprintf("%s/%s", v.ip.String(), v.user.ID)
  132. }
  133. return v.ip.String()
  134. }
  135. func (v *visitor) Context() map[string]any {
  136. v.mu.Lock()
  137. defer v.mu.Unlock()
  138. fields := map[string]any{
  139. "visitor_ip": v.ip.String(),
  140. }
  141. if v.user != nil {
  142. fields["user_id"] = v.user.ID
  143. fields["user_name"] = v.user.Name
  144. if v.user.Tier != nil {
  145. fields["tier_id"] = v.user.Tier.ID
  146. fields["tier_name"] = v.user.Tier.Name
  147. }
  148. if v.user.Billing.StripeCustomerID != "" {
  149. fields["stripe_customer_id"] = v.user.Billing.StripeCustomerID
  150. }
  151. if v.user.Billing.StripeSubscriptionID != "" {
  152. fields["stripe_subscription_id"] = v.user.Billing.StripeSubscriptionID
  153. }
  154. }
  155. return fields
  156. }
  157. func (v *visitor) RequestAllowed() error {
  158. v.mu.Lock() // limiters could be replaced!
  159. defer v.mu.Unlock()
  160. if !v.requestLimiter.Allow() {
  161. return errVisitorLimitReached
  162. }
  163. return nil
  164. }
  165. func (v *visitor) RequestLimiter() *rate.Limiter {
  166. v.mu.Lock() // limiters could be replaced!
  167. defer v.mu.Unlock()
  168. return v.requestLimiter
  169. }
  170. func (v *visitor) FirebaseAllowed() error {
  171. v.mu.Lock()
  172. defer v.mu.Unlock()
  173. if time.Now().Before(v.firebase) {
  174. return errVisitorLimitReached
  175. }
  176. return nil
  177. }
  178. func (v *visitor) FirebaseTemporarilyDeny() {
  179. v.mu.Lock()
  180. defer v.mu.Unlock()
  181. v.firebase = time.Now().Add(v.config.FirebaseQuotaExceededPenaltyDuration)
  182. }
  183. func (v *visitor) MessageAllowed() bool {
  184. v.mu.Lock() // limiters could be replaced!
  185. defer v.mu.Unlock()
  186. return v.messagesLimiter.Allow()
  187. }
  188. func (v *visitor) EmailAllowed() bool {
  189. v.mu.Lock() // limiters could be replaced!
  190. defer v.mu.Unlock()
  191. return v.emailsLimiter.Allow()
  192. }
  193. func (v *visitor) SubscriptionAllowed() bool {
  194. v.mu.Lock() // limiters could be replaced!
  195. defer v.mu.Unlock()
  196. return v.subscriptionLimiter.Allow()
  197. }
  198. func (v *visitor) AccountCreationAllowed() bool {
  199. v.mu.Lock() // limiters could be replaced!
  200. defer v.mu.Unlock()
  201. if v.accountLimiter == nil || (v.accountLimiter != nil && !v.accountLimiter.Allow()) {
  202. return false
  203. }
  204. return true
  205. }
  206. func (v *visitor) BandwidthAllowed(bytes int64) bool {
  207. v.mu.Lock() // limiters could be replaced!
  208. defer v.mu.Unlock()
  209. return v.bandwidthLimiter.AllowN(bytes)
  210. }
  211. func (v *visitor) RemoveSubscription() {
  212. v.mu.Lock()
  213. defer v.mu.Unlock()
  214. v.subscriptionLimiter.AllowN(-1)
  215. }
  216. func (v *visitor) Keepalive() {
  217. v.mu.Lock()
  218. defer v.mu.Unlock()
  219. v.seen = time.Now()
  220. }
  221. func (v *visitor) BandwidthLimiter() util.Limiter {
  222. v.mu.Lock() // limiters could be replaced!
  223. defer v.mu.Unlock()
  224. return v.bandwidthLimiter
  225. }
  226. func (v *visitor) Stale() bool {
  227. v.mu.Lock()
  228. defer v.mu.Unlock()
  229. return time.Since(v.seen) > visitorExpungeAfter
  230. }
  231. func (v *visitor) Stats() *user.Stats {
  232. v.mu.Lock() // limiters could be replaced!
  233. defer v.mu.Unlock()
  234. return &user.Stats{
  235. Messages: v.messagesLimiter.Value(),
  236. Emails: v.emailsLimiter.Value(),
  237. }
  238. }
  239. func (v *visitor) ResetStats() {
  240. v.mu.Lock() // limiters could be replaced!
  241. defer v.mu.Unlock()
  242. v.emailsLimiter.Reset()
  243. v.messagesLimiter.Reset()
  244. }
  245. // User returns the visitor user, or nil if there is none
  246. func (v *visitor) User() *user.User {
  247. v.mu.Lock()
  248. defer v.mu.Unlock()
  249. return v.user // May be nil
  250. }
  251. // IP returns the visitor IP address
  252. func (v *visitor) IP() netip.Addr {
  253. v.mu.Lock()
  254. defer v.mu.Unlock()
  255. return v.ip
  256. }
  257. // Authenticated returns true if a user successfully authenticated
  258. func (v *visitor) Authenticated() bool {
  259. v.mu.Lock()
  260. defer v.mu.Unlock()
  261. return v.user != nil
  262. }
  263. // SetUser sets the visitors user to the given value
  264. func (v *visitor) SetUser(u *user.User) {
  265. v.mu.Lock()
  266. defer v.mu.Unlock()
  267. shouldResetLimiters := v.user.TierID() != u.TierID() // TierID works with nil receiver
  268. v.user = u
  269. if shouldResetLimiters {
  270. v.resetLimitersNoLock(0, 0, true)
  271. }
  272. }
  273. // MaybeUserID returns the user ID of the visitor (if any). If this is an anonymous visitor,
  274. // an empty string is returned.
  275. func (v *visitor) MaybeUserID() string {
  276. v.mu.Lock()
  277. defer v.mu.Unlock()
  278. if v.user != nil {
  279. return v.user.ID
  280. }
  281. return ""
  282. }
  283. func (v *visitor) resetLimitersNoLock(messages, emails int64, enqueueUpdate bool) {
  284. log.Context(v).Debug("%s Resetting limiters for visitor", v.stringNoLock())
  285. limits := v.limitsNoLock()
  286. v.requestLimiter = rate.NewLimiter(limits.RequestLimitReplenish, limits.RequestLimitBurst)
  287. v.messagesLimiter = util.NewFixedLimiterWithValue(limits.MessageLimit, messages)
  288. v.emailsLimiter = util.NewRateLimiterWithValue(limits.EmailLimitReplenish, limits.EmailLimitBurst, emails)
  289. v.bandwidthLimiter = util.NewBytesLimiter(int(limits.AttachmentBandwidthLimit), oneDay)
  290. if v.user == nil {
  291. v.accountLimiter = rate.NewLimiter(rate.Every(v.config.VisitorAccountCreationLimitReplenish), v.config.VisitorAccountCreationLimitBurst)
  292. } else {
  293. v.accountLimiter = nil // Users cannot create accounts when logged in
  294. }
  295. if enqueueUpdate && v.user != nil {
  296. go v.userManager.EnqueueStats(v.user.ID, &user.Stats{
  297. Messages: messages,
  298. Emails: emails,
  299. })
  300. }
  301. }
  302. func (v *visitor) Limits() *visitorLimits {
  303. v.mu.Lock()
  304. defer v.mu.Unlock()
  305. return v.limitsNoLock()
  306. }
  307. func (v *visitor) limitsNoLock() *visitorLimits {
  308. if v.user != nil && v.user.Tier != nil {
  309. return tierBasedVisitorLimits(v.config, v.user.Tier)
  310. }
  311. return configBasedVisitorLimits(v.config)
  312. }
  313. func tierBasedVisitorLimits(conf *Config, tier *user.Tier) *visitorLimits {
  314. return &visitorLimits{
  315. Basis: visitorLimitBasisTier,
  316. RequestLimitBurst: util.MinMax(int(float64(tier.MessageLimit)*visitorMessageToRequestLimitBurstRate), conf.VisitorRequestLimitBurst, visitorMessageToRequestLimitBurstMax),
  317. RequestLimitReplenish: dailyLimitToRate(tier.MessageLimit * visitorMessageToRequestLimitReplenishFactor),
  318. MessageLimit: tier.MessageLimit,
  319. MessageExpiryDuration: tier.MessageExpiryDuration,
  320. EmailLimit: tier.EmailLimit,
  321. EmailLimitBurst: util.MinMax(int(float64(tier.EmailLimit)*visitorEmailLimitBurstRate), conf.VisitorEmailLimitBurst, visitorEmailLimitBurstMax),
  322. EmailLimitReplenish: dailyLimitToRate(tier.EmailLimit),
  323. ReservationsLimit: tier.ReservationLimit,
  324. AttachmentTotalSizeLimit: tier.AttachmentTotalSizeLimit,
  325. AttachmentFileSizeLimit: tier.AttachmentFileSizeLimit,
  326. AttachmentExpiryDuration: tier.AttachmentExpiryDuration,
  327. AttachmentBandwidthLimit: tier.AttachmentBandwidthLimit,
  328. }
  329. }
  330. func configBasedVisitorLimits(conf *Config) *visitorLimits {
  331. messagesLimit := replenishDurationToDailyLimit(conf.VisitorRequestLimitReplenish) // Approximation!
  332. if conf.VisitorMessageDailyLimit > 0 {
  333. messagesLimit = int64(conf.VisitorMessageDailyLimit)
  334. }
  335. return &visitorLimits{
  336. Basis: visitorLimitBasisIP,
  337. RequestLimitBurst: conf.VisitorRequestLimitBurst,
  338. RequestLimitReplenish: rate.Every(conf.VisitorRequestLimitReplenish),
  339. MessageLimit: messagesLimit,
  340. MessageExpiryDuration: conf.CacheDuration,
  341. EmailLimit: replenishDurationToDailyLimit(conf.VisitorEmailLimitReplenish), // Approximation!
  342. EmailLimitBurst: conf.VisitorEmailLimitBurst,
  343. EmailLimitReplenish: rate.Every(conf.VisitorEmailLimitReplenish),
  344. ReservationsLimit: visitorDefaultReservationsLimit,
  345. AttachmentTotalSizeLimit: conf.VisitorAttachmentTotalSizeLimit,
  346. AttachmentFileSizeLimit: conf.AttachmentFileSizeLimit,
  347. AttachmentExpiryDuration: conf.AttachmentExpiryDuration,
  348. AttachmentBandwidthLimit: conf.VisitorAttachmentDailyBandwidthLimit,
  349. }
  350. }
  351. func (v *visitor) Info() (*visitorInfo, error) {
  352. v.mu.Lock()
  353. messages := v.messagesLimiter.Value()
  354. emails := v.emailsLimiter.Value()
  355. v.mu.Unlock()
  356. var attachmentsBytesUsed int64
  357. var err error
  358. u := v.User()
  359. if u != nil {
  360. attachmentsBytesUsed, err = v.messageCache.AttachmentBytesUsedByUser(u.ID)
  361. } else {
  362. attachmentsBytesUsed, err = v.messageCache.AttachmentBytesUsedBySender(v.IP().String())
  363. }
  364. if err != nil {
  365. return nil, err
  366. }
  367. var reservations int64
  368. if v.userManager != nil && u != nil {
  369. reservations, err = v.userManager.ReservationsCount(u.Name)
  370. if err != nil {
  371. return nil, err
  372. }
  373. }
  374. limits := v.Limits()
  375. stats := &visitorStats{
  376. Messages: messages,
  377. MessagesRemaining: zeroIfNegative(limits.MessageLimit - messages),
  378. Emails: emails,
  379. EmailsRemaining: zeroIfNegative(limits.EmailLimit - emails),
  380. Reservations: reservations,
  381. ReservationsRemaining: zeroIfNegative(limits.ReservationsLimit - reservations),
  382. AttachmentTotalSize: attachmentsBytesUsed,
  383. AttachmentTotalSizeRemaining: zeroIfNegative(limits.AttachmentTotalSizeLimit - attachmentsBytesUsed),
  384. }
  385. return &visitorInfo{
  386. Limits: limits,
  387. Stats: stats,
  388. }, nil
  389. }
  390. func zeroIfNegative(value int64) int64 {
  391. if value < 0 {
  392. return 0
  393. }
  394. return value
  395. }
  396. func replenishDurationToDailyLimit(duration time.Duration) int64 {
  397. return int64(oneDay / duration)
  398. }
  399. func dailyLimitToRate(limit int64) rate.Limit {
  400. return rate.Limit(limit) * rate.Every(oneDay)
  401. }
  402. func visitorID(ip netip.Addr, u *user.User) string {
  403. if u != nil && u.Tier != nil {
  404. return fmt.Sprintf("user:%s", u.ID)
  405. }
  406. return fmt.Sprintf("ip:%s", ip.String())
  407. }