Skip to content

fix(coderd): improve password update logic #15210

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Oct 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion coderd/users.go
Original file line number Diff line number Diff line change
Expand Up @@ -1018,6 +1018,7 @@ func (api *API) putUserPassword(rw http.ResponseWriter, r *http.Request) {
ctx = r.Context()
user = httpmw.UserParam(r)
params codersdk.UpdateUserPasswordRequest
apiKey = httpmw.APIKey(r)
auditor = *api.Auditor.Load()
aReq, commitAudit = audit.InitRequest[database.User](rw, &audit.RequestParams{
Audit: auditor,
Expand Down Expand Up @@ -1045,6 +1046,14 @@ func (api *API) putUserPassword(rw http.ResponseWriter, r *http.Request) {
return
}

// A user need to put its own password to update it
if apiKey.UserID == user.ID && params.OldPassword == "" {
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
Message: "Old password is required.",
})
return
}

err := userpassword.Validate(params.Password)
if err != nil {
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
Expand All @@ -1059,7 +1068,6 @@ func (api *API) putUserPassword(rw http.ResponseWriter, r *http.Request) {
return
}

// admins can change passwords without sending old_password
if params.OldPassword != "" {
// if they send something let's validate it
ok, err := userpassword.Compare(string(user.HashedPassword), params.OldPassword)
Expand Down
34 changes: 31 additions & 3 deletions coderd/users_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1056,6 +1056,31 @@ func TestUpdateUserPassword(t *testing.T) {
require.NoError(t, err, "member should login successfully with the new password")
})

t.Run("AuditorCantUpdateOtherUserPassword", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
owner := coderdtest.CreateFirstUser(t, client)

auditor, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID, rbac.RoleAuditor())

ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()

member, err := client.CreateUserWithOrgs(ctx, codersdk.CreateUserRequestWithOrgs{
Email: "coder@coder.com",
Username: "coder",
Password: "SomeStrongPassword!",
OrganizationIDs: []uuid.UUID{owner.OrganizationID},
})
require.NoError(t, err, "create member")

err = auditor.UpdateUserPassword(ctx, member.ID.String(), codersdk.UpdateUserPasswordRequest{
Password: "SomeNewStrongPassword!",
})
require.Error(t, err, "auditor should not be able to update member password")
require.ErrorContains(t, err, "unexpected status code 404: Resource not found or you do not have access to this resource")
})

t.Run("MemberCanUpdateOwnPassword", func(t *testing.T) {
t.Parallel()
auditor := audit.NewMock()
Expand Down Expand Up @@ -1097,6 +1122,7 @@ func TestUpdateUserPassword(t *testing.T) {
Password: "newpassword",
})
require.Error(t, err, "member should not be able to update own password without providing old password")
require.ErrorContains(t, err, "Old password is required.")
})

t.Run("AuditorCantTellIfPasswordIncorrect", func(t *testing.T) {
Expand Down Expand Up @@ -1133,7 +1159,7 @@ func TestUpdateUserPassword(t *testing.T) {
require.Equal(t, int32(http.StatusNotFound), auditor.AuditLogs()[numLogs-1].StatusCode)
})

t.Run("AdminCanUpdateOwnPasswordWithoutOldPassword", func(t *testing.T) {
t.Run("AdminCantUpdateOwnPasswordWithoutOldPassword", func(t *testing.T) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed this test to reflect the logic change here.

IMO even an admin should not be able to change its own password without giving the old password - also the UI currently forces users (admin too) to have the old password.

t.Parallel()
auditor := audit.NewMock()
client := coderdtest.New(t, &coderdtest.Options{Auditor: auditor})
Expand All @@ -1150,7 +1176,8 @@ func TestUpdateUserPassword(t *testing.T) {
})
numLogs++ // add an audit log for user update

require.NoError(t, err, "admin should be able to update own password without providing old password")
require.Error(t, err, "admin should not be able to update own password without providing old password")
require.ErrorContains(t, err, "Old password is required.")

require.Len(t, auditor.AuditLogs(), numLogs)
require.Equal(t, database.AuditActionWrite, auditor.AuditLogs()[numLogs-1].Action)
Expand All @@ -1170,7 +1197,8 @@ func TestUpdateUserPassword(t *testing.T) {
require.NoError(t, err)

err = client.UpdateUserPassword(ctx, "me", codersdk.UpdateUserPasswordRequest{
Password: "MyNewSecurePassword!",
OldPassword: "SomeSecurePassword!",
Password: "MyNewSecurePassword!",
})
require.NoError(t, err)

Expand Down
Loading