limits.go
| 1 | package web |
| 2 | |
| 3 | import ( |
| 4 | "net/http" |
| 5 | "strconv" |
| 6 | "time" |
| 7 | |
| 8 | "hearthforge/internal/ratelimit" |
| 9 | ) |
| 10 | |
| 11 | // Rate limits. The window is one minute unless noted. |
| 12 | var ( |
| 13 | loginLimiter = ratelimit.New(10, time.Minute) |
| 14 | registrationLimiter = ratelimit.New(3, time.Hour) |
| 15 | gitAuthLimiter = ratelimit.New(10, time.Minute) |
| 16 | commentLimiter = ratelimit.New(30, time.Minute) |
| 17 | reactionLimiter = ratelimit.New(60, time.Minute) |
| 18 | issueCreateLimiter = ratelimit.New(10, time.Minute) |
| 19 | patchCreateLimiter = ratelimit.New(10, time.Minute) |
| 20 | labelWriteLimiter = ratelimit.New(30, time.Minute) |
| 21 | uploadLimiter = ratelimit.New(10, time.Minute) |
| 22 | passwordLimiter = ratelimit.New(10, time.Minute) |
| 23 | ) |
| 24 | |
| 25 | // limitKey is "u<id>" for logged-in users, else the client IP. |
| 26 | func (s *Server) limitKey(r *http.Request) string { |
| 27 | if u := User(r); u != nil { |
| 28 | return "u" + strconv.FormatInt(u.ID, 10) |
| 29 | } |
| 30 | return ratelimit.ClientIP(r, s.Cfg.TrustedProxy) |
| 31 | } |
| 32 | |
| 33 | // allowed reports whether the request may proceed under l. byIP keys on the |
| 34 | // client address instead of the session, for endpoints reachable logged out. |
| 35 | // Handlers that answer in JSON call this and write their own 429. |
| 36 | func (s *Server) allowed(r *http.Request, l *ratelimit.Limiter, byIP bool) bool { |
| 37 | if s.Cfg.RateLimitDisabled { |
| 38 | return true |
| 39 | } |
| 40 | key := s.limitKey(r) |
| 41 | if byIP { |
| 42 | key = ratelimit.ClientIP(r, s.Cfg.TrustedProxy) |
| 43 | } |
| 44 | return l.Allow(key) |
| 45 | } |
| 46 | |
| 47 | // limited writes a plain-text 429 when the request exceeded l. Callers return |
| 48 | // immediately when it reports true. |
| 49 | func (s *Server) limited(w http.ResponseWriter, r *http.Request, l *ratelimit.Limiter, byIP bool) bool { |
| 50 | if s.allowed(r, l, byIP) { |
| 51 | return false |
| 52 | } |
| 53 | w.Header().Set("Content-Type", "text/plain; charset=utf-8") |
| 54 | w.WriteHeader(http.StatusTooManyRequests) |
| 55 | w.Write([]byte("Too many requests. Please slow down.")) |
| 56 | return true |
| 57 | } |
| 58 |