Skip to content

Commit b3b4d73

Browse files
committed
feat: Add no_refresh option to Git auth configs
This allows organizations to disable refreshing Git tokens and instead prompt for authentication again.
1 parent b5181aa commit b3b4d73

File tree

5 files changed

+86
-0
lines changed

5 files changed

+86
-0
lines changed

coderd/gitauth/config.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,12 @@ type Config struct {
2222
Regex *regexp.Regexp
2323
// Type is the type of provider.
2424
Type codersdk.GitProvider
25+
// NoRefresh stops Coder from using the refresh token
26+
// to renew the access token.
27+
//
28+
// Some organizations have security policies that require
29+
// re-authentication for every token.
30+
NoRefresh bool
2531
}
2632

2733
// ConvertConfig converts the YAML configuration entry to the
@@ -107,6 +113,7 @@ func ConvertConfig(entries []codersdk.GitAuthConfig, accessURL *url.URL) ([]*Con
107113
ID: entry.ID,
108114
Regex: regex,
109115
Type: typ,
116+
NoRefresh: entry.NoRefresh,
110117
})
111118
}
112119
return configs, nil

coderd/workspaceagents.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1154,6 +1154,15 @@ func (api *API) workspaceAgentsGitAuth(rw http.ResponseWriter, r *http.Request)
11541154
return
11551155
}
11561156

1157+
// If the token is expired and refresh is disabled, we prompt
1158+
// the user to authenticate again.
1159+
if gitAuthConfig.NoRefresh && gitAuthLink.OAuthExpiry.Before(database.Now()) {
1160+
httpapi.Write(ctx, rw, http.StatusOK, codersdk.WorkspaceAgentGitAuthResponse{
1161+
URL: redirectURL.String(),
1162+
})
1163+
return
1164+
}
1165+
11571166
token, err := gitAuthConfig.TokenSource(ctx, &oauth2.Token{
11581167
AccessToken: gitAuthLink.OAuthAccessToken,
11591168
RefreshToken: gitAuthLink.OAuthRefreshToken,

coderd/workspaceagents_test.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,13 @@ import (
1717
"github.com/google/uuid"
1818
"github.com/stretchr/testify/assert"
1919
"github.com/stretchr/testify/require"
20+
"golang.org/x/oauth2"
2021

2122
"cdr.dev/slog"
2223
"cdr.dev/slog/sloggers/slogtest"
2324
"github.com/coder/coder/agent"
2425
"github.com/coder/coder/coderd/coderdtest"
26+
"github.com/coder/coder/coderd/database"
2527
"github.com/coder/coder/coderd/gitauth"
2628
"github.com/coder/coder/codersdk"
2729
"github.com/coder/coder/provisioner/echo"
@@ -884,6 +886,72 @@ func TestWorkspaceAgentsGitAuth(t *testing.T) {
884886
resp = gitAuthCallback(t, "github", client)
885887
require.Equal(t, http.StatusTemporaryRedirect, resp.StatusCode)
886888
})
889+
890+
t.Run("ExpiredNoRefresh", func(t *testing.T) {
891+
t.Parallel()
892+
client := coderdtest.New(t, &coderdtest.Options{
893+
IncludeProvisionerDaemon: true,
894+
GitAuthConfigs: []*gitauth.Config{{
895+
OAuth2Config: &oauth2Config{
896+
token: &oauth2.Token{
897+
AccessToken: "token",
898+
RefreshToken: "something",
899+
Expiry: database.Now().Add(-time.Hour),
900+
},
901+
},
902+
ID: "github",
903+
Regex: regexp.MustCompile(`github\.com`),
904+
Type: codersdk.GitProviderGitHub,
905+
NoRefresh: true,
906+
}},
907+
})
908+
user := coderdtest.CreateFirstUser(t, client)
909+
authToken := uuid.NewString()
910+
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
911+
Parse: echo.ParseComplete,
912+
ProvisionPlan: echo.ProvisionComplete,
913+
ProvisionApply: []*proto.Provision_Response{{
914+
Type: &proto.Provision_Response_Complete{
915+
Complete: &proto.Provision_Complete{
916+
Resources: []*proto.Resource{{
917+
Name: "example",
918+
Type: "aws_instance",
919+
Agents: []*proto.Agent{{
920+
Id: uuid.NewString(),
921+
Auth: &proto.Agent_Token{
922+
Token: authToken,
923+
},
924+
}},
925+
}},
926+
},
927+
},
928+
}},
929+
})
930+
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
931+
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
932+
workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
933+
coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
934+
935+
agentClient := codersdk.New(client.URL)
936+
agentClient.SetSessionToken(authToken)
937+
938+
token, err := agentClient.WorkspaceAgentGitAuth(context.Background(), "github.com/asd/asd", false)
939+
require.NoError(t, err)
940+
require.NotEmpty(t, token.URL)
941+
942+
// In the configuration, we set our OAuth provider
943+
// to return an expired token. Coder consumes this
944+
// and stores it.
945+
resp := gitAuthCallback(t, "github", client)
946+
require.Equal(t, http.StatusTemporaryRedirect, resp.StatusCode)
947+
948+
// Because the token is expired and `NoRefresh` is specified,
949+
// a redirect URL should be returned again.
950+
token, err = agentClient.WorkspaceAgentGitAuth(context.Background(), "github.com/asd/asd", false)
951+
require.NoError(t, err)
952+
require.NotEmpty(t, token.URL)
953+
})
954+
887955
t.Run("FullFlow", func(t *testing.T) {
888956
t.Parallel()
889957
client := coderdtest.New(t, &coderdtest.Options{

codersdk/deploymentconfig.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ type GitAuthConfig struct {
124124
AuthURL string `json:"auth_url"`
125125
TokenURL string `json:"token_url"`
126126
Regex string `json:"regex"`
127+
NoRefresh bool `json:"no_refresh"`
127128
Scopes []string `json:"scopes"`
128129
}
129130

site/src/api/typesGenerated.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,7 @@ export interface GitAuthConfig {
362362
readonly auth_url: string
363363
readonly token_url: string
364364
readonly regex: string
365+
readonly no_refresh: boolean
365366
readonly scopes: string[]
366367
}
367368

0 commit comments

Comments
 (0)