feat: add ability for users to convert their password login type to oauth/github login (#8105)

* Currently toggled by experiment flag

---------

Co-authored-by: Bruno Quaresma <bruno@coder.com>
This commit is contained in:
Steven Masley
2023-06-30 08:38:48 -04:00
committed by GitHub
parent 357f3b38f7
commit b5f26d9bdf
50 changed files with 2043 additions and 261 deletions

View File

@ -16,8 +16,9 @@ import (
type oauth2StateKey struct{}
type OAuth2State struct {
Token *oauth2.Token
Redirect string
Token *oauth2.Token
Redirect string
StateString string
}
// OAuth2Config exposes a subset of *oauth2.Config functions for easier testing.
@ -91,13 +92,24 @@ func ExtractOAuth2(config OAuth2Config, client *http.Client, authURLOpts map[str
if code == "" {
// If the code isn't provided, we'll redirect!
state, err := cryptorand.String(32)
if err != nil {
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Internal error generating state string.",
Detail: err.Error(),
})
return
var state string
// If this url param is provided, then a user is trying to merge
// their account with an OIDC account. Their password would have
// been required to get to this point, so we do not need to verify
// their password again.
oidcMergeState := r.URL.Query().Get("oidc_merge_state")
if oidcMergeState != "" {
state = oidcMergeState
} else {
var err error
state, err = cryptorand.String(32)
if err != nil {
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Internal error generating state string.",
Detail: err.Error(),
})
return
}
}
http.SetCookie(rw, &http.Cookie{
@ -158,8 +170,9 @@ func ExtractOAuth2(config OAuth2Config, client *http.Client, authURLOpts map[str
}
ctx = context.WithValue(ctx, oauth2StateKey{}, OAuth2State{
Token: oauthToken,
Redirect: redirect,
Token: oauthToken,
Redirect: redirect,
StateString: state,
})
next.ServeHTTP(rw, r.WithContext(ctx))
})

View File

@ -7,6 +7,7 @@ import (
"net/url"
"testing"
"github.com/moby/moby/pkg/namesgenerator"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/oauth2"
@ -125,4 +126,21 @@ func TestOAuth2(t *testing.T) {
// testOAuth2Provider does this job for us.
require.NotEmpty(t, location)
})
t.Run("PresetConvertState", func(t *testing.T) {
t.Parallel()
customState := namesgenerator.GetRandomName(1)
req := httptest.NewRequest("GET", "/?oidc_merge_state="+customState+"&redirect="+url.QueryEscape("/dashboard"), nil)
res := httptest.NewRecorder()
tp := newTestOAuth2Provider(t, oauth2.AccessTypeOffline)
httpmw.ExtractOAuth2(tp, nil, nil)(nil).ServeHTTP(res, req)
found := false
for _, cookie := range res.Result().Cookies() {
if cookie.Name == codersdk.OAuth2StateCookie {
require.Equal(t, cookie.Value, customState, "expected state")
found = true
}
}
require.True(t, found, "expected state cookie")
})
}