Skip to content

Commit b844b2b

Browse files
committed
Add AuthMethods endpoint
1 parent 05b6a37 commit b844b2b

File tree

5 files changed

+90
-30
lines changed

5 files changed

+90
-30
lines changed

cli/start.go

+6-3
Original file line numberDiff line numberDiff line change
@@ -557,6 +557,7 @@ func configureGithubOAuth2(accessURL *url.URL, clientID, clientSecret string) (*
557557
RedirectURL: redirectURL.String(),
558558
Scopes: []string{
559559
"read:user",
560+
"read:org",
560561
"user:email",
561562
},
562563
},
@@ -570,9 +571,11 @@ func configureGithubOAuth2(accessURL *url.URL, clientID, clientSecret string) (*
570571
emails, _, err := github.NewClient(client).Users.ListEmails(ctx, &github.ListOptions{})
571572
return emails, err
572573
},
573-
ListOrganizations: func(ctx context.Context, client *http.Client) ([]*github.Organization, error) {
574-
orgs, _, err := github.NewClient(client).Organizations.List(ctx, "", &github.ListOptions{})
575-
return orgs, err
574+
ListOrganizationMemberships: func(ctx context.Context, client *http.Client) ([]*github.Membership, error) {
575+
memberships, _, err := github.NewClient(client).Organizations.ListOrgMemberships(ctx, &github.ListOrgMembershipsOptions{
576+
State: "active",
577+
})
578+
return memberships, err
576579
},
577580
}, nil
578581
}

coderd/coderd.go

