87b6d72477
Signed-off-by: Michael Mayer <michael@photoprism.app>
164 lines
3.9 KiB
Go
164 lines
3.9 KiB
Go
package commands
|
|
|
|
import (
|
|
"bufio"
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"os/signal"
|
|
"strings"
|
|
"syscall"
|
|
|
|
"github.com/urfave/cli"
|
|
|
|
"github.com/photoprism/photoprism/internal/entity"
|
|
"github.com/photoprism/photoprism/pkg/clean"
|
|
"github.com/photoprism/photoprism/pkg/rnd"
|
|
"github.com/photoprism/photoprism/pkg/txt"
|
|
)
|
|
|
|
// PasswdCommand configures the command name, flags, and action.
|
|
var PasswdCommand = cli.Command{
|
|
Name: "passwd",
|
|
Usage: "Changes the password of the user specified as argument",
|
|
ArgsUsage: "[username]",
|
|
Flags: []cli.Flag{
|
|
cli.BoolFlag{
|
|
Name: "show, s",
|
|
Usage: "show bcrypt password hash",
|
|
},
|
|
},
|
|
Action: passwdAction,
|
|
}
|
|
|
|
// passwdAction changes the password of the user specified as command argument.
|
|
func passwdAction(ctx *cli.Context) error {
|
|
id := clean.Username(ctx.Args().First())
|
|
|
|
// Name or UID provided?
|
|
if id == "" {
|
|
return cli.ShowSubcommandHelp(ctx)
|
|
}
|
|
|
|
conf, err := InitConfig(ctx)
|
|
|
|
_, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
conf.InitDb()
|
|
defer conf.Shutdown()
|
|
|
|
// Find user record.
|
|
var m *entity.User
|
|
|
|
if rnd.IsUID(id, entity.UserUID) {
|
|
m = entity.FindUserByUID(id)
|
|
} else {
|
|
m = entity.FindUserByName(id)
|
|
}
|
|
|
|
if m == nil {
|
|
return fmt.Errorf("user %s not found", clean.LogQuote(id))
|
|
} else if m.Deleted() {
|
|
return fmt.Errorf("user %s has been deleted", clean.LogQuote(id))
|
|
}
|
|
|
|
log.Infof("please enter a new password for %s (%d-%d characters)\n", clean.Log(m.Username()), entity.PasswordLength, txt.ClipPassword)
|
|
|
|
newPassword := getPassword("New Password: ")
|
|
|
|
if len([]rune(newPassword)) < entity.PasswordLength {
|
|
return fmt.Errorf("password must have at least %d characters", entity.PasswordLength)
|
|
} else if len(newPassword) > txt.ClipPassword {
|
|
return fmt.Errorf("password must have less than %d characters", txt.ClipPassword)
|
|
}
|
|
|
|
retypePassword := getPassword("Retype Password: ")
|
|
|
|
if newPassword != retypePassword {
|
|
return errors.New("passwords did not match, please try again")
|
|
}
|
|
|
|
if err = m.SetPassword(newPassword); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Show bcrypt password hash?
|
|
if pw := entity.FindPassword(m.UserUID); ctx.Bool("show") && pw != nil {
|
|
log.Infof("password for %s successfully changed to %s\n", clean.Log(m.Username()), pw.Hash)
|
|
} else {
|
|
log.Infof("password for %s successfully changed\n", clean.Log(m.Username()))
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// License: MIT Open Source
|
|
// Copyright (c) Joe Linoff 2016
|
|
// Go code to prompt for password using only standard packages by utilizing syscall.ForkExec() and syscall.Wait4().
|
|
// Correctly resets terminal echo after ^C interrupts.
|
|
//
|
|
// techEcho() - turns terminal echo on or off.
|
|
func termEcho(on bool) {
|
|
// Common settings and variables for both stty calls.
|
|
attrs := syscall.ProcAttr{
|
|
Dir: "",
|
|
Env: []string{},
|
|
Files: []uintptr{os.Stdin.Fd(), os.Stdout.Fd(), os.Stderr.Fd()},
|
|
Sys: nil}
|
|
var ws syscall.WaitStatus
|
|
cmd := "echo"
|
|
if on == false {
|
|
cmd = "-echo"
|
|
}
|
|
|
|
// Enable/disable echoing.
|
|
pid, err := syscall.ForkExec(
|
|
"/bin/stty",
|
|
[]string{"stty", cmd},
|
|
&attrs)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
// Wait for the stty process to complete.
|
|
_, err = syscall.Wait4(pid, &ws, 0, nil)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
// getPassword - Prompt for password.
|
|
func getPassword(prompt string) string {
|
|
fmt.Print(prompt)
|
|
|
|
// Catch a ^C interrupt.
|
|
// Make sure that we reset term echo before exiting.
|
|
signalChannel := make(chan os.Signal, 1)
|
|
signal.Notify(signalChannel, os.Interrupt)
|
|
go func() {
|
|
for range signalChannel {
|
|
fmt.Println("")
|
|
termEcho(true)
|
|
os.Exit(1)
|
|
}
|
|
}()
|
|
|
|
// Echo is disabled, now grab the data.
|
|
termEcho(false) // disable terminal echo
|
|
reader := bufio.NewReader(os.Stdin)
|
|
text, err := reader.ReadString('\n')
|
|
termEcho(true) // always re-enable terminal echo
|
|
fmt.Println("")
|
|
if err != nil {
|
|
// The terminal has been reset, go ahead and exit.
|
|
fmt.Println("ERROR:", err.Error())
|
|
os.Exit(1)
|
|
}
|
|
return strings.TrimSpace(text)
|
|
}
|