visitor.go 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552
  1. package server
  2. import (
  3. "fmt"
  4. "heckel.io/ntfy/log"
  5. "heckel.io/ntfy/user"
  6. "net/netip"
  7. "sync"
  8. "time"
  9. "golang.org/x/time/rate"
  10. "heckel.io/ntfy/util"
  11. )
  12. const (
  13. // oneDay is an approximation of a day as a time.Duration
  14. oneDay = 24 * time.Hour
  15. // visitorExpungeAfter defines how long a visitor is active before it is removed from memory. This number
  16. // has to be very high to prevent e-mail abuse, but it doesn't really affect the other limits anyway, since
  17. // they are replenished faster (typically).
  18. visitorExpungeAfter = oneDay
  19. // visitorDefaultReservationsLimit is the amount of topic names a user without a tier is allowed to reserve.
  20. // This number is zero, and changing it may have unintended consequences in the web app, or otherwise
  21. visitorDefaultReservationsLimit = int64(0)
  22. )
  23. // Constants used to convert a tier-user's MessageLimit (see user.Tier) into adequate request limiter
  24. // values (token bucket). This is only used to increase the values in server.yml, never decrease them.
  25. //
  26. // Example: Assuming a user.Tier's MessageLimit is 10,000:
  27. // - the allowed burst is 500 (= 10,000 * 5%), which is < 1000 (the max)
  28. // - the replenish rate is 2 * 10,000 / 24 hours
  29. const (
  30. visitorMessageToRequestLimitBurstRate = 0.05
  31. visitorMessageToRequestLimitBurstMax = 1000
  32. visitorMessageToRequestLimitReplenishFactor = 2
  33. )
  34. // Constants used to convert a tier-user's EmailLimit (see user.Tier) into adequate email limiter
  35. // values (token bucket). Example: Assuming a user.Tier's EmailLimit is 200, the allowed burst is
  36. // 40 (= 200 * 20%), which is <150 (the max).
  37. const (
  38. visitorEmailLimitBurstRate = 0.2
  39. visitorEmailLimitBurstMax = 150
  40. )
  41. // visitor represents an API user, and its associated rate.Limiter used for rate limiting
  42. type visitor struct {
  43. config *Config
  44. messageCache *messageCache
  45. userManager *user.Manager // May be nil
  46. ip netip.Addr // Visitor IP address
  47. user *user.User // Only set if authenticated user, otherwise nil
  48. requestLimiter *rate.Limiter // Rate limiter for (almost) all requests (including messages)
  49. messagesLimiter *util.FixedLimiter // Rate limiter for messages
  50. emailsLimiter *util.RateLimiter // Rate limiter for emails
  51. smsLimiter *util.FixedLimiter // Rate limiter for SMS
  52. callsLimiter *util.FixedLimiter // Rate limiter for calls
  53. subscriptionLimiter *util.FixedLimiter // Fixed limiter for active subscriptions (ongoing connections)
  54. bandwidthLimiter *util.RateLimiter // Limiter for attachment bandwidth downloads
  55. accountLimiter *rate.Limiter // Rate limiter for account creation, may be nil
  56. authLimiter *rate.Limiter // Limiter for incorrect login attempts, may be nil
  57. firebase time.Time // Next allowed Firebase message
  58. seen time.Time // Last seen time of this visitor (needed for removal of stale visitors)
  59. mu sync.RWMutex
  60. }
  61. type visitorInfo struct {
  62. Limits *visitorLimits
  63. Stats *visitorStats
  64. }
  65. type visitorLimits struct {
  66. Basis visitorLimitBasis
  67. RequestLimitBurst int
  68. RequestLimitReplenish rate.Limit
  69. MessageLimit int64
  70. MessageExpiryDuration time.Duration
  71. EmailLimit int64
  72. EmailLimitBurst int
  73. EmailLimitReplenish rate.Limit
  74. SMSLimit int64
  75. CallLimit int64
  76. ReservationsLimit int64
  77. AttachmentTotalSizeLimit int64
  78. AttachmentFileSizeLimit int64
  79. AttachmentExpiryDuration time.Duration
  80. AttachmentBandwidthLimit int64
  81. }
  82. type visitorStats struct {
  83. Messages int64
  84. MessagesRemaining int64
  85. Emails int64
  86. EmailsRemaining int64
  87. SMS int64
  88. SMSRemaining int64
  89. Calls int64
  90. CallsRemaining int64
  91. Reservations int64
  92. ReservationsRemaining int64
  93. AttachmentTotalSize int64
  94. AttachmentTotalSizeRemaining int64
  95. }
  96. // visitorLimitBasis describes how the visitor limits were derived, either from a user's
  97. // IP address (default config), or from its tier
  98. type visitorLimitBasis string
  99. const (
  100. visitorLimitBasisIP = visitorLimitBasis("ip")
  101. visitorLimitBasisTier = visitorLimitBasis("tier")
  102. )
  103. func newVisitor(conf *Config, messageCache *messageCache, userManager *user.Manager, ip netip.Addr, user *user.User) *visitor {
  104. var messages, emails, sms, calls int64
  105. if user != nil {
  106. messages = user.Stats.Messages
  107. emails = user.Stats.Emails
  108. sms = user.Stats.SMS
  109. calls = user.Stats.Calls
  110. }
  111. v := &visitor{
  112. config: conf,
  113. messageCache: messageCache,
  114. userManager: userManager, // May be nil
  115. ip: ip,
  116. user: user,
  117. firebase: time.Unix(0, 0),
  118. seen: time.Now(),
  119. subscriptionLimiter: util.NewFixedLimiter(int64(conf.VisitorSubscriptionLimit)),
  120. requestLimiter: nil, // Set in resetLimiters
  121. messagesLimiter: nil, // Set in resetLimiters, may be nil
  122. emailsLimiter: nil, // Set in resetLimiters
  123. smsLimiter: nil, // Set in resetLimiters, may be nil
  124. callsLimiter: nil, // Set in resetLimiters, may be nil
  125. bandwidthLimiter: nil, // Set in resetLimiters
  126. accountLimiter: nil, // Set in resetLimiters, may be nil
  127. authLimiter: nil, // Set in resetLimiters, may be nil
  128. }
  129. v.resetLimitersNoLock(messages, emails, sms, calls, false)
  130. return v
  131. }
  132. func (v *visitor) Context() log.Context {
  133. v.mu.RLock()
  134. defer v.mu.RUnlock()
  135. return v.contextNoLock()
  136. }
  137. func (v *visitor) contextNoLock() log.Context {
  138. info := v.infoLightNoLock()
  139. fields := log.Context{
  140. "visitor_id": visitorID(v.ip, v.user),
  141. "visitor_ip": v.ip.String(),
  142. "visitor_seen": util.FormatTime(v.seen),
  143. "visitor_messages": info.Stats.Messages,
  144. "visitor_messages_limit": info.Limits.MessageLimit,
  145. "visitor_messages_remaining": info.Stats.MessagesRemaining,
  146. "visitor_request_limiter_limit": v.requestLimiter.Limit(),
  147. "visitor_request_limiter_tokens": v.requestLimiter.Tokens(),
  148. }
  149. if v.config.SMTPSenderFrom != "" {
  150. fields["visitor_emails"] = info.Stats.Emails
  151. fields["visitor_emails_limit"] = info.Limits.EmailLimit
  152. fields["visitor_emails_remaining"] = info.Stats.EmailsRemaining
  153. }
  154. if v.config.TwilioAccount != "" {
  155. fields["visitor_sms"] = info.Stats.SMS
  156. fields["visitor_sms_limit"] = info.Limits.SMSLimit
  157. fields["visitor_sms_remaining"] = info.Stats.SMSRemaining
  158. fields["visitor_calls"] = info.Stats.Calls
  159. fields["visitor_calls_limit"] = info.Limits.CallLimit
  160. fields["visitor_calls_remaining"] = info.Stats.CallsRemaining
  161. }
  162. if v.authLimiter != nil {
  163. fields["visitor_auth_limiter_limit"] = v.authLimiter.Limit()
  164. fields["visitor_auth_limiter_tokens"] = v.authLimiter.Tokens()
  165. }
  166. if v.user != nil {
  167. fields["user_id"] = v.user.ID
  168. fields["user_name"] = v.user.Name
  169. if v.user.Tier != nil {
  170. for field, value := range v.user.Tier.Context() {
  171. fields[field] = value
  172. }
  173. }
  174. if v.user.Billing.StripeCustomerID != "" {
  175. fields["stripe_customer_id"] = v.user.Billing.StripeCustomerID
  176. }
  177. if v.user.Billing.StripeSubscriptionID != "" {
  178. fields["stripe_subscription_id"] = v.user.Billing.StripeSubscriptionID
  179. }
  180. }
  181. return fields
  182. }
  183. func visitorExtendedInfoContext(info *visitorInfo) log.Context {
  184. return log.Context{
  185. "visitor_reservations": info.Stats.Reservations,
  186. "visitor_reservations_limit": info.Limits.ReservationsLimit,
  187. "visitor_reservations_remaining": info.Stats.ReservationsRemaining,
  188. "visitor_attachment_total_size": info.Stats.AttachmentTotalSize,
  189. "visitor_attachment_total_size_limit": info.Limits.AttachmentTotalSizeLimit,
  190. "visitor_attachment_total_size_remaining": info.Stats.AttachmentTotalSizeRemaining,
  191. }
  192. }
  193. func (v *visitor) RequestAllowed() bool {
  194. v.mu.RLock() // limiters could be replaced!
  195. defer v.mu.RUnlock()
  196. return v.requestLimiter.Allow()
  197. }
  198. func (v *visitor) FirebaseAllowed() bool {
  199. v.mu.RLock()
  200. defer v.mu.RUnlock()
  201. return !time.Now().Before(v.firebase)
  202. }
  203. func (v *visitor) FirebaseTemporarilyDeny() {
  204. v.mu.Lock()
  205. defer v.mu.Unlock()
  206. v.firebase = time.Now().Add(v.config.FirebaseQuotaExceededPenaltyDuration)
  207. }
  208. func (v *visitor) MessageAllowed() bool {
  209. v.mu.RLock() // limiters could be replaced!
  210. defer v.mu.RUnlock()
  211. return v.messagesLimiter.Allow()
  212. }
  213. func (v *visitor) EmailAllowed() bool {
  214. v.mu.RLock() // limiters could be replaced!
  215. defer v.mu.RUnlock()
  216. return v.emailsLimiter.Allow()
  217. }
  218. func (v *visitor) SMSAllowed() bool {
  219. v.mu.RLock() // limiters could be replaced!
  220. defer v.mu.RUnlock()
  221. return v.smsLimiter.Allow()
  222. }
  223. func (v *visitor) CallAllowed() bool {
  224. v.mu.RLock() // limiters could be replaced!
  225. defer v.mu.RUnlock()
  226. return v.callsLimiter.Allow()
  227. }
  228. func (v *visitor) SubscriptionAllowed() bool {
  229. v.mu.RLock() // limiters could be replaced!
  230. defer v.mu.RUnlock()
  231. return v.subscriptionLimiter.Allow()
  232. }
  233. // AuthAllowed returns true if an auth request can be attempted (> 1 token available)
  234. func (v *visitor) AuthAllowed() bool {
  235. v.mu.RLock() // limiters could be replaced!
  236. defer v.mu.RUnlock()
  237. if v.authLimiter == nil {
  238. return true
  239. }
  240. return v.authLimiter.Tokens() > 1
  241. }
  242. // AuthFailed records an auth failure
  243. func (v *visitor) AuthFailed() {
  244. v.mu.RLock() // limiters could be replaced!
  245. defer v.mu.RUnlock()
  246. if v.authLimiter != nil {
  247. v.authLimiter.Allow()
  248. }
  249. }
  250. // AccountCreationAllowed returns true if a new account can be created
  251. func (v *visitor) AccountCreationAllowed() bool {
  252. v.mu.RLock() // limiters could be replaced!
  253. defer v.mu.RUnlock()
  254. if v.accountLimiter == nil || (v.accountLimiter != nil && v.accountLimiter.Tokens() < 1) {
  255. return false
  256. }
  257. return true
  258. }
  259. // AccountCreated decreases the account limiter. This is to be called after an account was created.
  260. func (v *visitor) AccountCreated() {
  261. v.mu.RLock() // limiters could be replaced!
  262. defer v.mu.RUnlock()
  263. if v.accountLimiter != nil {
  264. v.accountLimiter.Allow()
  265. }
  266. }
  267. func (v *visitor) BandwidthAllowed(bytes int64) bool {
  268. v.mu.RLock() // limiters could be replaced!
  269. defer v.mu.RUnlock()
  270. return v.bandwidthLimiter.AllowN(bytes)
  271. }
  272. func (v *visitor) RemoveSubscription() {
  273. v.mu.RLock()
  274. defer v.mu.RUnlock()
  275. v.subscriptionLimiter.AllowN(-1)
  276. }
  277. func (v *visitor) Keepalive() {
  278. v.mu.Lock()
  279. defer v.mu.Unlock()
  280. v.seen = time.Now()
  281. }
  282. func (v *visitor) BandwidthLimiter() util.Limiter {
  283. v.mu.RLock() // limiters could be replaced!
  284. defer v.mu.RUnlock()
  285. return v.bandwidthLimiter
  286. }
  287. func (v *visitor) Stale() bool {
  288. v.mu.RLock()
  289. defer v.mu.RUnlock()
  290. return time.Since(v.seen) > visitorExpungeAfter
  291. }
  292. func (v *visitor) Stats() *user.Stats {
  293. v.mu.RLock() // limiters could be replaced!
  294. defer v.mu.RUnlock()
  295. return &user.Stats{
  296. Messages: v.messagesLimiter.Value(),
  297. Emails: v.emailsLimiter.Value(),
  298. SMS: v.smsLimiter.Value(),
  299. Calls: v.callsLimiter.Value(),
  300. }
  301. }
  302. func (v *visitor) ResetStats() {
  303. v.mu.RLock() // limiters could be replaced!
  304. defer v.mu.RUnlock()
  305. v.emailsLimiter.Reset()
  306. v.messagesLimiter.Reset()
  307. v.smsLimiter.Reset()
  308. v.callsLimiter.Reset()
  309. }
  310. // User returns the visitor user, or nil if there is none
  311. func (v *visitor) User() *user.User {
  312. v.mu.RLock()
  313. defer v.mu.RUnlock()
  314. return v.user // May be nil
  315. }
  316. // IP returns the visitor IP address
  317. func (v *visitor) IP() netip.Addr {
  318. v.mu.RLock()
  319. defer v.mu.RUnlock()
  320. return v.ip
  321. }
  322. // Authenticated returns true if a user successfully authenticated
  323. func (v *visitor) Authenticated() bool {
  324. v.mu.RLock()
  325. defer v.mu.RUnlock()
  326. return v.user != nil
  327. }
  328. // SetUser sets the visitors user to the given value
  329. func (v *visitor) SetUser(u *user.User) {
  330. v.mu.Lock()
  331. defer v.mu.Unlock()
  332. shouldResetLimiters := v.user.TierID() != u.TierID() // TierID works with nil receiver
  333. v.user = u // u may be nil!
  334. if shouldResetLimiters {
  335. var messages, emails, sms, calls int64
  336. if u != nil {
  337. messages, emails, sms, calls = u.Stats.Messages, u.Stats.Emails, u.Stats.SMS, u.Stats.Calls
  338. }
  339. v.resetLimitersNoLock(messages, emails, sms, calls, true)
  340. }
  341. }
  342. // MaybeUserID returns the user ID of the visitor (if any). If this is an anonymous visitor,
  343. // an empty string is returned.
  344. func (v *visitor) MaybeUserID() string {
  345. v.mu.RLock()
  346. defer v.mu.RUnlock()
  347. if v.user != nil {
  348. return v.user.ID
  349. }
  350. return ""
  351. }
  352. func (v *visitor) resetLimitersNoLock(messages, emails, sms, calls int64, enqueueUpdate bool) {
  353. limits := v.limitsNoLock()
  354. v.requestLimiter = rate.NewLimiter(limits.RequestLimitReplenish, limits.RequestLimitBurst)
  355. v.messagesLimiter = util.NewFixedLimiterWithValue(limits.MessageLimit, messages)
  356. v.emailsLimiter = util.NewRateLimiterWithValue(limits.EmailLimitReplenish, limits.EmailLimitBurst, emails)
  357. v.smsLimiter = util.NewFixedLimiterWithValue(limits.SMSLimit, sms)
  358. v.callsLimiter = util.NewFixedLimiterWithValue(limits.CallLimit, calls)
  359. v.bandwidthLimiter = util.NewBytesLimiter(int(limits.AttachmentBandwidthLimit), oneDay)
  360. if v.user == nil {
  361. v.accountLimiter = rate.NewLimiter(rate.Every(v.config.VisitorAccountCreationLimitReplenish), v.config.VisitorAccountCreationLimitBurst)
  362. v.authLimiter = rate.NewLimiter(rate.Every(v.config.VisitorAuthFailureLimitReplenish), v.config.VisitorAuthFailureLimitBurst)
  363. } else {
  364. v.accountLimiter = nil // Users cannot create accounts when logged in
  365. v.authLimiter = nil // Users are already logged in, no need to limit requests
  366. }
  367. if enqueueUpdate && v.user != nil {
  368. go v.userManager.EnqueueUserStats(v.user.ID, &user.Stats{
  369. Messages: messages,
  370. Emails: emails,
  371. SMS: sms,
  372. Calls: calls,
  373. })
  374. }
  375. log.Fields(v.contextNoLock()).Debug("Rate limiters reset for visitor") // Must be after function, because contextNoLock() describes rate limiters
  376. }
  377. func (v *visitor) Limits() *visitorLimits {
  378. v.mu.RLock()
  379. defer v.mu.RUnlock()
  380. return v.limitsNoLock()
  381. }
  382. func (v *visitor) limitsNoLock() *visitorLimits {
  383. if v.user != nil && v.user.Tier != nil {
  384. return tierBasedVisitorLimits(v.config, v.user.Tier)
  385. }
  386. return configBasedVisitorLimits(v.config)
  387. }
  388. func tierBasedVisitorLimits(conf *Config, tier *user.Tier) *visitorLimits {
  389. return &visitorLimits{
  390. Basis: visitorLimitBasisTier,
  391. RequestLimitBurst: util.MinMax(int(float64(tier.MessageLimit)*visitorMessageToRequestLimitBurstRate), conf.VisitorRequestLimitBurst, visitorMessageToRequestLimitBurstMax),
  392. RequestLimitReplenish: util.Max(rate.Every(conf.VisitorRequestLimitReplenish), dailyLimitToRate(tier.MessageLimit*visitorMessageToRequestLimitReplenishFactor)),
  393. MessageLimit: tier.MessageLimit,
  394. MessageExpiryDuration: tier.MessageExpiryDuration,
  395. EmailLimit: tier.EmailLimit,
  396. EmailLimitBurst: util.MinMax(int(float64(tier.EmailLimit)*visitorEmailLimitBurstRate), conf.VisitorEmailLimitBurst, visitorEmailLimitBurstMax),
  397. EmailLimitReplenish: dailyLimitToRate(tier.EmailLimit),
  398. SMSLimit: tier.SMSLimit,
  399. CallLimit: tier.CallLimit,
  400. ReservationsLimit: tier.ReservationLimit,
  401. AttachmentTotalSizeLimit: tier.AttachmentTotalSizeLimit,
  402. AttachmentFileSizeLimit: tier.AttachmentFileSizeLimit,
  403. AttachmentExpiryDuration: tier.AttachmentExpiryDuration,
  404. AttachmentBandwidthLimit: tier.AttachmentBandwidthLimit,
  405. }
  406. }
  407. func configBasedVisitorLimits(conf *Config) *visitorLimits {
  408. messagesLimit := replenishDurationToDailyLimit(conf.VisitorRequestLimitReplenish) // Approximation!
  409. if conf.VisitorMessageDailyLimit > 0 {
  410. messagesLimit = int64(conf.VisitorMessageDailyLimit)
  411. }
  412. return &visitorLimits{
  413. Basis: visitorLimitBasisIP,
  414. RequestLimitBurst: conf.VisitorRequestLimitBurst,
  415. RequestLimitReplenish: rate.Every(conf.VisitorRequestLimitReplenish),
  416. MessageLimit: messagesLimit,
  417. MessageExpiryDuration: conf.CacheDuration,
  418. EmailLimit: replenishDurationToDailyLimit(conf.VisitorEmailLimitReplenish), // Approximation!
  419. EmailLimitBurst: conf.VisitorEmailLimitBurst,
  420. EmailLimitReplenish: rate.Every(conf.VisitorEmailLimitReplenish),
  421. SMSLimit: int64(conf.VisitorSMSDailyLimit),
  422. CallLimit: int64(conf.VisitorCallDailyLimit),
  423. ReservationsLimit: visitorDefaultReservationsLimit,
  424. AttachmentTotalSizeLimit: conf.VisitorAttachmentTotalSizeLimit,
  425. AttachmentFileSizeLimit: conf.AttachmentFileSizeLimit,
  426. AttachmentExpiryDuration: conf.AttachmentExpiryDuration,
  427. AttachmentBandwidthLimit: conf.VisitorAttachmentDailyBandwidthLimit,
  428. }
  429. }
  430. func (v *visitor) Info() (*visitorInfo, error) {
  431. v.mu.RLock()
  432. info := v.infoLightNoLock()
  433. v.mu.RUnlock()
  434. // Attachment stats from database
  435. var attachmentsBytesUsed int64
  436. var err error
  437. u := v.User()
  438. if u != nil {
  439. attachmentsBytesUsed, err = v.messageCache.AttachmentBytesUsedByUser(u.ID)
  440. } else {
  441. attachmentsBytesUsed, err = v.messageCache.AttachmentBytesUsedBySender(v.IP().String())
  442. }
  443. if err != nil {
  444. return nil, err
  445. }
  446. info.Stats.AttachmentTotalSize = attachmentsBytesUsed
  447. info.Stats.AttachmentTotalSizeRemaining = zeroIfNegative(info.Limits.AttachmentTotalSizeLimit - attachmentsBytesUsed)
  448. // Reservation stats from database
  449. var reservations int64
  450. if v.userManager != nil && u != nil {
  451. reservations, err = v.userManager.ReservationsCount(u.Name)
  452. if err != nil {
  453. return nil, err
  454. }
  455. }
  456. info.Stats.Reservations = reservations
  457. info.Stats.ReservationsRemaining = zeroIfNegative(info.Limits.ReservationsLimit - reservations)
  458. return info, nil
  459. }
  460. func (v *visitor) infoLightNoLock() *visitorInfo {
  461. messages := v.messagesLimiter.Value()
  462. emails := v.emailsLimiter.Value()
  463. sms := v.smsLimiter.Value()
  464. calls := v.callsLimiter.Value()
  465. limits := v.limitsNoLock()
  466. stats := &visitorStats{
  467. Messages: messages,
  468. MessagesRemaining: zeroIfNegative(limits.MessageLimit - messages),
  469. Emails: emails,
  470. EmailsRemaining: zeroIfNegative(limits.EmailLimit - emails),
  471. SMS: sms,
  472. SMSRemaining: zeroIfNegative(limits.SMSLimit - sms),
  473. Calls: calls,
  474. CallsRemaining: zeroIfNegative(limits.CallLimit - calls),
  475. }
  476. return &visitorInfo{
  477. Limits: limits,
  478. Stats: stats,
  479. }
  480. }
  481. func zeroIfNegative(value int64) int64 {
  482. if value < 0 {
  483. return 0
  484. }
  485. return value
  486. }
  487. func replenishDurationToDailyLimit(duration time.Duration) int64 {
  488. return int64(oneDay / duration)
  489. }
  490. func dailyLimitToRate(limit int64) rate.Limit {
  491. return rate.Limit(limit) * rate.Every(oneDay)
  492. }
  493. func visitorID(ip netip.Addr, u *user.User) string {
  494. if u != nil && u.Tier != nil {
  495. return fmt.Sprintf("user:%s", u.ID)
  496. }
  497. return fmt.Sprintf("ip:%s", ip.String())
  498. }