// orchestra-user manages browser-operator accounts in Orchestra's embedded // credential database. Passwords are read from the terminal and hashed inside // the database; no reusable hash has to be copied into deployment config. package main import ( "flag" "fmt" "log" "orchestra/internal/authn" "os" "syscall" "golang.org/x/term" ) func usage() { fmt.Fprintln(os.Stderr, "usage:") fmt.Fprintln(os.Stderr, " orchestra-user set -data DIR -username NAME") fmt.Fprintln(os.Stderr, " orchestra-user list -data DIR") } func readPassword(prompt string) (string, error) { fmt.Fprint(os.Stderr, prompt) value, err := term.ReadPassword(int(syscall.Stdin)) fmt.Fprintln(os.Stderr) return string(value), err } func open(data string) *authn.Store { if data == "" { log.Fatal("-data is required") } users, err := authn.Open(authn.Path(data)) if err != nil { log.Fatal(err) } return users } func main() { if len(os.Args) < 2 { usage() os.Exit(2) } switch os.Args[1] { case "set": flags := flag.NewFlagSet("set", flag.ExitOnError) data := flags.String("data", "", "Orchestra data directory") username := flags.String("username", "", "operator username") _ = flags.Parse(os.Args[2:]) password, err := readPassword("New password: ") if err != nil { log.Fatal(err) } confirmation, err := readPassword("Confirm password: ") if err != nil { log.Fatal(err) } if password != confirmation { log.Fatal("passwords do not match") } users := open(*data) defer users.Close() user, created, err := users.SetPassword(*username, password) if err != nil { log.Fatal(err) } if created { fmt.Printf("created operator %s\n", user.Username) } else { fmt.Printf("updated password for %s\n", user.Username) } case "list": flags := flag.NewFlagSet("list", flag.ExitOnError) data := flags.String("data", "", "Orchestra data directory") _ = flags.Parse(os.Args[2:]) users := open(*data) defer users.Close() list, err := users.Users() if err != nil { log.Fatal(err) } for _, user := range list { fmt.Println(user.Username) } default: usage() os.Exit(2) } }