(lmt *limiter.Limiter, req *http.Request)
| 70 | var emailRateLimitCounter = observability.ObtainMetricCounter("gotrue_email_rate_limit_counter", "Number of times an email rate limit has been triggered") |
| 71 | |
| 72 | func (a *API) performRateLimitingWithHeader(lmt *limiter.Limiter, req *http.Request) error { |
| 73 | limitHeader := a.config.RateLimitHeader |
| 74 | |
| 75 | // If no rate limit header was set, ignore rate limiting |
| 76 | if limitHeader == "" { |
| 77 | return nil |
| 78 | } |
| 79 | |
| 80 | valuesStr := req.Header.Get(limitHeader) |
| 81 | |
| 82 | // If a rate limit header was set, but has no value, ignore rate limiting but warn with an error |
| 83 | if valuesStr == "" { |
| 84 | log := observability.GetLogEntry(req).Entry |
| 85 | log.WithField("header", limitHeader).Warn("request does not have a value for the rate limiting header, rate limiting is not applied") |
| 86 | |
| 87 | return nil |
| 88 | } |
| 89 | |
| 90 | // According to RFC 7230 section 3.2.2, multiple headers with the same name are equivalent |
| 91 | // to a single header with that name where each value is separated by a comma and whitespace. |
| 92 | // |
| 93 | // Note that there is some ambiguity in RFC 7230 where section 3.2.4 states that |
| 94 | // header field values (which can contain commas) are processed independently of the header |
| 95 | // field name, and thus it is not always clear if a comma is a list delimiter or simply par |
| 96 | // of a single value. |
| 97 | // |
| 98 | // Given that this function is primarily for use with headers like X-Forwarded-For which |
| 99 | // vendors generally combine into comma-separated lists, we opt for the simpler approach |
| 100 | // here and split the header value by commas before taking the first value. |
| 101 | values := strings.SplitN(valuesStr, ",", 2) |
| 102 | |
| 103 | // We will always get at least one value back, so this operation is safe |
| 104 | key := strings.TrimSpace(values[0]) |
| 105 | |
| 106 | // If the rate limit header has at least one value, but the first value is all whitespace, return a warning. |
| 107 | // This will happen if the header is something like "X-Foo-Bar: ,baz". |
| 108 | if key == "" { |
| 109 | log := observability.GetLogEntry(req).Entry |
| 110 | log.WithField("header", limitHeader).Warn("first rate limit header value is empty, rate limiting is not applied") |
| 111 | |
| 112 | return nil |
| 113 | } |
| 114 | |
| 115 | // Otherwise, apply rate limiting based on the first rate limit header value |
| 116 | if err := tollbooth.LimitByKeys(lmt, []string{key}); err != nil { |
| 117 | return apierrors.NewTooManyRequestsError(apierrors.ErrorCodeOverRequestRateLimit, "Request rate limit reached") |
| 118 | } |
| 119 | |
| 120 | return nil |
| 121 | } |
| 122 | |
| 123 | func (a *API) performRateLimiting(lmt *limiter.Limiter, req *http.Request) error { |
| 124 | if sbffAddr, ok := sbff.GetIPAddress(req); ok { |
no test coverage detected