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`.
|
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),
|
Defaults: listens on `:8420` (all interfaces, so it's reachable over Tailscale),
|
||||||
database at `~/.local/share/tracker/tracker.db`.
|
database at `~/.local/share/tracker/tracker.db`.
|
||||||
|
|||||||
@@ -46,6 +46,14 @@ func (s *Store) UserByName(username string) (User, error) {
|
|||||||
return u, err
|
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 ---
|
// --- api keys ---
|
||||||
|
|
||||||
func (s *Store) CreateAPIKey(name, keyHash string) (int64, error) {
|
func (s *Store) CreateAPIKey(name, keyHash string) (int64, error) {
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ func main() {
|
|||||||
dbPath := flag.String("db", defaultDB, "sqlite database path")
|
dbPath := flag.String("db", defaultDB, "sqlite database path")
|
||||||
addr := flag.String("addr", ":8420", "listen address")
|
addr := flag.String("addr", ":8420", "listen address")
|
||||||
addUser := flag.String("adduser", "", "create a login user, then exit")
|
addUser := flag.String("adduser", "", "create a login user, then exit")
|
||||||
|
changepw := flag.String("changepw", "", "change a login user's passphrase, then exit")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
if err := os.MkdirAll(filepath.Dir(*dbPath), 0o755); err != nil {
|
if err := os.MkdirAll(filepath.Dir(*dbPath), 0o755); err != nil {
|
||||||
@@ -35,6 +36,13 @@ func main() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if *changepw != "" {
|
||||||
|
if err := runChangePassword(*dbPath, *changepw); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
st, err := store.Open(*dbPath)
|
st, err := store.Open(*dbPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
@@ -106,3 +114,61 @@ func runAddUser(dbPath, username string) error {
|
|||||||
fmt.Printf("user %q created. log in at /login\n", username)
|
fmt.Printf("user %q created. log in at /login\n", username)
|
||||||
return nil
|
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