@@ -10,6 +10,7 @@ import (
1010 "os"
1111 "strconv"
1212 "strings"
13+ "time"
1314
1415 "github.com/lightninglabs/aperture/auth"
1516 "github.com/lightninglabs/aperture/l402"
@@ -167,10 +168,47 @@ func (p *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
167168 return
168169 }
169170
171+ // Apply per-endpoint rate limits, if configured.
172+ for _ , rl := range target .compiledRateLimits {
173+ if ! rl .re .MatchString (r .URL .Path ) {
174+ continue
175+ }
176+
177+ // Fast path: allow if a token is available now.
178+ if rl .allow () {
179+ continue
180+ }
181+
182+ // Otherwise, compute suggested retry delay without consuming
183+ // tokens.
184+ res := rl .limiter .Reserve ()
185+ if res .OK () {
186+ delay := res .Delay ()
187+ res .CancelAt (time .Now ())
188+ if delay > 0 {
189+ // As seconds; for sub-second delays we still
190+ // send 1 second.
191+ secs := int (delay .Seconds ())
192+ if secs == 0 {
193+ secs = 1
194+ }
195+ w .Header ().Set (
196+ "Retry-After" , strconv .Itoa (secs ),
197+ )
198+ }
199+ }
200+ addCorsHeaders (w .Header ())
201+ sendDirectResponse (
202+ w , r , http .StatusTooManyRequests , "rate limit exceeded" ,
203+ )
204+
205+ return
206+ }
207+
170208 resourceName := target .ResourceName (r .URL .Path )
171209
172- // Determine auth level required to access service and dispatch request
173- // accordingly.
210+ // Determine the auth level required to access service and dispatch the
211+ // request accordingly.
174212 authLevel := target .AuthRequired (r )
175213 skipInvoiceCreation := target .SkipInvoiceCreation (r )
176214 switch {
0 commit comments