generated from VLADIMIR/template
add change password
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
package users_service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"evening_detective_server/internal/repos"
|
||||
"evening_detective_server/internal/repos/users_repo"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
func TestChangeUserPassword(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
name, role, password string
|
||||
actor, target int
|
||||
want error
|
||||
}{
|
||||
{"success", "admin", " новый пароль ", 1, 2, nil},
|
||||
{"other admin", "admin", "123456789012", 1, 3, nil},
|
||||
{"removed role", "user", "123456789012", 1, 2, ErrPasswordChangeForbidden},
|
||||
{"missing actor", "admin", "123456789012", 4, 2, ErrPasswordChangeForbidden},
|
||||
{"self", "admin", "123456789012", 1, 1, ErrInvalidPasswordChange},
|
||||
{"bad id", "admin", "123456789012", 1, -1, ErrInvalidPasswordChange},
|
||||
{"missing target", "admin", "123456789012", 1, 4, users_repo.ErrUserNotFound},
|
||||
{"empty", "admin", "", 1, 2, ErrInvalidPasswordChange},
|
||||
{"short unicode", "admin", "яяббввггдде", 1, 2, ErrInvalidPasswordChange},
|
||||
{"too many bytes", "admin", strings.Repeat("я", 37), 1, 2, ErrInvalidPasswordChange},
|
||||
{"72 bytes", "admin", strings.Repeat("я", 36), 1, 2, nil},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
r := newFakeRepo()
|
||||
old, err := bcrypt.GenerateFromPassword([]byte("old password"), bcrypt.MinCost)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for id, role := range []string{tc.role, "user", "admin"} {
|
||||
u := &repos.User{ID: id + 1, Email: role + string(rune('0'+id)), Roles: []string{role}, PasswordHash: string(old)}
|
||||
r.byID[u.ID] = u
|
||||
r.users[u.Email] = u
|
||||
}
|
||||
// Nil JWT, token repository and mail sender ensure this operation does not use them.
|
||||
s := &UsersService{usersRepo: r}
|
||||
err = s.ChangeUserPassword(context.Background(), tc.actor, tc.target, tc.password)
|
||||
if !errors.Is(err, tc.want) {
|
||||
t.Fatalf("error = %v, want %v", err, tc.want)
|
||||
}
|
||||
for id, u := range r.byID {
|
||||
if tc.want == nil && id == tc.target {
|
||||
if bcrypt.CompareHashAndPassword([]byte(u.PasswordHash), []byte(tc.password)) != nil {
|
||||
t.Fatal("new password rejected")
|
||||
}
|
||||
if bcrypt.CompareHashAndPassword([]byte(u.PasswordHash), []byte("old password")) == nil {
|
||||
t.Fatal("old password accepted")
|
||||
}
|
||||
} else if u.PasswordHash != string(old) {
|
||||
t.Fatal("unrelated or rejected update")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
@@ -372,3 +373,34 @@ func (s *UsersService) DeleteUserRole(
|
||||
}
|
||||
return s.usersRepo.DeleteUserRole(ctx, userId, role)
|
||||
}
|
||||
|
||||
var (
|
||||
ErrPasswordChangeForbidden = errors.New("Изменять чужой пароль может только администратор")
|
||||
ErrInvalidPasswordChange = errors.New("Укажите чужой положительный ID и пароль от 12 символов до 72 байт")
|
||||
)
|
||||
|
||||
// ChangeUserPassword сохраняет только хеш; действующие сеансы не изменяются.
|
||||
func (s *UsersService) ChangeUserPassword(ctx context.Context, actorID, userID int, password string) error {
|
||||
actor, err := s.usersRepo.GetUserByID(ctx, actorID)
|
||||
if errors.Is(err, users_repo.ErrUserNotFound) {
|
||||
return ErrPasswordChangeForbidden
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !slices.Contains(actor.Roles, roles.Admin) {
|
||||
return ErrPasswordChangeForbidden
|
||||
}
|
||||
if userID <= 0 || userID == actorID || !utf8.ValidString(password) || utf8.RuneCountInString(password) < 12 || len(password) > 72 {
|
||||
return ErrInvalidPasswordChange
|
||||
}
|
||||
user, err := s.usersRepo.GetUserByID(ctx, userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.usersRepo.UpdateUserPassword(ctx, user.Email, string(hash))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user