10000 chore: implement device auth flow for fake idp by Emyrk · Pull Request #11707 · coder/coder · GitHub
[go: up one dir, main page]

Skip to content

chore: implement device auth flow for fake idp #11707

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

Merged
merged 9 commits into from
Jan 22, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
improve error handling around device code failure
  • Loading branch information
Emyrk committed Jan 22, 2024
commit c3fab8f041afa1db5703a97371409cd445d778b2
32 changes: 26 additions & 6 deletions coderd/externalauth/externalauth.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ import (
"encoding/json"
"fmt"
"io"
"mime"
"net/http"
"net/url"
"regexp"
"strconv"
"strings"
"time"

Expand Down Expand Up @@ -321,13 +323,31 @@ func (c *DeviceAuth) AuthorizeDevice(ctx context.Context) (*codersdk.ExternalAut
}
err = json.NewDecoder(resp.Body).Decode(&r)
if err != nil {
// Some status codes do not return json payloads, and we should
// return a better error.
switch resp.StatusCode {
case http.StatusTooManyRequests:
return nil, xerrors.New("rate limit hit, unable to authorize device. please try again later")
mediaType, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type"))
if err != nil {
mediaType = "unknown"
}

// If the json fails to decode, do a best effort to return a better error.
switch {
case resp.StatusCode == http.StatusTooManyRequests:
retryIn := "please try again later"
resetIn := resp.Header.Get("x-ratelimit-reset")
if resetIn != "" {
// Best effort to tell the user exactly how long they need
// to wait for.
unix, err := strconv.ParseInt(resetIn, 10, 64)
if err == nil {
waitFor := time.Unix(unix, 0).Sub(time.Now().Truncate(time.Second))
retryIn = fmt.Sprintf(" retry after %s", waitFor.Truncate(time.Second))
}
}
// 429 returns a plaintext payload with a message.
return nil, xerrors.New(fmt.Sprintf("rate limit hit, unable to authorize device. %s", retryIn))
case mediaType == "application/x-www-form-urlencoded":
return nil, xerrors.Errorf("%s payload response is form-url encoded, expected a json payload", resp.StatusCode)
default:
return nil, xerrors.Errorf("status_code=%d: %w", resp.StatusCode, err)
return nil, fmt.Errorf("status_code=%d, mediaType=%s: %w", resp.StatusCode, mediaType, err)
}
}
if r.ErrorDescription != "" {
Expand Down
25 changes: 25 additions & 0 deletions coderd/externalauth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"net/http"
"net/http/httptest"
"net/url"
"regexp"
"strings"
"testing"
Expand Down Expand Up @@ -363,6 +364,30 @@ func TestExternalAuthDevice(t *testing.T) {
_, err := client.ExternalAuthDeviceByID(context.Background(), "test")
require.ErrorContains(t, err, "rate limit hit")
})

// If we forget to add the accept header, we get a form encoded body instead.
t.Run("FormEncodedBody", func(t *testing.T) {
t.Parallel()
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/x-www-form-urlencoded")
_, _ = w.Write([]byte(url.Values{"access_token": {"hey"}}.Encode()))
}))
defer srv.Close()
client := coderdtest.New(t, &coderdtest.Options{
ExternalAuthConfigs: []*externalauth.Config{{
ID: "test",
DeviceAuth: &externalauth.DeviceAuth{
ClientID: "test",
CodeURL: srv.URL,
Scopes: []string{"repo"},
},
}},
})
coderdtest.CreateFirstUser(t, client)
_, err := client.ExternalAuthDeviceByID(context.Background(), "test")
require.Error(t, err)
require.ErrorContains(t, err, "is form-url encoded")
})
}

// nolint:bodyclose
Expand Down
0