Skip to content
This repository was archived by the owner on Aug 30, 2024. It is now read-only.

Commit 2d520dc

Browse files
authored
feat: enable password changes in coder-sdk (#251)
* Allow modification of passwords and the temporary_password flag for users managed by the built-in authentication provider. * Split unit tests into separate files corresponding to the implementations.
1 parent 3253e56 commit 2d520dc

File tree

4 files changed

+176
-84
lines changed

4 files changed

+176
-84
lines changed

coder-sdk/activity_test.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package coder_test
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"net/http"
7+
"net/http/httptest"
8+
"net/url"
9+
"testing"
10+
11+
"github.com/stretchr/testify/require"
12+
13+
"cdr.dev/coder-cli/coder-sdk"
14+
)
15+
16+
func TestPushActivity(t *testing.T) {
17+
t.Parallel()
18+
19+
const source = "test"
20+
const envID = "602d377a-e6b8d763cae7561885c5f1b2"
21+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
22+
require.Equal(t, http.MethodPost, r.Method, "PushActivity is a POST")
23+
require.Equal(t, "/api/private/metrics/usage/push", r.URL.Path)
24+
25+
expected := map[string]interface{}{
26+
"source": source,
27+
"environment_id": envID,
28+
}
29+
var request map[string]interface{}
30+
err := json.NewDecoder(r.Body).Decode(&request)
31+
require.NoError(t, err, "error decoding JSON")
32+
require.EqualValues(t, expected, request, "unexpected request data")
33+
34+
w.WriteHeader(http.StatusOK)
35+
}))
36+
t.Cleanup(func() {
37+
server.Close()
38+
})
39+
40+
u, err := url.Parse(server.URL)
41+
require.NoError(t, err, "failed to parse test server URL")
42+
43+
client, err := coder.NewClient(coder.ClientOptions{
44+
BaseURL: u,
45+
Token: "SwdcSoq5Jc-0C1r8wfwm7h6h9i0RDk7JT",
46+
})
47+
require.NoError(t, err, "failed to create coder.Client")
48+
49+
err = client.PushActivity(context.Background(), source, envID)
50+
require.NoError(t, err)
51+
}

coder-sdk/client_test.go

Lines changed: 0 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -14,89 +14,6 @@ import (
1414
"cdr.dev/coder-cli/coder-sdk"
1515
)
1616

