Files
kopia/cli/command_user_add_set.go
Julio López 9c5fc842a1 feat(cli): add server user set-password-hash command (#3974)
Objectives:
- Facilitate the generation of valid password hashes that can be used with
  the `server user --user-password` CLI command.
- Encapsulate implementation details of password hashing in
  the `user` package.

Adds a new `server user hash-password` CLI command to generate the
hash from a supplied password.

Modifies the `server user set/add --user-password-hash` CLI command
to accept the password hash generated using the `hash-password`
command.

Adds `GetNewProfile(ctx, rep, username)` helper to move implementation
details to the `user` package.

Includes CLI and unit tests.

Cleans up and removes unused functions.
2024-07-11 19:29:06 -07:00

128 lines
3.2 KiB
Go

package cli
import (
"context"
"io"
"github.com/alecthomas/kingpin/v2"
"github.com/pkg/errors"
"github.com/kopia/kopia/internal/user"
"github.com/kopia/kopia/repo"
)
type commandServerUserAddSet struct {
userAskPassword bool
userSetName string
userSetPassword string
userSetPasswordHash string
isNew bool // true == 'add', false == 'update'
out textOutput
}
func (c *commandServerUserAddSet) setup(svc appServices, parent commandParent, isNew bool) {
var cmd *kingpin.CmdClause
c.isNew = isNew
if isNew {
cmd = parent.Command("add", "Add new repository user").Alias("create")
} else {
cmd = parent.Command("set", "Set password for a repository user.").Alias("update")
}
cmd.Flag("ask-password", "Ask for user password").BoolVar(&c.userAskPassword)
cmd.Flag("user-password", "Password").StringVar(&c.userSetPassword)
cmd.Flag("user-password-hash", "Password hash").StringVar(&c.userSetPasswordHash)
cmd.Arg("username", "Username").Required().StringVar(&c.userSetName)
cmd.Action(svc.repositoryWriterAction(c.runServerUserAddSet))
c.out.setup(svc)
}
func (c *commandServerUserAddSet) getExistingOrNewUserProfile(ctx context.Context, rep repo.Repository, username string) (*user.Profile, error) {
if c.isNew {
up, err := user.GetNewProfile(ctx, rep, username)
return up, errors.Wrap(err, "error getting new user profile")
}
up, err := user.GetUserProfile(ctx, rep, username)
return up, errors.Wrap(err, "error getting user profile")
}
func (c *commandServerUserAddSet) runServerUserAddSet(ctx context.Context, rep repo.RepositoryWriter) error {
username := c.userSetName
up, err := c.getExistingOrNewUserProfile(ctx, rep, username)
if err != nil {
return err
}
changed := false
if p := c.userSetPassword; p != "" {
changed = true
if err := up.SetPassword(p); err != nil {
return errors.Wrap(err, "error setting password")
}
}
if ph := c.userSetPasswordHash; ph != "" {
if err := up.SetPasswordHash(ph); err != nil {
return errors.Wrap(err, "error setting password hash")
}
changed = true
}
if up.PasswordHash == nil || c.userAskPassword {
pwd, err := askConfirmPass(c.out.stdout(), "Enter new password for user "+username+": ")
if err != nil {
return err
}
changed = true
if err := up.SetPassword(pwd); err != nil {
return errors.Wrap(err, "error setting password")
}
}
if !changed && !c.isNew {
return errors.Errorf("no change")
}
if err := user.SetUserProfile(ctx, rep, up); err != nil {
return errors.Wrap(err, "error setting user profile")
}
log(ctx).Infof(`
Updated user credentials will take effect in 5-10 minutes or when the server is restarted.
To refresh credentials in a running server use 'kopia server refresh' command.
`)
return nil
}
func askConfirmPass(out io.Writer, initialPrompt string) (string, error) {
pwd, err := askPass(out, initialPrompt)
if err != nil {
return "", errors.Wrap(err, "error asking for password")
}
pwd2, err := askPass(out, "Re-enter password for verification: ")
if err != nil {
return "", errors.Wrap(err, "error asking for password")
}
if pwd != pwd2 {
return "", errors.Wrap(err, "passwords don't match")
}
return pwd, nil
}