source: code/trunk/cmd/sojuctl/main.go@ 384

Last change on this file since 384 was 380, checked in by contact, 5 years ago

cmd/sojuctl: read user from DB before updating it

This makes sure we don't overwrite other fields, such as Admin.

Closes: https://todo.sr.ht/~emersion/soju/85

File size: 3.1 KB
Line 
1package main
2
3import (
4 "bufio"
5 "flag"
6 "fmt"
7 "log"
8 "os"
9
10 "git.sr.ht/~emersion/soju"
11 "git.sr.ht/~emersion/soju/config"
12 "golang.org/x/crypto/bcrypt"
13 "golang.org/x/crypto/ssh/terminal"
14)
15
16const usage = `usage: sojuctl [-config path] <action> [options...]
17
18 create-user <username> [-admin] Create a new user
19 change-password <username> Change password for a user
20 help Show this help message
21`
22
23func init() {
24 flag.Usage = func() {
25 fmt.Fprintf(flag.CommandLine.Output(), usage)
26 }
27}
28
29func main() {
30 var configPath string
31 flag.StringVar(&configPath, "config", "", "path to configuration file")
32 flag.Parse()
33
34 var cfg *config.Server
35 if configPath != "" {
36 var err error
37 cfg, err = config.Load(configPath)
38 if err != nil {
39 log.Fatalf("failed to load config file: %v", err)
40 }
41 } else {
42 cfg = config.Defaults()
43 }
44
45 db, err := soju.OpenSQLDB(cfg.SQLDriver, cfg.SQLSource)
46 if err != nil {
47 log.Fatalf("failed to open database: %v", err)
48 }
49
50 switch cmd := flag.Arg(0); cmd {
51 case "create-user":
52 username := flag.Arg(1)
53 if username == "" {
54 flag.Usage()
55 os.Exit(1)
56 }
57
58 fs := flag.NewFlagSet("", flag.ExitOnError)
59 admin := fs.Bool("admin", false, "make the new user admin")
60 fs.Parse(flag.Args()[2:])
61
62 password, err := readPassword()
63 if err != nil {
64 log.Fatalf("failed to read password: %v", err)
65 }
66
67 hashed, err := bcrypt.GenerateFromPassword(password, bcrypt.DefaultCost)
68 if err != nil {
69 log.Fatalf("failed to hash password: %v", err)
70 }
71
72 user := soju.User{
73 Username: username,
74 Password: string(hashed),
75 Admin: *admin,
76 }
77 if err := db.StoreUser(&user); err != nil {
78 log.Fatalf("failed to create user: %v", err)
79 }
80 case "change-password":
81 username := flag.Arg(1)
82 if username == "" {
83 flag.Usage()
84 os.Exit(1)
85 }
86
87 password, err := readPassword()
88 if err != nil {
89 log.Fatalf("failed to read password: %v", err)
90 }
91
92 hashed, err := bcrypt.GenerateFromPassword(password, bcrypt.DefaultCost)
93 if err != nil {
94 log.Fatalf("failed to hash password: %v", err)
95 }
96
97 user, err := db.GetUser(username)
98 if err != nil {
99 log.Fatalf("failed to get user: %v", err)
100 }
101 user.Password = string(hashed)
102 if err := db.StoreUser(user); err != nil {
103 log.Fatalf("failed to update password: %v", err)
104 }
105 default:
106 flag.Usage()
107 if cmd != "help" {
108 os.Exit(1)
109 }
110 }
111}
112
113func readPassword() ([]byte, error) {
114 var password []byte
115 var err error
116 fd := int(os.Stdin.Fd())
117
118 if terminal.IsTerminal(fd) {
119 fmt.Printf("Password: ")
120 password, err = terminal.ReadPassword(int(os.Stdin.Fd()))
121 if err != nil {
122 return nil, err
123 }
124 fmt.Printf("\n")
125 } else {
126 fmt.Fprintf(os.Stderr, "Warning: Reading password from stdin.\n")
127 scanner := bufio.NewScanner(os.Stdin)
128 if !scanner.Scan() {
129 if err := scanner.Err(); err != nil {
130 log.Fatalf("failed to read password from stdin: %v", err)
131 }
132 log.Fatalf("failed to read password from stdin: stdin is empty")
133 }
134 password = scanner.Bytes()
135
136 if len(password) == 0 {
137 return nil, fmt.Errorf("zero length password")
138 }
139 }
140
141 return password, nil
142}
Note: See TracBrowser for help on using the repository browser.