-
Notifications
You must be signed in to change notification settings - Fork 1.1k
feat: add apiKeyMiddlewareOptional to r.Use(
#21357
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
ethanndickson
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know it's a draft, but we definitely should add a regression test for this
| // Test that /api/experimental works with authentication | ||
| t.Run("APIExperimentalWithAuth", func(t *testing.T) { | ||
| t.Parallel() | ||
|
|
||
| ctx := testutil.Context(t, testutil.WaitLong) | ||
| req, err := http.NewRequestWithContext(ctx, http.MethodGet, client.URL.String()+"/api/experimental", nil) | ||
| require.NoError(t, err) | ||
| req.Header.Set(codersdk.SessionTokenHeader, client.SessionToken()) | ||
|
|
||
| resp, err := client.HTTPClient.Do(req) | ||
| require.NoError(t, err) | ||
| defer resp.Body.Close() | ||
|
|
||
| require.Equal(t, http.StatusOK, resp.StatusCode, "should succeed with auth") | ||
| }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wanted to add a regression test for Experimental routes other than / but I don't think there is anything that will be long-lived here (being that we either promote or remove).
Thoughts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense, / is the only thing that won't get promoted eventually.
| apiRateLimiter, | ||
| httpmw.ReportCLITelemetry(api.Logger, options.Telemetry), | ||
| ) | ||
| r.Get("/", apiRoot) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added an apiRoot here so we can test against something long-lived. Same as the root api/v2/ handler
$ curl http://localhost:8080/api/experimental
{"message":"👋"}
spikecurtis
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are 2 other instances of the rate limit handler being used, currently lines 892 and 915 -- these are for workspace app routes and won't have the user identity available.
|
|
||
| r.NotFound(func(rw http.ResponseWriter, _ *http.Request) { httpapi.RouteNotFound(rw) }) | ||
| r.Use( | ||
| apiKeyMiddlewareOptional, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This needs a more significant refactor than just adding apiKeyMiddlewareOptional at the top of the stack of middlewares.
The problem is that when we add apiKeyMiddleware deeper in the route tree, that middleware restarts the whole process of extracting the API key, including making another database call.
Instead we need an architecture where we always attempt to extract the API key at the top of the route tree, and then deeper in the tree, when we want to make it required, we just check that the identity has been added to the context.
Closes #20857
This PR fixes rate limiting by user authentication. Previously, we weren't passing
apiKeyMiddlewareOptionalto the rate limit middleware, which meant theapiKeywas never available in the rate limiter:Testing
Note
I've prepended
(User)to the rate limit error messages inRateLimitByAuthToken()to distinguish them from the genericRateLimit()function (which shares the same error message, but to catch the error we uselimit+1onRateLimit()whilst debugging). This ensures we're actually testing the user-based rate limiting.Without bypass header (rate limited after 5 requests)
{"regions":[{"id":"...","name":"primary","display_name":"Default","icon_url":"/emojis/1f3e1.png","healthy":true,"path_app_url":"http://127.0.0.1:3000","wildcard_hostname":""}]} {"regions":[{"id":"...","name":"primary","display_name":"Default","icon_url":"/emojis/1f3e1.png","healthy":true,"path_app_url":"http://127.0.0.1:3000","wildcard_hostname":""}]} {"regions":[{"id":"...","name":"primary","display_name":"Default","icon_url":"/emojis/1f3e1.png","healthy":true,"path_app_url":"http://127.0.0.1:3000","wildcard_hostname":""}]} {"regions":[{"id":"...","name":"primary","display_name":"Default","icon_url":"/emojis/1f3e1.png","healthy":true,"path_app_url":"http://127.0.0.1:3000","wildcard_hostname":""}]} {"regions":[{"id":"...","name":"primary","display_name":"Default","icon_url":"/emojis/1f3e1.png","healthy":true,"path_app_url":"http://127.0.0.1:3000","wildcard_hostname":""}]} {"message":"(User) You've been rate limited for sending more than 5 requests in 1m0s."} {"message":"(User) You've been rate limited for sending more than 5 requests in 1m0s."} {"message":"(User) You've been rate limited for sending more than 5 requests in 1m0s."} {"message":"(User) You've been rate limited for sending more than 5 requests in 1m0s."} {"message":"(User) You've been rate limited for sending more than 5 requests in 1m0s."}Result: First 5 requests succeed, remaining 5 are rate limited with error message:
"(User) You've been rate limited for sending more than 5 requests in 1m0s."With bypass header (no rate limiting)
{"regions":[{"id":"...","name":"primary","display_name":"Default","icon_url":"/emojis/1f3e1.png","healthy":true,"path_app_url":"http://127.0.0.1:3000","wildcard_hostname":""}]} {"regions":[{"id":"...","name":"primary","display_name":"Default","icon_url":"/emojis/1f3e1.png","healthy":true,"path_app_url":"http://127.0.0.1:3000","wildcard_hostname":""}]} {"regions":[{"id":"...","name":"primary","display_name":"Default","icon_url":"/emojis/1f3e1.png","healthy":true,"path_app_url":"http://127.0.0.1:3000","wildcard_hostname":""}]} {"regions":[{"id":"...","name":"primary","display_name":"Default","icon_url":"/emojis/1f3e1.png","healthy":true,"path_app_url":"http://127.0.0.1:3000","wildcard_hostname":""}]} {"regions":[{"id":"...","name":"primary","display_name":"Default","icon_url":"/emojis/1f3e1.png","healthy":true,"path_app_url":"http://127.0.0.1:3000","wildcard_hostname":""}]} {"regions":[{"id":"...","name":"primary","display_name":"Default","icon_url":"/emojis/1f3e1.png","healthy":true,"path_app_url":"http://127.0.0.1:3000","wildcard_hostname":""}]} {"regions":[{"id":"...","name":"primary","display_name":"Default","icon_url":"/emojis/1f3e1.png","healthy":true,"path_app_url":"http://127.0.0.1:3000","wildcard_hostname":""}]} {"regions":[{"id":"...","name":"primary","display_name":"Default","icon_url":"/emojis/1f3e1.png","healthy":true,"path_app_url":"http://127.0.0.1:3000","wildcard_hostname":""}]} {"regions":[{"id":"...","name":"primary","display_name":"Default","icon_url":"/emojis/1f3e1.png","healthy":true,"path_app_url":"http://127.0.0.1:3000","wildcard_hostname":""}]} {"regions":[{"id":"...","name":"primary","display_name":"Default","icon_url":"/emojis/1f3e1.png","healthy":true,"path_app_url":"http://127.0.0.1:3000","wildcard_hostname":""}]}Result: All 10 requests succeed without rate limiting.