+1
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ func New(options *Options) (http.Handler, func()) {
146146
r.Post("/first", api.postFirstUser)
147147
r.Post("/login", api.postLogin)
148148
r.Post("/logout", api.postLogout)
149+
r.Get("/authmethods", api.userAuthMethods)
149150
r.Route("/oauth2", func(r chi.Router) {
150151
r.Route("/github", func(r chi.Router) {
151152
r.Use(httpmw.ExtractOAuth2(options.GithubOAuth2Config))

coderd/useroauth2.go renamed to coderd/userauth.go

+16-12
Original file line numberDiff line numberDiff line change
@@ -20,39 +20,43 @@ import (
2020
// GithubOAuth2Provider exposes required functions for the Github authentication flow.
2121
type GithubOAuth2Config struct {
2222
httpmw.OAuth2Config
23-
AuthenticatedUser func(ctx context.Context, client *http.Client) (*github.User, error)
24-
ListEmails func(ctx context.Context, client *http.Client) ([]*github.UserEmail, error)
25-
ListOrganizations func(ctx context.Context, client *http.Client) ([]*github.Organization, error)
23+
AuthenticatedUser func(ctx context.Context, client *http.Client) (*github.User, error)
24+
ListEmails func(ctx context.Context, client *http.Client) ([]*github.UserEmail, error)
25+
ListOrganizationMemberships func(ctx context.Context, client *http.Client) ([]*github.Membership, error)
2626

2727
AllowSignups bool
2828
AllowOrganizations []string
2929
}
3030

31+
func (api *api) userAuthMethods(rw http.ResponseWriter, r *http.Request) {
32+
httpapi.Write(rw, http.StatusOK, codersdk.AuthMethods{
33+
Password: true,
34+
Github: api.GithubOAuth2Config != nil,
35+
})
36+
}
37+
3138
func (api *api) userOAuth2Github(rw http.ResponseWriter, r *http.Request) {
3239
state := httpmw.OAuth2(r)
3340

3441
oauthClient := oauth2.NewClient(r.Context(), oauth2.StaticTokenSource(state.Token))
35-
organizations, err := api.GithubOAuth2Config.ListOrganizations(r.Context(), oauthClient)
42+
memberships, err := api.GithubOAuth2Config.ListOrganizationMemberships(r.Context(), oauthClient)
3643
if err != nil {
3744
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
3845
Message: fmt.Sprintf("get authenticated github user organizations: %s", err),
3946
})
4047
return
4148
}
42-
var selectedOrganization *github.Organization
43-
for _, organization := range organizations {
44-
if organization.Login == nil {
45-
continue
46-
}
49+
var selectedMembership *github.Membership
50+
for _, membership := range memberships {
4751
for _, allowed := range api.GithubOAuth2Config.AllowOrganizations {
48-
if *organization.Login != allowed {
52+
if *membership.Organization.Login != allowed {
4953
continue
5054
}
51-
selectedOrganization = organization
55+
selectedMembership = membership
5256
break
5357
}
5458
}
55-
if selectedOrganization == nil {
59+
if selectedMembership == nil {
5660
httpapi.Write(rw, http.StatusUnauthorized, httpapi.Response{
5761
Message: fmt.Sprintf("You aren't a member of the authorized Github organizations!"),
5862
})

coderd/useroauth2_test.go renamed to coderd/userauth_test.go

+45-15
Original file line numberDiff line numberDiff line change
@@ -31,16 +31,38 @@ func (*oauth2Config) TokenSource(context.Context, *oauth2.Token) oauth2.TokenSou
3131
return nil
3232
}
3333

34+
func TestUserAuthMethods(t *testing.T) {
35+
t.Parallel()
36+
t.Run("Basic", func(t *testing.T) {
37+
client := coderdtest.New(t, nil)
38+
methods, err := client.AuthMethods(context.Background())
39+
require.NoError(t, err)
40+
require.True(t, methods.Password)
41+
require.False(t, methods.Github)
42+
})
43+
t.Run("Github", func(t *testing.T) {
44+
client := coderdtest.New(t, &coderdtest.Options{
45+
GithubOAuth2Config: &coderd.GithubOAuth2Config{},
46+
})
47+
methods, err := client.AuthMethods(context.Background())
48+
require.NoError(t, err)
49+
require.True(t, methods.Password)
50+
require.True(t, methods.Github)
51+
})
52+
}
53+
3454
func TestUserOAuth2Github(t *testing.T) {
3555
t.Parallel()
3656
t.Run("NotInAllowedOrganization", func(t *testing.T) {
3757
t.Parallel()
3858
client := coderdtest.New(t, &coderdtest.Options{
3959
GithubOAuth2Config: &coderd.GithubOAuth2Config{
4060
OAuth2Config: &oauth2Config{},
41-
ListOrganizations: func(ctx context.Context, client *http.Client) ([]*github.Organization, error) {
42-
return []*github.Organization{{
43-
Login: github.String("kyle"),
61+
ListOrganizationMemberships: func(ctx context.Context, client *http.Client) ([]*github.Membership, error) {
62+
return []*github.Membership{{
63+
Organization: &github.Organization{
64+
Login: github.String("kyle"),
65+
},
4466
}}, nil
4567
},
4668
},
@@ -55,9 +77,11 @@ func TestUserOAuth2Github(t *testing.T) {
5577
GithubOAuth2Config: &coderd.GithubOAuth2Config{
5678
OAuth2Config: &oauth2Config{},
5779
AllowOrganizations: []string{"coder"},
58-
ListOrganizations: func(ctx context.Context, client *http.Client) ([]*github.Organization, error) {
59-
return []*github.Organization{{
60-
Login: github.String("coder"),
80+
ListOrganizationMemberships: func(ctx context.Context, client *http.Client) ([]*github.Membership, error) {
81+
return []*github.Membership{{
82+
Organization: &github.Organization{
83+
Login: github.String("kyle"),
84+
},
6185
}}, nil
6286
},
6387
AuthenticatedUser: func(ctx context.Context, client *http.Client) (*github.User, error) {
@@ -81,9 +105,11 @@ func TestUserOAuth2Github(t *testing.T) {
81105
GithubOAuth2Config: &coderd.GithubOAuth2Config{
82106
OAuth2Config: &oauth2Config{},
83107
AllowOrganizations: []string{"coder"},
84-
ListOrganizations: func(ctx context.Context, client *http.Client) ([]*github.Organization, error) {
85-
return []*github.Organization{{
86-
Login: github.String("coder"),
108+
ListOrganizationMemberships: func(ctx context.Context, client *http.Client) ([]*github.Membership, error) {
109+
return []*github.Membership{{
110+
Organization: &github.Organization{
111+
Login: github.String("coder"),
112+
},
87113
}}, nil
88114
},
89115
AuthenticatedUser: func(ctx context.Context, client *http.Client) (*github.User, error) {
@@ -104,9 +130,11 @@ func TestUserOAuth2Github(t *testing.T) {
104130
OAuth2Config: &oauth2Config{},
105131
AllowOrganizations: []string{"coder"},
106132
AllowSignups: true,
107-
ListOrganizations: func(ctx context.Context, client *http.Client) ([]*github.Organization, error) {
108-
return []*github.Organization{{
109-
Login: github.String("coder"),
133+
ListOrganizationMemberships: func(ctx context.Context, client *http.Client) ([]*github.Membership, error) {
134+
return []*github.Membership{{
135+
Organization: &github.Organization{
136+
Login: github.String("coder"),
137+
},
110138
}}, nil
111139
},
112140
AuthenticatedUser: func(ctx context.Context, client *http.Client) (*github.User, error) {
@@ -129,9 +157,11 @@ func TestUserOAuth2Github(t *testing.T) {
129157
GithubOAuth2Config: &coderd.GithubOAuth2Config{
130158
OAuth2Config: &oauth2Config{},
131159
AllowOrganizations: []string{"coder"},
132-
ListOrganizations: func(ctx context.Context, client *http.Client) ([]*github.Organization, error) {
133-
return []*github.Organization{{
134-
Login: github.String("coder"),
160+
ListOrganizationMemberships: func(ctx context.Context, client *http.Client) ([]*github.Membership, error) {
161+
return []*github.Membership{{
162+
Organization: &github.Organization{
163+
Login: github.String("coder"),
164+
},
135165
}}, nil
136166
},
137167
AuthenticatedUser: func(ctx context.Context, client *http.Client) (*github.User, error) {

codersdk/users.go

+22
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,12 @@ type CreateWorkspaceRequest struct {
7777
ParameterValues []CreateParameterRequest `json:"parameter_values"`
7878
}
7979

80+
// AuthMethods contains whether authentication types are enabled or not.
81+
type AuthMethods struct {
82+
Password bool `json:"password"`
83+
Github bool `json:"github"`
84+
}
85+
8086
// HasFirstUser returns whether the first user has been created.
8187
func (c *Client) HasFirstUser(ctx context.Context) (bool, error) {
8288
res, err := c.request(ctx, http.MethodGet, "/api/v2/users/first", nil)
@@ -287,6 +293,22 @@ func (c *Client) WorkspaceByName(ctx context.Context, userID uuid.UUID, name str
287293
return workspace, json.NewDecoder(res.Body).Decode(&workspace)
288294
}
289295

296+
// AuthMethods returns types of authentication available to the user.
297+
func (c *Client) AuthMethods(ctx context.Context) (AuthMethods, error) {
298+
res, err := c.request(ctx, http.MethodGet, "/api/v2/users/authmethods", nil)
299+
if err != nil {
300+
return AuthMethods{}, err
301+
}
302+
defer res.Body.Close()
303+
304+
if res.StatusCode != http.StatusOK {
305+
return AuthMethods{}, readBodyAsError(res)
306+
}
307+
308+
var userAuth AuthMethods
309+
return userAuth, json.NewDecoder(res.Body).Decode(&userAuth)
310+
}
311+
290312
// uuidOrMe returns the provided uuid as a string if it's valid, ortherwise
291313
// `me`.
292314
func uuidOrMe(id uuid.UUID) string {

0 commit comments

Comments
 (0)