mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
fix: Add route for user to change own password (#1812)
This commit is contained in:
+1
-1
@@ -40,7 +40,7 @@ func (api *API) assignableOrgRoles(rw http.ResponseWriter, r *http.Request) {
|
||||
func (api *API) checkPermissions(rw http.ResponseWriter, r *http.Request) {
|
||||
user := httpmw.UserParam(r)
|
||||
|
||||
if !api.Authorize(rw, r, rbac.ActionRead, rbac.ResourceUser.WithOwner(user.ID.String())) {
|
||||
if !api.Authorize(rw, r, rbac.ActionRead, rbac.ResourceUser.WithID(user.ID.String())) {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -121,3 +121,19 @@ func hashWithSaltAndIter(password string, salt []byte, iter int) string {
|
||||
|
||||
return fmt.Sprintf("$%s$%d$%s$%s", hashScheme, iter, encSalt, encHash)
|
||||
}
|
||||
|
||||
// Validate checks that the plain text password meets the minimum password requirements.
|
||||
// It returns properly formatted errors for detailed form validation on the client.
|
||||
func Validate(password string) error {
|
||||
const (
|
||||
minLength = 8
|
||||
maxLength = 64
|
||||
)
|
||||
if len(password) < minLength {
|
||||
return xerrors.Errorf("Password must be at least %d characters.", minLength)
|
||||
}
|
||||
if len(password) > maxLength {
|
||||
return xerrors.Errorf("Password must be no more than %d characters.", maxLength)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -355,6 +355,7 @@ func (api *API) putUserStatus(status database.UserStatus) func(rw http.ResponseW
|
||||
func (api *API) putUserPassword(rw http.ResponseWriter, r *http.Request) {
|
||||
var (
|
||||
user = httpmw.UserParam(r)
|
||||
apiKey = httpmw.APIKey(r)
|
||||
params codersdk.UpdateUserPasswordRequest
|
||||
)
|
||||
|
||||
@@ -366,6 +367,43 @@ func (api *API) putUserPassword(rw http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
err := userpassword.Validate(params.Password)
|
||||
if err != nil {
|
||||
httpapi.Write(rw, http.StatusBadRequest, httpapi.Response{
|
||||
Errors: []httpapi.Error{
|
||||
{
|
||||
Field: "password",
|
||||
Detail: err.Error(),
|
||||
},
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// we want to require old_password field if the user is changing their
|
||||
// own password. This is to prevent a compromised session from being able
|
||||
// to change password and lock out the user.
|
||||
if user.ID == apiKey.UserID {
|
||||
ok, err := userpassword.Compare(string(user.HashedPassword), params.OldPassword)
|
||||
if err != nil {
|
||||
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
|
||||
Message: fmt.Sprintf("compare user password: %s", err.Error()),
|
||||
})
|
||||
return
|
||||
}
|
||||
if !ok {
|
||||
httpapi.Write(rw, http.StatusBadRequest, httpapi.Response{
|
||||
Errors: []httpapi.Error{
|
||||
{
|
||||
Field: "old_password",
|
||||
Detail: "Old password is incorrect.",
|
||||
},
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
hashedPassword, err := userpassword.Hash(params.Password)
|
||||
if err != nil {
|
||||
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
|
||||
|
||||
@@ -324,6 +324,36 @@ func TestUpdateUserPassword(t *testing.T) {
|
||||
})
|
||||
require.NoError(t, err, "member should login successfully with the new password")
|
||||
})
|
||||
t.Run("MemberCanUpdateOwnPassword", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t, nil)
|
||||
admin := coderdtest.CreateFirstUser(t, client)
|
||||
member := coderdtest.CreateAnotherUser(t, client, admin.OrganizationID)
|
||||
err := member.UpdateUserPassword(context.Background(), "me", codersdk.UpdateUserPasswordRequest{
|
||||
OldPassword: "testpass",
|
||||
Password: "newpassword",
|
||||
})
|
||||
require.NoError(t, err, "member should be able to update own password")
|
||||
})
|
||||
t.Run("MemberCantUpdateOwnPasswordWithoutOldPassword", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t, nil)
|
||||
admin := coderdtest.CreateFirstUser(t, client)
|
||||
member := coderdtest.CreateAnotherUser(t, client, admin.OrganizationID)
|
||||
err := member.UpdateUserPassword(context.Background(), "me", codersdk.UpdateUserPasswordRequest{
|
||||
Password: "newpassword",
|
||||
})
|
||||
require.Error(t, err, "member should not be able to update own password without providing old password")
|
||||
})
|
||||
t.Run("AdminCantUpdateOwnPasswordWithoutOldPassword", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t, nil)
|
||||
_ = coderdtest.CreateFirstUser(t, client)
|
||||
err := client.UpdateUserPassword(context.Background(), "me", codersdk.UpdateUserPasswordRequest{
|
||||
Password: "newpassword",
|
||||
})
|
||||
require.Error(t, err, "admin should not be able to update own password without providing old password")
|
||||
})
|
||||
}
|
||||
|
||||
func TestGrantRoles(t *testing.T) {
|
||||
|
||||
+2
-1
@@ -65,7 +65,8 @@ type UpdateUserProfileRequest struct {
|
||||
}
|
||||
|
||||
type UpdateUserPasswordRequest struct {
|
||||
Password string `json:"password" validate:"required"`
|
||||
OldPassword string `json:"old_password" validate:""`
|
||||
Password string `json:"password" validate:"required"`
|
||||
}
|
||||
|
||||
type UpdateRoles struct {
|
||||
|
||||
@@ -12,7 +12,7 @@ export interface AgentGitSSHKey {
|
||||
readonly private_key: string
|
||||
}
|
||||
|
||||
// From codersdk/users.go:151:6
|
||||
// From codersdk/users.go:152:6
|
||||
export interface AuthMethods {
|
||||
readonly password: boolean
|
||||
readonly github: boolean
|
||||
@@ -44,7 +44,7 @@ export interface CreateFirstUserResponse {
|
||||
readonly organization_id: string
|
||||
}
|
||||
|
||||
// From codersdk/users.go:146:6
|
||||
// From codersdk/users.go:147:6
|
||||
export interface CreateOrganizationRequest {
|
||||
readonly name: string
|
||||
}
|
||||
@@ -100,7 +100,7 @@ export interface CreateWorkspaceRequest {
|
||||
readonly parameter_values?: CreateParameterRequest[]
|
||||
}
|
||||
|
||||
// From codersdk/users.go:142:6
|
||||
// From codersdk/users.go:143:6
|
||||
export interface GenerateAPIKeyResponse {
|
||||
readonly key: string
|
||||
}
|
||||
@@ -118,13 +118,13 @@ export interface GoogleInstanceIdentityToken {
|
||||
readonly json_web_token: string
|
||||
}
|
||||
|
||||
// From codersdk/users.go:131:6
|
||||
// From codersdk/users.go:132:6
|
||||
export interface LoginWithPasswordRequest {
|
||||
readonly email: string
|
||||
readonly password: string
|
||||
}
|
||||
|
||||
// From codersdk/users.go:137:6
|
||||
// From codersdk/users.go:138:6
|
||||
export interface LoginWithPasswordResponse {
|
||||
readonly session_token: string
|
||||
}
|
||||
@@ -276,13 +276,14 @@ export interface UpdateActiveTemplateVersion {
|
||||
readonly id: string
|
||||
}
|
||||
|
||||
// From codersdk/users.go:71:6
|
||||
// From codersdk/users.go:72:6
|
||||
export interface UpdateRoles {
|
||||
readonly roles: string[]
|
||||
}
|
||||
|
||||
// From codersdk/users.go:67:6
|
||||
export interface UpdateUserPasswordRequest {
|
||||
readonly old_password: string
|
||||
readonly password: string
|
||||
}
|
||||
|
||||
@@ -319,13 +320,13 @@ export interface User {
|
||||
readonly roles: Role[]
|
||||
}
|
||||
|
||||
// From codersdk/users.go:96:6
|
||||
// From codersdk/users.go:97:6
|
||||
export interface UserAuthorization {
|
||||
readonly object: UserAuthorizationObject
|
||||
readonly action: string
|
||||
}
|
||||
|
||||
// From codersdk/users.go:112:6
|
||||
// From codersdk/users.go:113:6
|
||||
export interface UserAuthorizationObject {
|
||||
readonly resource_type: string
|
||||
readonly owner_id?: string
|
||||
@@ -333,15 +334,15 @@ export interface UserAuthorizationObject {
|
||||
readonly resource_id?: string
|
||||
}
|
||||
|
||||
// From codersdk/users.go:85:6
|
||||
// From codersdk/users.go:86:6
|
||||
export interface UserAuthorizationRequest {
|
||||
readonly checks: Record<string, UserAuthorization>
|
||||
}
|
||||
|
||||
// From codersdk/users.go:80:6
|
||||
// From codersdk/users.go:81:6
|
||||
export type UserAuthorizationResponse = Record<string, boolean>
|
||||
|
||||
// From codersdk/users.go:75:6
|
||||
// From codersdk/users.go:76:6
|
||||
export interface UserRoles {
|
||||
readonly roles: string[]
|
||||
readonly organization_roles: Record<string, string[]>
|
||||
|
||||
Reference in New Issue
Block a user