package main import ( "bufio" "fmt" "os" "strings" "github.com/yourusername/caldav-server/internal/db" "golang.org/x/term" ) func runUser(dbase *db.DB, args []string) int { if len(args) < 1 { usage() return 2 } switch args[0] { case "create": return userCreate(dbase, args[1:]) case "delete": return userDelete(dbase, args[1:]) case "list": return userList(dbase, args[1:]) case "passwd": return userPasswd(dbase, args[1:]) default: fmt.Fprintf(os.Stderr, "unknown user subcommand %q\n", args[0]) return 2 } } func userCreate(dbase *db.DB, args []string) int { fs := newFlagSet("nidusctl user create") displayName := fs.String("display-name", "", "display name shown in DAV clients") email := fs.String("email", "", "email address") password := fs.String("password", "", "password (omit to be prompted, recommended)") if err := fs.Parse(args); err != nil { return 2 } rest := fs.Args() if len(rest) != 1 { fmt.Fprintln(os.Stderr, "usage: nidusctl user create [--display-name NAME] [--email EMAIL] [--password PASSWORD]") return 2 } username := rest[0] pw := *password if pw == "" { var err error pw, err = promptPassword(username) if err != nil { fmt.Fprintf(os.Stderr, "error reading password: %v\n", err) return 1 } } if err := dbase.CreateUser(username, pw, *displayName, *email); err != nil { fmt.Fprintf(os.Stderr, "error: %v\n", err) return 1 } fmt.Printf("created user %q\n", username) return 0 } func userDelete(dbase *db.DB, args []string) int { if len(args) != 1 { fmt.Fprintln(os.Stderr, "usage: nidusctl user delete ") return 2 } username := args[0] if err := dbase.DeleteUser(username); err != nil { fmt.Fprintf(os.Stderr, "error: %v\n", err) return 1 } fmt.Printf("deleted user %q (and its calendars, address books, and shares)\n", username) return 0 } func userList(dbase *db.DB, args []string) int { if len(args) != 0 { fmt.Fprintln(os.Stderr, "usage: nidusctl user list") return 2 } users, err := dbase.ListUsers() if err != nil { fmt.Fprintf(os.Stderr, "error: %v\n", err) return 1 } if len(users) == 0 { fmt.Println("no users") return 0 } for _, u := range users { fmt.Printf("%s\t%s\t%s\n", u.Username, u.DisplayName, u.Email) } return 0 } func userPasswd(dbase *db.DB, args []string) int { fs := newFlagSet("nidusctl user passwd") password := fs.String("password", "", "new password (omit to be prompted, recommended)") if err := fs.Parse(args); err != nil { return 2 } rest := fs.Args() if len(rest) != 1 { fmt.Fprintln(os.Stderr, "usage: nidusctl user passwd [--password PASSWORD]") return 2 } username := rest[0] pw := *password if pw == "" { var err error pw, err = promptPassword(username) if err != nil { fmt.Fprintf(os.Stderr, "error reading password: %v\n", err) return 1 } } if err := dbase.SetPassword(username, pw); err != nil { fmt.Fprintf(os.Stderr, "error: %v\n", err) return 1 } fmt.Printf("updated password for %q\n", username) return 0 } // promptPassword reads a password twice from the terminal (without echo, // if stdin is a TTY) and confirms both entries match. func promptPassword(username string) (string, error) { if term.IsTerminal(int(os.Stdin.Fd())) { fmt.Fprintf(os.Stderr, "password for %s: ", username) pw1, err := term.ReadPassword(int(os.Stdin.Fd())) fmt.Fprintln(os.Stderr) if err != nil { return "", err } fmt.Fprint(os.Stderr, "confirm password: ") pw2, err := term.ReadPassword(int(os.Stdin.Fd())) fmt.Fprintln(os.Stderr) if err != nil { return "", err } if string(pw1) != string(pw2) { return "", fmt.Errorf("passwords do not match") } if len(pw1) == 0 { return "", fmt.Errorf("password must not be empty") } return string(pw1), nil } // Not a TTY (e.g. piped input in scripts/tests) — read a single line. reader := bufio.NewReader(os.Stdin) line, err := reader.ReadString('\n') if err != nil && line == "" { return "", err } pw := strings.TrimRight(line, "\r\n") if pw == "" { return "", fmt.Errorf("password must not be empty") } return pw, nil }