File tree Expand file tree Collapse file tree 5 files changed +82
-2
lines changed Expand file tree Collapse file tree 5 files changed +82
-2
lines changed Original file line number Diff line number Diff line change @@ -47,14 +47,23 @@ func New(options *Options) (http.Handler, func()) {
47
47
48
48
r := chi .NewRouter ()
49
49
r .Route ("/api/v2" , func (r chi.Router ) {
50
- r .Use (chitrace .Middleware ())
50
+ r .Use (
51
+ chitrace .Middleware (),
52
+ // Specific routes can specify smaller limits.
53
+ httpmw .RateLimitPerMinute (512 ),
54
+ )
51
55
r .Get ("/" , func (w http.ResponseWriter , r * http.Request ) {
52
56
httpapi .Write (w , http .StatusOK , httpapi.Response {
53
57
Message : "👋" ,
54
58
})
55
59
})
56
60
r .Route ("/files" , func (r chi.Router ) {
57
- r .Use (httpmw .ExtractAPIKey (options .Database , nil ))
61
+ r .Use (
62
+ httpmw .ExtractAPIKey (options .Database , nil ),
63
+ // This number is arbitrary, but reading/writing
64
+ // file content is expensive so it should be small.
65
+ httpmw .RateLimitPerMinute (12 ),
66
+ )
58
67
r .Get ("/{hash}" , api .fileByHash )
59
68
r .Post ("/" , api .postFile )
60
69
})
Original file line number Diff line number Diff line change
1
+ package httpmw
2
+
3
+ import (
4
+ "net/http"
5
+ "time"
6
+
7
+ "github.com/go-chi/httprate"
8
+ "github.com/go-chi/render"
9
+
10
+ "github.com/coder/coder/coderd/database"
11
+ "github.com/coder/coder/coderd/httpapi"
12
+ )
13
+
14
+ // RateLimitPerMinute returns a handler that limits requests per-minute based
15
+ // on IP, endpoint, and user ID (if available).
16
+ func RateLimitPerMinute (count int ) func (http.Handler ) http.Handler {
17
+ return httprate .Limit (
18
+ count ,
19
+ 1 * time .Minute ,
20
+ httprate .WithKeyFuncs (func (r * http.Request ) (string , error ) {
21
+ // Prioritize by user, but fallback to IP.
22
+ apiKey , ok := r .Context ().Value (apiKeyContextKey {}).(database.APIKey )
23
+ if ok {
24
+ return apiKey .UserID .String (), nil
25
+ }
26
+ return httprate .KeyByIP (r )
27
+ }, httprate .KeyByEndpoint ),
28
+ httprate .WithLimitHandler (func (w http.ResponseWriter , r * http.Request ) {
29
+ render .Status (r , http .StatusTooManyRequests )
30
+ render .JSON (w , r , httpapi.Response {
31
+ Message : "You've been rate limited for sending too many requests!" ,
32
+ })
33
+ }),
34
+ )
35
+ }
Original file line number Diff line number Diff line change
1
+ package httpmw_test
2
+
3
+ import (
4
+ "net/http"
5
+ "net/http/httptest"
6
+ "testing"
7
+ "time"
8
+
9
+ "github.com/go-chi/chi/v5"
10
+ "github.com/stretchr/testify/require"
11
+
12
+ "github.com/coder/coder/coderd/httpmw"
13
+ )
14
+
15
+ func TestRateLimit (t * testing.T ) {
16
+ t .Parallel ()
17
+ t .Run ("NoUser" , func (t * testing.T ) {
18
+ t .Parallel ()
19
+ rtr := chi .NewRouter ()
20
+ rtr .Use (httpmw .RateLimitPerMinute (5 ))
21
+ rtr .Get ("/" , func (rw http.ResponseWriter , r * http.Request ) {
22
+ rw .WriteHeader (http .StatusOK )
23
+ })
24
+
25
+ require .Eventually (t , func () bool {
26
+ req := httptest .NewRequest ("GET" , "/" , nil )
27
+ rec := httptest .NewRecorder ()
28
+ rtr .ServeHTTP (rec , req )
29
+ return rec .Result ().StatusCode == http .StatusTooManyRequests
30
+ }, 5 * time .Second , time .Millisecond )
31
+ })
32
+ }
Original file line number Diff line number Diff line change @@ -94,6 +94,8 @@ require (
94
94
storj.io/drpc v0.0.30
95
95
)
96
96
97
+ require github.com/go-chi/httprate v0.5.3
98
+
97
99
require (
98
100
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
99
101
github.com/BurntSushi/toml v1.0.0 // indirect
Original file line number Diff line number Diff line change @@ -598,6 +598,8 @@ github.com/go-chi/chi/v4 v4.0.0-rc1/go.mod h1:Yfiy+5nynjDc7IMJiguACIro1KxlGW2dLU
598
598
github.com/go-chi/chi/v5 v5.0.0 /go.mod h1:BBug9lr0cqtdAhsu6R4AAdvufI0/XBzAQSsUqJpoZOs =
599
599
github.com/go-chi/chi/v5 v5.0.7 h1:rDTPXLDHGATaeHvVlLcR4Qe0zftYethFucbjVQ1PxU8 =
600
600
github.com/go-chi/chi/v5 v5.0.7 /go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8 =
601
+ github.com/go-chi/httprate v0.5.3 h1:5HPWb0N6ymIiuotMtCfOGpQKiKeqXVzMexHh1W1yXPc =
602
+ github.com/go-chi/httprate v0.5.3 /go.mod h1:kYR4lorHX3It9tTh4eTdHhcF2bzrYnCrRNlv5+IBm2M =
601
603
github.com/go-chi/render v1.0.1 h1:4/5tis2cKaNdnv9zFLfXzcquC9HbeZgCnxGnKrltBS8 =
602
604
github.com/go-chi/render v1.0.1 /go.mod h1:pq4Rr7HbnsdaeHagklXub+p6Wd16Af5l9koip1OvJns =
603
605
github.com/go-errors/errors v1.0.1 /go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q =
You can’t perform that action at this time.
0 commit comments