17-
func TestPushActivity(t *testing.T) {
18-
t.Parallel()
19-
20-
const source = "test"
21-
const envID = "602d377a-e6b8d763cae7561885c5f1b2"
22-
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
23-
require.Equal(t, http.MethodPost, r.Method, "PushActivity is a POST")
24-
require.Equal(t, "/api/private/metrics/usage/push", r.URL.Path)
25-
26-
expected := map[string]interface{}{
27-
"source": source,
28-
"environment_id": envID,
29-
}
30-
var request map[string]interface{}
31-
err := json.NewDecoder(r.Body).Decode(&request)
32-
require.NoError(t, err, "error decoding JSON")
33-
require.EqualValues(t, expected, request, "unexpected request data")
34-
35-
w.WriteHeader(http.StatusOK)
36-
}))
37-
t.Cleanup(func() {
38-
server.Close()
39-
})
40-
41-
u, err := url.Parse(server.URL)
42-
require.NoError(t, err, "failed to parse test server URL")
43-
44-
client, err := coder.NewClient(coder.ClientOptions{
45-
BaseURL: u,
46-
Token: "SwdcSoq5Jc-0C1r8wfwm7h6h9i0RDk7JT",
47-
})
48-
require.NoError(t, err, "failed to create coder.Client")
49-
50-
err = client.PushActivity(context.Background(), source, envID)
51-
require.NoError(t, err)
52-
}
53-
54-
func TestUsers(t *testing.T) {
55-
t.Parallel()
56-
57-
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
58-
require.Equal(t, http.MethodGet, r.Method, "Users is a GET")
59-
require.Equal(t, "/api/v0/users", r.URL.Path)
60-
61-
users := []map[string]interface{}{
62-
{
63-
"id": "default",
64-
"email": "[email protected]",
65-
"username": "root",
66-
"name": "Charlie Root",
67-
"roles": []coder.Role{coder.SiteAdmin},
68-
"temporary_password": false,
69-
"login_type": coder.LoginTypeBuiltIn,
70-
"key_regenerated_at": time.Now(),
71-
"created_at": time.Now(),
72-
"updated_at": time.Now(),
73-
},
74-
}
75-
76-
w.WriteHeader(http.StatusOK)
77-
err := json.NewEncoder(w).Encode(users)
78-
require.NoError(t, err, "error encoding JSON")
79-
}))
80-
t.Cleanup(func() {
81-
server.Close()
82-
})
83-
84-
u, err := url.Parse(server.URL)
85-
require.NoError(t, err, "failed to parse test server URL")
86-
87-
client, err := coder.NewClient(coder.ClientOptions{
88-
BaseURL: u,
89-
Token: "JcmErkJjju-KSrztst0IJX7xGJhKQPtfv",
90-
})
91-
require.NoError(t, err, "failed to create coder.Client")
92-
93-
users, err := client.Users(context.Background())
94-
require.NoError(t, err, "error getting Users")
95-
require.Len(t, users, 1, "users should return a single user")
96-
require.Equal(t, "Charlie Root", users[0].Name)
97-
require.Equal(t, "root", users[0].Username)
98-
}
99-
10017
func TestAuthentication(t *testing.T) {
10118
t.Parallel()
10219

coder-sdk/users.go

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ func (c *DefaultClient) UserByEmail(ctx context.Context, email string) (*User, e
9999
// UpdateUserReq defines a modification to the user, updating the
100100
// value of all non-nil values.
101101
type UpdateUserReq struct {
102-
// TODO(@cmoog) add update password option
102+
*UserPasswordSettings
103103
Revoked *bool `json:"revoked,omitempty"`
104104
Roles *[]Role `json:"roles,omitempty"`
105105
LoginType *LoginType `json:"login_type,omitempty"`
@@ -109,6 +109,27 @@ type UpdateUserReq struct {
109109
DotfilesGitURL *string `json:"dotfiles_git_uri,omitempty"`
110110
}
111111

112+
// UserPasswordSettings allows modification of the user's password
113+
// settings.
114+
//
115+
// These settings are only applicable to users managed using the
116+
// built-in authentication provider; users authenticating using
117+
// OAuth must change their password through the identity provider
118+
// instead.
119+
type UserPasswordSettings struct {
120+
// OldPassword is the account's current password.
121+
OldPassword string `json:"old_password,omitempty"`
122+
123+
// Password is the new password, which may be a temporary password.
124+
Password string `json:"password,omitempty"`
125+
126+
// Temporary indicates that API access should be restricted to the
127+
// password change API and a few other APIs. If set to true, Coder
128+
// will prompt the user to change their password upon their next
129+
// login through the web interface.
130+
Temporary bool `json:"temporary_password,omitempty"`
131+
}
132+
112133
// UpdateUser applyes the partial update to the given user.
113134
func (c *DefaultClient) UpdateUser(ctx context.Context, userID string, req UpdateUserReq) error {
114135
return c.requestBody(ctx, http.MethodPatch, "/api/v0/users/"+userID, req, nil)

coder-sdk/users_test.go

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
package coder_test
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"net/http"
7+
"net/http/httptest"
8+
"net/url"
9+
"testing"
10+
"time"
11+
12+
"github.com/stretchr/testify/require"
13+
14+
"cdr.dev/coder-cli/coder-sdk"
15+
)
16+
17+
func TestUsers(t *testing.T) {
18+
t.Parallel()
19+
20+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
21+
require.Equal(t, http.MethodGet, r.Method, "Users is a GET")
22+
require.Equal(t, "/api/v0/users", r.URL.Path)
23+
24+
users := []map[string]interface{}{
25+
{
26+
"id": "default",
27+
"email": "[email protected]",
28+
"username": "root",
29+
"name": "Charlie Root",
30+
"roles": []coder.Role{coder.SiteAdmin},
31+
"temporary_password": false,
32+
"login_type": coder.LoginTypeBuiltIn,
33+
"key_regenerated_at": time.Now(),
34+
"created_at": time.Now(),
35+
"updated_at": time.Now(),
36+
},
37+
}
38+
39+
w.WriteHeader(http.StatusOK)
40+
err := json.NewEncoder(w).Encode(users)
41+
require.NoError(t, err, "error encoding JSON")
42+
}))
43+
t.Cleanup(func() {
44+
server.Close()
45+
})
46+
47+
u, err := url.Parse(server.URL)
48+
require.NoError(t, err, "failed to parse test server URL")
49+
50+
client, err := coder.NewClient(coder.ClientOptions{
51+
BaseURL: u,
52+
Token: "JcmErkJjju-KSrztst0IJX7xGJhKQPtfv",
53+
})
54+
require.NoError(t, err, "failed to create coder.Client")
55+
56+
users, err := client.Users(context.Background())
57+
require.NoError(t, err, "error getting Users")
58+
require.Len(t, users, 1, "users should return a single user")
59+
require.Equal(t, "Charlie Root", users[0].Name)
60+
require.Equal(t, "root", users[0].Username)
61+
}
62+
63+
func TestUserUpdatePassword(t *testing.T) {
64+
t.Parallel()
65+
66+
server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
67+
require.Equal(t, http.MethodPatch, r.Method, "Users is a PATCH")
68+
require.Equal(t, "/api/v0/users/me", r.URL.Path)
69+
70+
expected := map[string]interface{}{
71+
"old_password": "vt9g9rxsptrq",
72+
"password": "wmf39jw2f7pk",
73+
}
74+
var request map[string]interface{}
75+
err := json.NewDecoder(r.Body).Decode(&request)
76+
require.NoError(t, err, "error decoding JSON")
77+
require.EqualValues(t, expected, request, "unexpected request data")
78+
79+
w.WriteHeader(http.StatusOK)
80+
}))
81+
t.Cleanup(func() {
82+
server.Close()
83+
})
84+
85+
u, err := url.Parse(server.URL)
86+
require.NoError(t, err, "failed to parse test server URL")
87+
88+
client, err := coder.NewClient(coder.ClientOptions{
89+
BaseURL: u,
90+
HTTPClient: server.Client(),
91+
Token: "JcmErkJjju-KSrztst0IJX7xGJhKQPtfv",
92+
})
93+
require.NoError(t, err, "failed to create coder.Client")
94+
95+
err = client.UpdateUser(context.Background(), "me", coder.UpdateUserReq{
96+
UserPasswordSettings: &coder.UserPasswordSettings{
97+
OldPassword: "vt9g9rxsptrq",
98+
Password: "wmf39jw2f7pk",
99+
Temporary: false,
100+
},
101+
})
102+
require.NoError(t, err, "error when updating password")
103+
}

0 commit comments

Comments
 (0)