Skip to content

Commit 79568bf

Browse files
committed
Revert "fix: always attempt external auth refresh when fetching (#11762)"
This reverts commit 0befc08.
1 parent 0befc08 commit 79568bf

File tree

6 files changed

+80
-129
lines changed

6 files changed

+80
-129
lines changed

coderd/database/modelmethods.go

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import (
66
"time"
77

88
"golang.org/x/exp/maps"
9-
"golang.org/x/oauth2"
109

1110
"github.com/coder/coder/v2/coderd/database/dbtime"
1211
"github.com/coder/coder/v2/coderd/rbac"
@@ -269,14 +268,6 @@ func (u ExternalAuthLink) RBACObject() rbac.Object {
269268
return rbac.ResourceUserData.WithID(u.UserID).WithOwner(u.UserID.String())
270269
}
271270

272-
func (u ExternalAuthLink) OAuthToken() *oauth2.Token {
273-
return &oauth2.Token{
274-
AccessToken: u.OAuthAccessToken,
275-
RefreshToken: u.OAuthRefreshToken,
276-
Expiry: u.OAuthExpiry,
277-
}
278-
}
279-
280271
func (u UserLink) RBACObject() rbac.Object {
281272
// I assume UserData is ok?
282273
return rbac.ResourceUserData.WithOwner(u.UserID.String()).WithID(u.UserID)

coderd/externalauth.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ func (api *API) externalAuthByID(w http.ResponseWriter, r *http.Request) {
5757
}
5858
var eg errgroup.Group
5959
eg.Go(func() (err error) {
60-
res.Authenticated, res.User, err = config.ValidateToken(ctx, link.OAuthToken())
60+
res.Authenticated, res.User, err = config.ValidateToken(ctx, link.OAuthAccessToken)
6161
return err
6262
})
6363
eg.Go(func() (err error) {

coderd/externalauth/externalauth.go

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ func (c *Config) RefreshToken(ctx context.Context, db database.Store, externalAu
138138
retryCtx, retryCtxCancel := context.WithTimeout(ctx, time.Second)
139139
defer retryCtxCancel()
140140
validate:
141-
valid, _, err := c.ValidateToken(ctx, token)
141+
valid, _, err := c.ValidateToken(ctx, token.AccessToken)
142142
if err != nil {
143143
return externalAuthLink, false, xerrors.Errorf("validate external auth token: %w", err)
144144
}
@@ -179,14 +179,7 @@ validate:
179179

180180
// ValidateToken ensures the Git token provided is valid!
181181
// The user is optionally returned if the provider supports it.
182-
func (c *Config) ValidateToken(ctx context.Context, link *oauth2.Token) (bool, *codersdk.ExternalAuthUser, error) {
183-
if link == nil {
184-
return false, nil, xerrors.New("validate external auth token: token is nil")
185-
}
186-
if !link.Expiry.IsZero() && link.Expiry.Before(dbtime.Now()) {
187-
return false, nil, nil
188-
}
189-
182+
func (c *Config) ValidateToken(ctx context.Context, token string) (bool, *codersdk.ExternalAuthUser, error) {
190183
if c.ValidateURL == "" {
191184
// Default that the token is valid if no validation URL is provided.
192185
return true, nil, nil
@@ -196,7 +189,7 @@ func (c *Config) ValidateToken(ctx context.Context, link *oauth2.Token) (bool, *
196189
return false, nil, err
197190
}
198191

199-
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", link.AccessToken))
192+
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
200193
res, err := c.InstrumentedOAuth2Config.Do(ctx, promoauth.SourceValidateToken, req)
201194
if err != nil {
202195
return false, nil, err
@@ -403,15 +396,10 @@ func (c *DeviceAuth) ExchangeDeviceCode(ctx context.Context, deviceCode string)
403396
if body.Error != "" {
404397
return nil, xerrors.New(body.Error)
405398
}
406-
// If expiresIn is 0, then the token never expires.
407-
expires := dbtime.Now().Add(time.Duration(body.ExpiresIn) * time.Second)
408-
if body.ExpiresIn == 0 {
409-
expires = time.Time{}
410-
}
411399
return &oauth2.Token{
412400
AccessToken: body.AccessToken,
413401
RefreshToken: body.RefreshToken,
414-
Expiry: expires,
402+
Expiry: dbtime.Now().Add(time.Duration(body.ExpiresIn) * time.Second),
415403
}, nil
416404
}
417405

coderd/promoauth/oauth2_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ func TestInstrument(t *testing.T) {
7575
require.Equal(t, count("TokenSource"), 1)
7676

7777
// Try a validate
78-
valid, _, err := cfg.ValidateToken(ctx, refreshed)
78+
valid, _, err := cfg.ValidateToken(ctx, refreshed.AccessToken)
7979
require.NoError(t, err)
8080
require.True(t, valid)
8181
require.Equal(t, count("ValidateToken"), 1)

coderd/workspaceagents.go

Lines changed: 72 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -2031,26 +2031,78 @@ func (api *API) workspaceAgentsExternalAuth(rw http.ResponseWriter, r *http.Requ
20312031
return
20322032
}
20332033

2034-
var previousToken *database.ExternalAuthLink
2035-
// handleRetrying will attempt to continually check for a new token
2036-
// if listen is true. This is useful if an error is encountered in the
2037-
// original single flow.
2038-
//
2039-
// By default, if no errors are encountered, then the single flow response
2040-
// is returned.
2041-
handleRetrying := func(code int, response any) {
2042-
if !listen {
2043-
httpapi.Write(ctx, rw, code, response)
2034+
if listen {
2035+
// Since we're ticking frequently and this sign-in operation is rare,
2036+
// we are OK with polling to avoid the complexity of pubsub.
2037+
ticker, done := api.NewTicker(time.Second)
2038+
defer done()
2039+
var previousToken database.ExternalAuthLink
2040+
for {
2041+
select {
2042+
case <-ctx.Done():
2043+
return
2044+
case <-ticker:
2045+
}
2046+
externalAuthLink, err := api.Database.GetExternalAuthLink(ctx, database.GetExternalAuthLinkParams{
2047+
ProviderID: externalAuthConfig.ID,
2048+
UserID: workspace.OwnerID,
2049+
})
2050+
if err != nil {
2051+
if errors.Is(err, sql.ErrNoRows) {
2052+
continue
2053+
}
2054+
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
2055+
Message: "Failed to get external auth link.",
2056+
Detail: err.Error(),
2057+
})
2058+
return
2059+
}
2060+
2061+
// Expiry may be unset if the application doesn't configure tokens
2062+
// to expire.
2063+
// See
2064+
// https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/generating-a-user-access-token-for-a-github-app.
2065+
if externalAuthLink.OAuthExpiry.Before(dbtime.Now()) && !externalAuthLink.OAuthExpiry.IsZero() {
2066+
continue
2067+
}
2068+
2069+
// Only attempt to revalidate an oauth token if it has actually changed.
2070+
// No point in trying to validate the same token over and over again.
2071+
if previousToken.OAuthAccessToken == externalAuthLink.OAuthAccessToken &&
2072+
previousToken.OAuthRefreshToken == externalAuthLink.OAuthRefreshToken &&
2073+
previousToken.OAuthExpiry == externalAuthLink.OAuthExpiry {
2074+
continue
2075+
}
2076+
2077+
valid, _, err := externalAuthConfig.ValidateToken(ctx, externalAuthLink.OAuthAccessToken)
2078+
if err != nil {
2079+
api.Logger.Warn(ctx, "failed to validate external auth token",
2080+
slog.F("workspace_owner_id", workspace.OwnerID.String()),
2081+
slog.F("validate_url", externalAuthConfig.ValidateURL),
2082+
slog.Error(err),
2083+
)
2084+
}
2085+
previousToken = externalAuthLink
2086+
if !valid {
2087+
continue
2088+
}
2089+
resp, err := createExternalAuthResponse(externalAuthConfig.Type, externalAuthLink.OAuthAccessToken, externalAuthLink.OAuthExtra)
2090+
if err != nil {
2091+
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
2092+
Message: "Failed to create external auth response.",
2093+
Detail: err.Error(),
2094+
})
2095+
return
2096+
}
2097+
httpapi.Write(ctx, rw, http.StatusOK, resp)
20442098
return
20452099
}
2046-
2047-
api.workspaceAgentsExternalAuthListen(ctx, rw, previousToken, externalAuthConfig, workspace)
20482100
}
20492101

20502102
// This is the URL that will redirect the user with a state token.
20512103
redirectURL, err := api.AccessURL.Parse(fmt.Sprintf("/external-auth/%s", externalAuthConfig.ID))
20522104
if err != nil {
2053-
handleRetrying(http.StatusInternalServerError, codersdk.Response{
2105+
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
20542106
Message: "Failed to parse access URL.",
20552107
Detail: err.Error(),
20562108
})
@@ -2063,40 +2115,36 @@ func (api *API) workspaceAgentsExternalAuth(rw http.ResponseWriter, r *http.Requ
20632115
})
20642116
if err != nil {
20652117
if !errors.Is(err, sql.ErrNoRows) {
2066-
handleRetrying(http.StatusInternalServerError, codersdk.Response{
2118+
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
20672119
Message: "Failed to get external auth link.",
20682120
Detail: err.Error(),
20692121
})
20702122
return
20712123
}
20722124

2073-
handleRetrying(http.StatusOK, agentsdk.ExternalAuthResponse{
2125+
httpapi.Write(ctx, rw, http.StatusOK, agentsdk.ExternalAuthResponse{
20742126
URL: redirectURL.String(),
20752127
})
20762128
return
20772129
}
20782130

2079-
externalAuthLink, valid, err := externalAuthConfig.RefreshToken(ctx, api.Database, externalAuthLink)
2131+
externalAuthLink, updated, err := externalAuthConfig.RefreshToken(ctx, api.Database, externalAuthLink)
20802132
if err != nil {
2081-
handleRetrying(http.StatusInternalServerError, codersdk.Response{
2133+
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
20822134
Message: "Failed to refresh external auth token.",
20832135
Detail: err.Error(),
20842136
})
20852137
return
20862138
}
2087-
if !valid {
2088-
// Set the previous token so the retry logic will skip validating the
2089-
// same token again. This should only be set if the token is invalid and there
2090-
// was no error. If it is invalid because of an error, then we should recheck.
2091-
previousToken = &externalAuthLink
2092-
handleRetrying(http.StatusOK, agentsdk.ExternalAuthResponse{
2139+
if !updated {
2140+
httpapi.Write(ctx, rw, http.StatusOK, agentsdk.ExternalAuthResponse{
20932141
URL: redirectURL.String(),
20942142
})
20952143
return
20962144
}
20972145
resp, err := createExternalAuthResponse(externalAuthConfig.Type, externalAuthLink.OAuthAccessToken, externalAuthLink.OAuthExtra)
20982146
if err != nil {
2099-
handleRetrying(http.StatusInternalServerError, codersdk.Response{
2147+
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
21002148
Message: "Failed to create external auth response.",
21012149
Detail: err.Error(),
21022150
})
@@ -2105,81 +2153,6 @@ func (api *API) workspaceAgentsExternalAuth(rw http.ResponseWriter, r *http.Requ
21052153
httpapi.Write(ctx, rw, http.StatusOK, resp)
21062154
}
21072155

2108-
func (api *API) workspaceAgentsExternalAuthListen(ctx context.Context, rw http.ResponseWriter, previous *database.ExternalAuthLink, externalAuthConfig *externalauth.Config, workspace database.Workspace) {
2109-
// Since we're ticking frequently and this sign-in operation is rare,
2110-
// we are OK with polling to avoid the complexity of pubsub.
2111-
ticker, done := api.NewTicker(time.Second)
2112-
defer done()
2113-
// If we have a previous token that is invalid, we should not check this again.
2114-
// This serves to prevent doing excessive unauthorized requests to the external
2115-
// auth provider. For github, this limit is 60 per hour, so saving a call
2116-
// per invalid token can be significant.
2117-
var previousToken database.ExternalAuthLink
2118-
if previous != nil {
2119-
previousToken = *previous
2120-
}
2121-
for {
2122-
select {
2123-
case <-ctx.Done():
2124-
return
2125-
case <-ticker:
2126-
}
2127-
externalAuthLink, err := api.Database.GetExternalAuthLink(ctx, database.GetExternalAuthLinkParams{
2128-
ProviderID: externalAuthConfig.ID,
2129-
UserID: workspace.OwnerID,
2130-
})
2131-
if err != nil {
2132-
if errors.Is(err, sql.ErrNoRows) {
2133-
continue
2134-
}
2135-
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
2136-
Message: "Failed to get external auth link.",
2137-
Detail: err.Error(),
2138-
})
2139-
return
2140-
}
2141-
2142-
// Expiry may be unset if the application doesn't configure tokens
2143-
// to expire.
2144-
// See
2145-
// https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/generating-a-user-access-token-for-a-github-app.
2146-
if externalAuthLink.OAuthExpiry.Before(dbtime.Now()) && !externalAuthLink.OAuthExpiry.IsZero() {
2147-
continue
2148-
}
2149-
2150-
// Only attempt to revalidate an oauth token if it has actually changed.
2151-
// No point in trying to validate the same token over and over again.
2152-
if previousToken.OAuthAccessToken == externalAuthLink.OAuthAccessToken &&
2153-
previousToken.OAuthRefreshToken == externalAuthLink.OAuthRefreshToken &&
2154-
previousToken.OAuthExpiry == externalAuthLink.OAuthExpiry {
2155-
continue
2156-
}
2157-
2158-
valid, _, err := externalAuthConfig.ValidateToken(ctx, externalAuthLink.OAuthToken())
2159-
if err != nil {
2160-
api.Logger.Warn(ctx, "failed to validate external auth token",
2161-
slog.F("workspace_owner_id", workspace.OwnerID.String()),
2162-
slog.F("validate_url", externalAuthConfig.ValidateURL),
2163-
slog.Error(err),
2164-
)
2165-
}
2166-
previousToken = externalAuthLink
2167-
if !valid {
2168-
continue
2169-
}
2170-
resp, err := createExternalAuthResponse(externalAuthConfig.Type, externalAuthLink.OAuthAccessToken, externalAuthLink.OAuthExtra)
2171-
if err != nil {
2172-
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
2173-
Message: "Failed to create external auth response.",
2174-
Detail: err.Error(),
2175-
})
2176-
return
2177-
}
2178-
httpapi.Write(ctx, rw, http.StatusOK, resp)
2179-
return
2180-
}
2181-
}
2182-
21832156
// createExternalAuthResponse creates an ExternalAuthResponse based on the
21842157
// provider type. This is to support legacy `/workspaceagents/me/gitauth`
21852158
// which uses `Username` and `Password`.

coderd/workspaceagents_test.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1577,7 +1577,7 @@ func TestWorkspaceAgentExternalAuthListen(t *testing.T) {
15771577
},
15781578
ExternalAuthConfigs: []*externalauth.Config{
15791579
fake.ExternalAuthConfig(t, providerID, nil, func(cfg *externalauth.Config) {
1580-
cfg.Type = codersdk.EnhancedExternalAuthProviderGitLab.String()
1580+
cfg.Type = codersdk.EnhancedExternalAuthProviderGitHub.String()
15811581
}),
15821582
},
15831583
})
@@ -1623,8 +1623,7 @@ func TestWorkspaceAgentExternalAuthListen(t *testing.T) {
16231623
ticks <- time.Now()
16241624
}
16251625
cancel()
1626-
// We expect only 1. One from the initial "Refresh" attempt, and the
1627-
// other should be skipped.
1626+
// We expect only 1
16281627
// In a failed test, you will likely see 9, as the last one
16291628
// gets canceled.
16301629
require.Equal(t, 1, validateCalls, "validate calls duplicated on same token")

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy