1package user
2
3import (
4	"errors"
5	"fmt"
6
7	"github.com/AlecAivazis/survey"
8	"github.com/sensu/sensu-go/cli"
9	"github.com/sensu/sensu-go/cli/commands/flags"
10	"github.com/sensu/sensu-go/cli/commands/helpers"
11	"github.com/sensu/sensu-go/types"
12	"github.com/spf13/cobra"
13	"github.com/spf13/pflag"
14)
15
16var (
17	errBadCurrentPassword   = errors.New("given password did not match the one on file")
18	errEmptyCurrentPassword = errors.New("current user's password must be provided")
19	errPasswordsDoNotMatch  = errors.New("given passwords do not match")
20)
21
22type passwordOpts struct {
23	New     string `survey:"new-password"`
24	Confirm string `survey:"confirm-password"`
25}
26
27// SetPasswordCommand adds command that allows user to create new users
28func SetPasswordCommand(cli *cli.SensuCli) *cobra.Command {
29	cmd := &cobra.Command{
30		Use:          "change-password [USERNAME]",
31		Short:        "change password for given user",
32		SilenceUsage: true,
33		PreRun: func(cmd *cobra.Command, args []string) {
34			isInteractive, _ := cmd.Flags().GetBool(flags.Interactive)
35			if !isInteractive {
36				// Mark flags are required for bash-completions
37				_ = cmd.MarkFlagRequired("new-password")
38			}
39		},
40		RunE: func(cmd *cobra.Command, args []string) error {
41			if len(args) > 1 {
42				_ = cmd.Help()
43				return errors.New("invalid argument(s) received")
44			}
45
46			isInteractive, _ := cmd.Flags().GetBool(flags.Interactive)
47
48			password := &passwordOpts{}
49			var promptForCurrentPassword bool
50			var username string
51
52			// Retrieve current username from JWT
53			currentUsername := helpers.GetCurrentUsername(cli.Config)
54
55			// If no username is given we use the current user's name
56			if len(args) > 0 {
57				username = args[0]
58
59				// Prompt for password if specified username is current user
60				if username == currentUsername {
61					promptForCurrentPassword = true
62				}
63			} else {
64				username = currentUsername
65				promptForCurrentPassword = true
66			}
67
68			// As a precaution, ask for the current user's password
69			if promptForCurrentPassword {
70				if err := verifyExistingPassword(cli, cmd.Flags(), isInteractive, currentUsername); err != nil {
71					return err
72				}
73			}
74
75			if isInteractive {
76				// Prompt user for new password
77				if err := password.administerQuestionnaire(); err != nil {
78					return err
79				}
80			} else {
81				if err := password.withFlags(cmd.Flags()); err != nil {
82					_ = cmd.Help()
83					return errors.New("new password must be provided")
84				}
85			}
86
87			// Validate new password
88			if err := password.validate(); err != nil {
89				return err
90			}
91
92			// Update password
93			err := cli.Client.UpdatePassword(username, password.New)
94			if err != nil {
95				return err
96			}
97
98			fmt.Fprintln(cmd.OutOrStdout(), "Updated")
99			return nil
100		},
101	}
102
103	_ = cmd.Flags().String("current-password", "", "current password")
104	_ = cmd.Flags().String("new-password", "", "new password")
105
106	helpers.AddInteractiveFlag(cmd.Flags())
107
108	return cmd
109}
110
111func verifyExistingPassword(cli *cli.SensuCli, flags *pflag.FlagSet, isInteractive bool, username string) error {
112	input := struct{ Password string }{}
113
114	if isInteractive {
115		qs := []*survey.Question{
116			{
117				Name:     "password",
118				Prompt:   &survey.Password{Message: "Current Password:\t"},
119				Validate: survey.Required,
120			},
121		}
122
123		// Get password
124		if err := survey.Ask(qs, &input); err != nil {
125			return err
126		}
127	} else {
128		input.Password, _ = flags.GetString("current-password")
129	}
130
131	// Validate that the current password has been provided
132	if input.Password == "" {
133		return errEmptyCurrentPassword
134	}
135
136	// Attempt to authenticate
137	if _, err := cli.Client.CreateAccessToken(cli.Config.APIUrl(), username, input.Password); err != nil {
138		return errBadCurrentPassword
139	}
140
141	return nil
142}
143
144func (opts *passwordOpts) administerQuestionnaire() error {
145	qs := []*survey.Question{
146		{
147			Name:     "new-password",
148			Prompt:   &survey.Password{Message: "New Password:\t\t"},
149			Validate: survey.Required,
150		},
151		{
152			Name:     "confirm-password",
153			Prompt:   &survey.Password{Message: "Confirm Password:\t"},
154			Validate: survey.Required,
155		},
156	}
157
158	return survey.Ask(qs, opts)
159}
160
161func (opts *passwordOpts) withFlags(flags *pflag.FlagSet) error {
162	password, _ := flags.GetString("new-password")
163	if password == "" {
164		return errors.New("empty password")
165	}
166
167	opts.New = password
168	opts.Confirm = password
169
170	return nil
171}
172
173func (opts *passwordOpts) validate() error {
174	if opts.New != opts.Confirm {
175		return errPasswordsDoNotMatch
176	}
177
178	user := types.User{Password: opts.New}
179	return user.ValidatePassword()
180}
181