feat(authctl): support password reset
This commit is contained in:
@@ -22,6 +22,7 @@ type AuthRepository interface {
|
||||
FindUserByUsername(context.Context, string) (domain.User, error)
|
||||
ProvisionUser(context.Context, domain.User) (domain.User, error)
|
||||
ProvisionDevice(context.Context, domain.Device) (domain.Device, error)
|
||||
ResetUserPassword(context.Context, string, string, time.Time) error
|
||||
SetUserActive(context.Context, string, bool, time.Time) error
|
||||
SetDeviceEnabled(context.Context, string, bool, time.Time) error
|
||||
CreateAdminSession(
|
||||
|
||||
@@ -74,6 +74,11 @@ type SetUserActiveCommand struct {
|
||||
Active bool
|
||||
}
|
||||
|
||||
type ResetUserPasswordCommand struct {
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
|
||||
type SetDeviceEnabledCommand struct {
|
||||
DeviceID string
|
||||
Enabled bool
|
||||
@@ -396,6 +401,49 @@ func (s *AuthService) SetUserActive(
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *AuthService) ResetUserPassword(
|
||||
ctx context.Context,
|
||||
command ResetUserPasswordCommand,
|
||||
) error {
|
||||
username := domain.NormalizeUsername(command.Username)
|
||||
user, err := s.repository.FindUserByUsername(ctx, username)
|
||||
if err != nil {
|
||||
return wrapAuthRepositoryError(err)
|
||||
}
|
||||
if err := domain.ValidateUserInput(
|
||||
username,
|
||||
command.Password,
|
||||
user.Role,
|
||||
); err != nil {
|
||||
var validation *domain.AuthValidationError
|
||||
if errors.As(err, &validation) {
|
||||
return invalidError(
|
||||
"AUTH_VALIDATION_FAILED",
|
||||
"authentication input is invalid",
|
||||
validation.Fields,
|
||||
)
|
||||
}
|
||||
return invalidError(
|
||||
"AUTH_VALIDATION_FAILED",
|
||||
"authentication input is invalid",
|
||||
map[string]string{},
|
||||
)
|
||||
}
|
||||
passwordHash, err := s.passwords.Hash(command.Password)
|
||||
if err != nil {
|
||||
return internalAuthFailure(err)
|
||||
}
|
||||
if err := s.repository.ResetUserPassword(
|
||||
ctx,
|
||||
username,
|
||||
passwordHash,
|
||||
s.clock.Now().UTC(),
|
||||
); err != nil {
|
||||
return wrapAuthRepositoryError(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *AuthService) SetDeviceEnabled(
|
||||
ctx context.Context,
|
||||
command SetDeviceEnabledCommand,
|
||||
|
||||
@@ -213,6 +213,41 @@ func TestAuthServiceProvisionsHashedCredentials(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthServiceResetsExistingUserPassword(t *testing.T) {
|
||||
fixture := newAuthServiceFixture(t)
|
||||
fixture.repository.users["admin"] = domain.User{
|
||||
ID: "user-admin",
|
||||
Username: "admin",
|
||||
PasswordHash: "hashed:old-password",
|
||||
Role: domain.UserRoleAdmin,
|
||||
IsActive: true,
|
||||
}
|
||||
|
||||
err := fixture.service.ResetUserPassword(
|
||||
context.Background(),
|
||||
ResetUserPasswordCommand{
|
||||
Username: " ADMIN ",
|
||||
Password: "admin",
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("ResetUserPassword() error = %v", err)
|
||||
}
|
||||
if fixture.repository.users["admin"].PasswordHash != "hashed:admin" {
|
||||
t.Fatalf(
|
||||
"password hash = %q",
|
||||
fixture.repository.users["admin"].PasswordHash,
|
||||
)
|
||||
}
|
||||
|
||||
err = fixture.service.ResetUserPassword(
|
||||
context.Background(),
|
||||
ResetUserPasswordCommand{Username: "missing", Password: "admin"},
|
||||
)
|
||||
assertAuthCode(t, err, "RESOURCE_NOT_FOUND")
|
||||
}
|
||||
|
||||
func TestAuthServiceChangesUserAndDeviceStatus(t *testing.T) {
|
||||
fixture := newAuthServiceFixture(t)
|
||||
if err := fixture.service.SetUserActive(
|
||||
@@ -409,6 +444,21 @@ func (repository *fakeAuthRepository) ProvisionDevice(
|
||||
return device, nil
|
||||
}
|
||||
|
||||
func (repository *fakeAuthRepository) ResetUserPassword(
|
||||
_ context.Context,
|
||||
username string,
|
||||
passwordHash string,
|
||||
_ time.Time,
|
||||
) error {
|
||||
user, found := repository.users[username]
|
||||
if !found {
|
||||
return ErrRepositoryNotFound
|
||||
}
|
||||
user.PasswordHash = passwordHash
|
||||
repository.users[username] = user
|
||||
return nil
|
||||
}
|
||||
|
||||
func (repository *fakeAuthRepository) SetUserActive(
|
||||
_ context.Context,
|
||||
username string,
|
||||
|
||||
Reference in New Issue
Block a user