admin.go (view raw)
1package main
2
3// Commands for administering your instance
4// reset user password -> generate link
5// delete user
6
7// Run some scripts to setup your instance
8
9import (
10 "flag"
11 "fmt"
12 "golang.org/x/crypto/bcrypt"
13 "golang.org/x/crypto/ssh/terminal"
14 "io/ioutil"
15 "log"
16 "os"
17 "path"
18 "path/filepath"
19 "syscall"
20)
21
22// TODO improve cli
23func runAdminCommand() {
24 args := flag.Args() // again?
25 if len(args) < 3 {
26 fmt.Println("Expected subcommand with parameter activate-user|delete-user|make-admin|rename-user")
27 os.Exit(1)
28 }
29 var err error
30 switch args[1] {
31 case "activate-user":
32 username := args[2]
33 err = activateUser(username)
34 case "delete-user":
35 username := args[2]
36 // TODO add confirmation
37 err = deleteUser(username)
38 case "make-admin":
39 username := args[2]
40 err = makeAdmin(username)
41 case "rename-user":
42 username := args[2]
43 newUsername := args[3]
44 err = renameUser(username, newUsername)
45 case "set-password":
46 username := args[2]
47 fmt.Print("Enter New Password: ")
48 bytePassword, err := terminal.ReadPassword(int(syscall.Stdin))
49 if err != nil {
50 log.Fatal(err)
51 }
52 err = setPassword(username, bytePassword)
53 }
54 if err != nil {
55 log.Fatal(err)
56 }
57 // reset password
58
59}
60
61func makeAdmin(username string) error {
62 _, err := DB.Exec("UPDATE user SET admin = true WHERE username = $1", username)
63 if err != nil {
64 return err
65 }
66 log.Println("Made admin user", username)
67 return nil
68}
69
70func setPassword(username string, newPass []byte) error { // TODO rm code dup
71 hashedPassword, err := bcrypt.GenerateFromPassword(newPass, 8)
72 if err != nil {
73 return err
74 }
75 _, err = DB.Exec("UPDATE user SET password_hash = ? WHERE username = ?", hashedPassword, username)
76 if err != nil {
77 return err
78 }
79 return nil
80}
81
82func activateUser(username string) error {
83 // Not ideal here
84 row := DB.QueryRow("SELECT email FROM user where username = ?", username)
85 var email string
86 err := row.Scan(&email)
87 if err != nil {
88 return err
89 }
90 _, err = DB.Exec("UPDATE user SET active = true WHERE username = ?", username)
91 if err != nil {
92 // TODO verify 1 row updated
93 return err
94 }
95 log.Println("Activated user", username)
96 baseIndex := fmt.Sprintf("# Welcome to %s!\n\n", c.SiteTitle) +
97 `## About
98Welcome to an ultra-lightweight platform for making and sharing small websites. You can get started by editing this page -- remove this content and replace it with whatever you like! It will be live at <your-name>.` + c.Host + ` -- You can go there right now to see what this page currently looks like. Here is a link to a page which will give you more information about using this site:
99=> //admin.flounder.online
100
101And here's a guide to the text format that this site uses to create pages. These pages are converted into HTML so they can be displayed in a web browser.
102=> //admin.flounder.online/gemini_text_guide.gmi
103
104Have fun!`
105 // Redundant filepath.Clean call just in case.
106 username = filepath.Clean(username)
107 os.Mkdir(path.Join(c.FilesDirectory, username), os.ModePerm)
108 ioutil.WriteFile(path.Join(c.FilesDirectory, username, "index.gmi"), []byte(baseIndex), 0644)
109 os.Mkdir(path.Join(c.FilesDirectory, username), os.ModePerm)
110 if c.SMTPUsername != "" {
111 // TODO move into work queue
112 SendEmail(email, fmt.Sprintf("Welcome to %s!", c.SiteTitle), fmt.Sprintf(`
113Hi %s, Welcome to %s! You can now log into your account at
114https://%s/login -- For more information about
115using this site, check out https://admin.flounder.online/
116
117Let me know if you have any questions, and have fun!`, c.SiteTitle, c.Host, username))
118 }
119 return nil
120}
121
122func renameUser(oldUsername string, newUsername string) error {
123 err := isOkUsername(newUsername)
124 if err != nil {
125 return err
126 }
127 res, err := DB.Exec("UPDATE user set username = ? WHERE username = ?", newUsername, oldUsername)
128 if err != nil {
129 return err
130 }
131 rowsAffected, err := res.RowsAffected()
132 if rowsAffected != 1 {
133 return fmt.Errorf("No User updated %s %s", oldUsername, newUsername)
134 } else if err != nil {
135 return err
136 }
137 userFolder := path.Join(c.FilesDirectory, oldUsername)
138 newUserFolder := path.Join(c.FilesDirectory, newUsername)
139 err = os.Rename(userFolder, newUserFolder)
140 if err != nil {
141 // This would be bad. User in broken, insecure state.
142 // TODO some sort of better handling?
143 return err
144 }
145 log.Printf("Changed username from %s to %s", oldUsername, newUsername)
146 return nil
147}
148
149func deleteUser(username string) error {
150 _, err := DB.Exec("DELETE FROM user WHERE username = $1", username)
151 if err != nil {
152 return err
153 }
154 username = filepath.Clean(username)
155 err = os.RemoveAll(path.Join(c.FilesDirectory, username))
156 if err != nil {
157 // bad state
158 return err
159 }
160 log.Println("Deleted user", username)
161 return nil
162}