tracker: add -changepw to change a user passphrase
Mirrors -adduser: prompts for a new passphrase (min 8 chars) or reads TRACKER_PASSWORD, hashes with bcrypt, and updates the stored hash. Returns an error if the user does not exist.
This commit is contained in:
@@ -23,6 +23,8 @@ go build -o tracker .
|
||||
```
|
||||
|
||||
Non-interactive variant: `TRACKER_PASSWORD=... ./tracker -adduser kyle`.
|
||||
Change an existing user's passphrase the same way:
|
||||
`./tracker -changepw kyle` (or `TRACKER_PASSWORD=... ./tracker -changepw kyle`).
|
||||
|
||||
Defaults: listens on `:8420` (all interfaces, so it's reachable over Tailscale),
|
||||
database at `~/.local/share/tracker/tracker.db`.
|
||||
|
||||
@@ -46,6 +46,14 @@ func (s *Store) UserByName(username string) (User, error) {
|
||||
return u, err
|
||||
}
|
||||
|
||||
func (s *Store) SetPassword(username, passwordHash string) error {
|
||||
res, err := s.db.Exec(`UPDATE users SET password_hash = ? WHERE username = ?`, passwordHash, username)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return checkAffected(res)
|
||||
}
|
||||
|
||||
// --- api keys ---
|
||||
|
||||
func (s *Store) CreateAPIKey(name, keyHash string) (int64, error) {
|
||||
|
||||
@@ -22,6 +22,7 @@ func main() {
|
||||
dbPath := flag.String("db", defaultDB, "sqlite database path")
|
||||
addr := flag.String("addr", ":8420", "listen address")
|
||||
addUser := flag.String("adduser", "", "create a login user, then exit")
|
||||
changepw := flag.String("changepw", "", "change a login user's passphrase, then exit")
|
||||
flag.Parse()
|
||||
|
||||
if err := os.MkdirAll(filepath.Dir(*dbPath), 0o755); err != nil {
|
||||
@@ -35,6 +36,13 @@ func main() {
|
||||
return
|
||||
}
|
||||
|
||||
if *changepw != "" {
|
||||
if err := runChangePassword(*dbPath, *changepw); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
st, err := store.Open(*dbPath)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
@@ -106,3 +114,61 @@ func runAddUser(dbPath, username string) error {
|
||||
fmt.Printf("user %q created. log in at /login\n", username)
|
||||
return nil
|
||||
}
|
||||
|
||||
func runChangePassword(dbPath, username string) error {
|
||||
username = strings.TrimSpace(username)
|
||||
if username == "" {
|
||||
return errors.New("username cannot be empty")
|
||||
}
|
||||
if strings.ContainsAny(username, " \t") {
|
||||
return errors.New("username cannot contain whitespace")
|
||||
}
|
||||
|
||||
st, err := store.Open(dbPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer st.Close()
|
||||
|
||||
if _, err := st.UserByName(username); err != nil {
|
||||
if errors.Is(err, store.ErrNotFound) {
|
||||
return fmt.Errorf("user %q not found", username)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
password := os.Getenv("TRACKER_PASSWORD")
|
||||
if password == "" {
|
||||
fmt.Print("new passphrase: ")
|
||||
pw, err := term.ReadPassword(int(os.Stdin.Fd()))
|
||||
fmt.Println()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Print("confirm: ")
|
||||
confirmPw, err := term.ReadPassword(int(os.Stdin.Fd()))
|
||||
fmt.Println()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
password = string(pw)
|
||||
if string(confirmPw) != password {
|
||||
return errors.New("passphrases do not match")
|
||||
}
|
||||
}
|
||||
|
||||
if len(password) < 8 {
|
||||
return errors.New("passphrase must be at least 8 characters")
|
||||
}
|
||||
|
||||
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := st.SetPassword(username, string(hash)); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("passphrase for %q updated\n", username)
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user