feat(authctl): support password reset
This commit is contained in:
@@ -46,12 +46,12 @@ func run(
|
||||
return err
|
||||
}
|
||||
var plainPassword string
|
||||
if command == "create-user" {
|
||||
if command == "create-user" || command == "reset-password" {
|
||||
var exists bool
|
||||
plainPassword, exists = lookup(passwordEnvironment)
|
||||
if !exists || plainPassword == "" {
|
||||
return errors.New(
|
||||
passwordEnvironment + " must be set for create-user",
|
||||
passwordEnvironment + " must be set for " + command,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -133,6 +133,22 @@ func run(
|
||||
result.DeviceToken,
|
||||
)
|
||||
return err
|
||||
case "reset-password":
|
||||
if err := service.ResetUserPassword(
|
||||
ctx,
|
||||
usecase.ResetUserPasswordCommand{
|
||||
Username: value,
|
||||
Password: plainPassword,
|
||||
},
|
||||
); err != nil {
|
||||
return publicProvisioningError(err)
|
||||
}
|
||||
_, err = fmt.Fprintf(
|
||||
output,
|
||||
"username=%s password_updated=true\n",
|
||||
domain.NormalizeUsername(value),
|
||||
)
|
||||
return err
|
||||
case "enable-user", "disable-user":
|
||||
active := command == "enable-user"
|
||||
if err := service.SetUserActive(
|
||||
@@ -182,7 +198,7 @@ func parseArguments(
|
||||
}
|
||||
if len(arguments) == 2 {
|
||||
switch arguments[0] {
|
||||
case "enable-user", "disable-user",
|
||||
case "reset-password", "enable-user", "disable-user",
|
||||
"enable-device", "disable-device":
|
||||
return arguments[0], arguments[1], "", nil
|
||||
}
|
||||
@@ -198,6 +214,7 @@ func parseArguments(
|
||||
}
|
||||
return "", "", "", errors.New(
|
||||
"usage: authctl create-user <ADMIN|BUYER> <username> | " +
|
||||
"authctl reset-password <username> | " +
|
||||
"authctl create-device <name> | " +
|
||||
"authctl <enable-user|disable-user> <username> | " +
|
||||
"authctl <enable-device|disable-device> <device-id>",
|
||||
|
||||
@@ -64,6 +64,48 @@ func TestRunCreatesUserWithoutExposingPassword(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunResetsPasswordWithoutExposingIt(t *testing.T) {
|
||||
databasePath := migratedDatabase(t)
|
||||
var output bytes.Buffer
|
||||
if err := run(
|
||||
[]string{"create-user", "ADMIN", "Admin"},
|
||||
testLookup(databasePath, "old-password"),
|
||||
&output,
|
||||
); err != nil {
|
||||
t.Fatalf("create user: %v", err)
|
||||
}
|
||||
output.Reset()
|
||||
|
||||
err := run(
|
||||
[]string{"reset-password", "ADMIN"},
|
||||
testLookup(databasePath, "admin"),
|
||||
&output,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("run(reset-password) error = %v", err)
|
||||
}
|
||||
if strings.Contains(output.String(), "password=admin") ||
|
||||
output.String() != "username=admin password_updated=true\n" {
|
||||
t.Fatalf("output = %q", output.String())
|
||||
}
|
||||
db, err := database.Open(context.Background(), databasePath)
|
||||
if err != nil {
|
||||
t.Fatalf("database.Open() error = %v", err)
|
||||
}
|
||||
defer db.Close()
|
||||
var hash string
|
||||
if err := db.QueryRow(
|
||||
"SELECT password_hash FROM users WHERE username = 'admin'",
|
||||
).Scan(&hash); err != nil {
|
||||
t.Fatalf("query user: %v", err)
|
||||
}
|
||||
if bcrypt.CompareHashAndPassword([]byte(hash), []byte("admin")) != nil ||
|
||||
bcrypt.CompareHashAndPassword([]byte(hash), []byte("old-password")) == nil {
|
||||
t.Fatal("password was not replaced with a bcrypt hash")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunCreatesDeviceAndStoresOnlyTokenHash(t *testing.T) {
|
||||
databasePath := migratedDatabase(t)
|
||||
var output bytes.Buffer
|
||||
@@ -177,7 +219,7 @@ func TestRunRejectsWeakPasswordAndInvalidCommands(t *testing.T) {
|
||||
databasePath := migratedDatabase(t)
|
||||
err := run(
|
||||
[]string{"create-user", "BUYER", "buyer"},
|
||||
testLookup(databasePath, "short"),
|
||||
testLookup(databasePath, "tiny"),
|
||||
&bytes.Buffer{},
|
||||
)
|
||||
if err == nil || err.Error() != "authentication input is invalid" {
|
||||
|
||||
Reference in New Issue
Block a user