Skip to content

Commit 57bb108

Browse files
feat: Add update user password endpoint (#1310)
1 parent a2be7c0 commit 57bb108

File tree

12 files changed

+160
-26
lines changed

12 files changed

+160
-26
lines changed

coderd/coderd.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,10 @@ func New(options *Options) (http.Handler, func()) {
240240
r.Get("/", api.userByName)
241241
r.Put("/profile", api.putUserProfile)
242242
r.Put("/suspend", api.putUserSuspend)
243+
r.Route("/password", func(r chi.Router) {
244+
r.Use(httpmw.WithRBACObject(rbac.ResourceUserPasswordRole))
245+
r.Put("/", authorize(api.putUserPassword, rbac.ActionUpdate))
246+
})
243247
r.Get("/organizations", api.organizationsByUser)
244248
r.Post("/organizations", api.postOrganizationsByUser)
245249
// These roles apply to the site wide permissions.

coderd/coderdtest/coderdtest.go

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -174,21 +174,22 @@ func NewProvisionerDaemon(t *testing.T, client *codersdk.Client) io.Closer {
174174
return closer
175175
}
176176

177+
var FirstUserParams = codersdk.CreateFirstUserRequest{
178+
Email: "testuser@coder.com",
179+
Username: "testuser",
180+
Password: "testpass",
181+
OrganizationName: "testorg",
182+
}
183+
177184
// CreateFirstUser creates a user with preset credentials and authenticates
178185
// with the passed in codersdk client.
179186
func CreateFirstUser(t *testing.T, client *codersdk.Client) codersdk.CreateFirstUserResponse {
180-
req := codersdk.CreateFirstUserRequest{
181-
Email: "testuser@coder.com",
182-
Username: "testuser",
183-
Password: "testpass",
184-
OrganizationName: "testorg",
185-
}
186-
resp, err := client.CreateFirstUser(context.Background(), req)
187+
resp, err := client.CreateFirstUser(context.Background(), FirstUserParams)
187188
require.NoError(t, err)
188189

189190
login, err := client.LoginWithPassword(context.Background(), codersdk.LoginWithPasswordRequest{
190-
Email: req.Email,
191-
Password: req.Password,
191+
Email: FirstUserParams.Email,
192+
Password: FirstUserParams.Password,
192193
})
193194
require.NoError(t, err)
194195
client.SessionToken = login.SessionToken

coderd/database/databasefake/databasefake.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1314,6 +1314,21 @@ func (q *fakeQuerier) UpdateUserStatus(_ context.Context, arg database.UpdateUse
13141314
return database.User{}, sql.ErrNoRows
13151315
}
13161316

1317+
func (q *fakeQuerier) UpdateUserHashedPassword(_ context.Context, arg database.UpdateUserHashedPasswordParams) error {
1318+
q.mutex.Lock()
1319+
defer q.mutex.Unlock()
1320+
1321+
for i, user := range q.users {
1322+
if user.ID != arg.ID {
1323+
continue
1324+
}
1325+
user.HashedPassword = arg.HashedPassword
1326+
q.users[i] = user
1327+
return nil
1328+
}
1329+
return sql.ErrNoRows
1330+
}
1331+
13171332
func (q *fakeQuerier) InsertWorkspace(_ context.Context, arg database.InsertWorkspaceParams) (database.Workspace, error) {
13181333
q.mutex.Lock()
13191334
defer q.mutex.Unlock()

coderd/database/querier.go

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/database/queries.sql.go

Lines changed: 19 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/database/queries/users.sql

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,14 @@ WHERE
5959
id = @id
6060
RETURNING *;
6161

62+
-- name: UpdateUserHashedPassword :exec
63+
UPDATE
64+
users
65+
SET
66+
hashed_password = $2
67+
WHERE
68+
id = $1;
69+
6270
-- name: GetUsers :many
6371
SELECT
6472
*
@@ -133,4 +141,4 @@ FROM
133141
LEFT JOIN organization_members
134142
ON id = user_id
135143
WHERE
136-
id = @user_id;
144+
id = @user_id;

coderd/httpmw/userparam.go

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -76,14 +76,6 @@ func ExtractUserParam(db database.Store) func(http.Handler) http.Handler {
7676
}
7777
}
7878

79-
apiKey := APIKey(r)
80-
if apiKey.UserID != user.ID {
81-
httpapi.Write(rw, http.StatusBadRequest, httpapi.Response{
82-
Message: "getting non-personal users isn't supported yet",
83-
})
84-
return
85-
}
86-
8779
ctx := context.WithValue(r.Context(), userParamContextKey{}, user)
8880
next.ServeHTTP(rw, r.WithContext(ctx))
8981
})

coderd/rbac/object.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ var (
2424
Type: "user_role",
2525
}
2626

27+
ResourceUserPasswordRole = Object{
28+
Type: "user_password",
29+
}
30+
2731
// ResourceWildcard represents all resource types
2832
ResourceWildcard = Object{
2933
Type: WildcardSymbol,

coderd/users.go

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,36 @@ func (api *api) putUserSuspend(rw http.ResponseWriter, r *http.Request) {
360360
httpapi.Write(rw, http.StatusOK, convertUser(suspendedUser, organizations))
361361
}
362362

363+
func (api *api) putUserPassword(rw http.ResponseWriter, r *http.Request) {
364+
var (
365+
user = httpmw.UserParam(r)
366+
params codersdk.UpdateUserPasswordRequest
367+
)
368+
if !httpapi.Read(rw, r, &params) {
369+
return
370+
}
371+
372+
hashedPassword, err := userpassword.Hash(params.Password)
373+
if err != nil {
374+
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
375+
Message: fmt.Sprintf("hash password: %s", err.Error()),
376+
})
377+
return
378+
}
379+
err = api.Database.UpdateUserHashedPassword(r.Context(), database.UpdateUserHashedPasswordParams{
380+
ID: user.ID,
381+
HashedPassword: []byte(hashedPassword),
382+
})
383+
if err != nil {
384+
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
385+
Message: fmt.Sprintf("put user password: %s", err.Error()),
386+
})
387+
return
388+
}
389+
390+
httpapi.Write(rw, http.StatusNoContent, nil)
391+
}
392+
363393
func (api *api) userRoles(rw http.ResponseWriter, r *http.Request) {
364394
user := httpmw.UserParam(r)
365395

@@ -577,7 +607,6 @@ func (api *api) postLogin(rw http.ResponseWriter, r *http.Request) {
577607
}
578608

579609
// If the user doesn't exist, it will be a default struct.
580-
581610
equal, err := userpassword.Compare(string(user.HashedPassword), loginWithPassword.Password)
582611
if err != nil {
583612
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{

coderd/users_test.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,44 @@ func TestUpdateUserProfile(t *testing.T) {
287287
})
288288
}
289289

290+
func TestUpdateUserPassword(t *testing.T) {
291+
t.Parallel()
292+
293+
t.Run("MemberCantUpdateAdminPassword", func(t *testing.T) {
294+
t.Parallel()
295+
client := coderdtest.New(t, nil)
296+
admin := coderdtest.CreateFirstUser(t, client)
297+
member := coderdtest.CreateAnotherUser(t, client, admin.OrganizationID)
298+
err := member.UpdateUserPassword(context.Background(), admin.UserID, codersdk.UpdateUserPasswordRequest{
299+
Password: "newpassword",
300+
})
301+
require.Error(t, err, "member should not be able to update admin password")
302+
})
303+
304+
t.Run("AdminCanUpdateMemberPassword", func(t *testing.T) {
305+
t.Parallel()
306+
client := coderdtest.New(t, nil)
307+
admin := coderdtest.CreateFirstUser(t, client)
308+
member, err := client.CreateUser(context.Background(), codersdk.CreateUserRequest{
309+
Email: "coder@coder.com",
310+
Username: "coder",
311+
Password: "password",
312+
OrganizationID: admin.OrganizationID,
313+
})
314+
require.NoError(t, err, "create member")
315+
err = client.UpdateUserPassword(context.Background(), member.ID, codersdk.UpdateUserPasswordRequest{
316+
Password: "newpassword",
317+
})
318+
require.NoError(t, err, "admin should be able to update member password")
319+
// Check if the member can login using the new password
320+
_, err = client.LoginWithPassword(context.Background(), codersdk.LoginWithPasswordRequest{
321+
Email: "coder@coder.com",
322+
Password: "newpassword",
323+
})
324+
require.NoError(t, err, "member should login successfully with the new password")
325+
})
326+
}
327+
290328
func TestGrantRoles(t *testing.T) {
291329
t.Parallel()
292330
t.Run("UpdateIncorrectRoles", func(t *testing.T) {

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