Skip to content

Commit 9c098b2

Browse files
authored
feat: allow external auth providers to expose extra metadata (#10157)
1 parent 3eb9a43 commit 9c098b2

12 files changed

+143
-29
lines changed

cli/externalauth.go

+33-15
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
package cli
22

33
import (
4+
"encoding/json"
45
"os/signal"
56

67
"golang.org/x/xerrors"
78

9+
"github.com/tidwall/gjson"
10+
811
"github.com/coder/coder/v2/cli/clibase"
912
"github.com/coder/coder/v2/cli/cliui"
1013
"github.com/coder/coder/v2/codersdk/agentsdk"
@@ -25,7 +28,7 @@ func (r *RootCmd) externalAuth() *clibase.Cmd {
2528
}
2629

2730
func (r *RootCmd) externalAuthAccessToken() *clibase.Cmd {
28-
var silent bool
31+
var extra string
2932
return &clibase.Cmd{
3033
Use: "access-token <provider>",
3134
Short: "Print auth for an external provider",
@@ -45,12 +48,16 @@ else
4548
fi
4649
`,
4750
},
51+
example{
52+
Description: "Obtain an extra property of an access token for additional metadata.",
53+
Command: "coder external-auth access-token slack --extra \"authed_user.id\"",
54+
},
4855
),
4956
Options: clibase.OptionSet{{
50-
Name: "Silent",
51-
Flag: "s",
52-
Description: "Do not print the URL or access token.",
53-
Value: clibase.BoolOf(&silent),
57+
Name: "Extra",
58+
Flag: "extra",
59+
Description: "Extract a field from the \"extra\" properties of the OAuth token.",
60+
Value: clibase.StringOf(&extra),
5461
}},
5562

5663
Handler: func(inv *clibase.Invocation) error {
@@ -64,26 +71,37 @@ fi
6471
return xerrors.Errorf("create agent client: %w", err)
6572
}
6673

67-
token, err := client.ExternalAuth(ctx, agentsdk.ExternalAuthRequest{
74+
extAuth, err := client.ExternalAuth(ctx, agentsdk.ExternalAuthRequest{
6875
ID: inv.Args[0],
6976
})
7077
if err != nil {
7178
return xerrors.Errorf("get external auth token: %w", err)
7279
}
73-
74-
if !silent {
75-
if token.URL != "" {
76-
_, err = inv.Stdout.Write([]byte(token.URL))
77-
} else {
78-
_, err = inv.Stdout.Write([]byte(token.AccessToken))
80+
if extAuth.URL != "" {
81+
_, err = inv.Stdout.Write([]byte(extAuth.URL))
82+
if err != nil {
83+
return err
84+
}
85+
return cliui.Canceled
86+
}
87+
if extra != "" {
88+
if extAuth.TokenExtra == nil {
89+
return xerrors.Errorf("no extra properties found for token")
90+
}
91+
data, err := json.Marshal(extAuth.TokenExtra)
92+
if err != nil {
93+
return xerrors.Errorf("marshal extra properties: %w", err)
7994
}
95+
result := gjson.GetBytes(data, extra)
96+
_, err = inv.Stdout.Write([]byte(result.String()))
8097
if err != nil {
8198
return err
8299
}
100+
return nil
83101
}
84-
85-
if token.URL != "" {
86-
return cliui.Canceled
102+
_, err = inv.Stdout.Write([]byte(extAuth.AccessToken))
103+
if err != nil {
104+
return err
87105
}
88106
return nil
89107
},

cli/externalauth_test.go

+18
Original file line numberDiff line numberDiff line change
@@ -46,4 +46,22 @@ func TestExternalAuth(t *testing.T) {
4646
clitest.Start(t, inv)
4747
pty.ExpectMatch("bananas")
4848
})
49+
t.Run("SuccessWithExtra", func(t *testing.T) {
50+
t.Parallel()
51+
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
52+
httpapi.Write(context.Background(), w, http.StatusOK, agentsdk.ExternalAuthResponse{
53+
AccessToken: "bananas",
54+
TokenExtra: map[string]interface{}{
55+
"hey": "there",
56+
},
57+
})
58+
}))
59+
t.Cleanup(srv.Close)
60+
url := srv.URL
61+
inv, _ := clitest.New(t, "--agent-url", url, "external-auth", "access-token", "github", "--extra", "hey")
62+
pty := ptytest.New(t)
63+
inv.Stdout = pty.Output()
64+
clitest.Start(t, inv)
65+
pty.ExpectMatch("there")
66+
})
4967
}

cli/testdata/coder_external-auth_access-token_--help.golden

+6-2
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,14 @@ USAGE:
1919
echo "Please authenticate with GitHub:"
2020
echo $OUTPUT
2121
fi
22+
23+
- Obtain an extra property of an access token for additional metadata.:
24+
25+
$ coder external-auth access-token slack --extra "authed_user.id"
2226

2327
OPTIONS:
24-
--s bool
25-
Do not print the URL or access token.
28+
--extra string
29+
Extract a field from the "extra" properties of the OAuth token.
2630

2731
———
2832
Run `coder --help` for a list of global options.

coderd/apidoc/docs.go

+4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/apidoc/swagger.json

+4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/workspaceagents.go

+26-4
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222

2323
"github.com/go-chi/chi/v5"
2424
"github.com/google/uuid"
25+
"github.com/sqlc-dev/pqtype"
2526
"golang.org/x/exp/maps"
2627
"golang.org/x/exp/slices"
2728
"golang.org/x/mod/semver"
@@ -2306,7 +2307,15 @@ func (api *API) workspaceAgentsExternalAuth(rw http.ResponseWriter, r *http.Requ
23062307
if !valid {
23072308
continue
23082309
}
2309-
httpapi.Write(ctx, rw, http.StatusOK, createExternalAuthResponse(externalAuthConfig.Type, externalAuthLink.OAuthAccessToken))
2310+
resp, err := createExternalAuthResponse(externalAuthConfig.Type, externalAuthLink.OAuthAccessToken, externalAuthLink.OAuthExtra)
2311+
if err != nil {
2312+
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
2313+
Message: "Failed to create external auth response.",
2314+
Detail: err.Error(),
2315+
})
2316+
return
2317+
}
2318+
httpapi.Write(ctx, rw, http.StatusOK, resp)
23102319
return
23112320
}
23122321
}
@@ -2354,13 +2363,21 @@ func (api *API) workspaceAgentsExternalAuth(rw http.ResponseWriter, r *http.Requ
23542363
})
23552364
return
23562365
}
2357-
httpapi.Write(ctx, rw, http.StatusOK, createExternalAuthResponse(externalAuthConfig.Type, externalAuthLink.OAuthAccessToken))
2366+
resp, err := createExternalAuthResponse(externalAuthConfig.Type, externalAuthLink.OAuthAccessToken, externalAuthLink.OAuthExtra)
2367+
if err != nil {
2368+
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
2369+
Message: "Failed to create external auth response.",
2370+
Detail: err.Error(),
2371+
})
2372+
return
2373+
}
2374+
httpapi.Write(ctx, rw, http.StatusOK, resp)
23582375
}
23592376

23602377
// createExternalAuthResponse creates an ExternalAuthResponse based on the
23612378
// provider type. This is to support legacy `/workspaceagents/me/gitauth`
23622379
// which uses `Username` and `Password`.
2363-
func createExternalAuthResponse(typ, token string) agentsdk.ExternalAuthResponse {
2380+
func createExternalAuthResponse(typ, token string, extra pqtype.NullRawMessage) (agentsdk.ExternalAuthResponse, error) {
23642381
var resp agentsdk.ExternalAuthResponse
23652382
switch typ {
23662383
case string(codersdk.EnhancedExternalAuthProviderGitLab):
@@ -2382,7 +2399,12 @@ func createExternalAuthResponse(typ, token string) agentsdk.ExternalAuthResponse
23822399
}
23832400
resp.AccessToken = token
23842401
resp.Type = typ
2385-
return resp
2402+
2403+
var err error
2404+
if extra.Valid {
2405+
err = json.Unmarshal(extra.RawMessage, &resp.TokenExtra)
2406+
}
2407+
return resp, err
23862408
}
23872409

23882410
// wsNetConn wraps net.Conn created by websocket.NetConn(). Cancel func

codersdk/agentsdk/agentsdk.go

+4-3
Original file line numberDiff line numberDiff line change
@@ -711,9 +711,10 @@ func (c *Client) GetServiceBanner(ctx context.Context) (codersdk.ServiceBannerCo
711711
}
712712

713713
type ExternalAuthResponse struct {
714-
AccessToken string `json:"access_token"`
715-
URL string `json:"url"`
716-
Type string `json:"type"`
714+
AccessToken string `json:"access_token"`
715+
TokenExtra map[string]interface{} `json:"token_extra"`
716+
URL string `json:"url"`
717+
Type string `json:"type"`
717718

718719
// Deprecated: Only supported on `/workspaceagents/me/gitauth`
719720
// for backwards compatibility.

docs/api/agents.md

+2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/api/schemas.md

+2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/cli/external-auth_access-token.md

+9-5
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

go.mod

+3
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,9 @@ require (
379379
github.com/tcnksm/go-httpstat v0.2.0 // indirect
380380
github.com/tdewolff/parse/v2 v2.6.6 // indirect
381381
github.com/tdewolff/test v1.0.9 // indirect
382+
github.com/tidwall/gjson v1.17.0 // indirect
383+
github.com/tidwall/match v1.1.1 // indirect
384+
github.com/tidwall/pretty v1.2.1 // indirect
382385
github.com/tinylib/msgp v1.1.8 // indirect
383386
github.com/u-root/uio v0.0.0-20230305220412-3e8cd9d6bf63 // indirect
384387
github.com/ulikunitz/xz v0.5.11 // indirect

0 commit comments

Comments
 (0)