admin.go (4694B) - raw
1 package 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 9 import ( 10 "flag" 11 "fmt" 12 "io/ioutil" 13 "log" 14 "os" 15 "path" 16 "syscall" 17 18 "golang.org/x/crypto/bcrypt" 19 "golang.org/x/crypto/ssh/terminal" 20 ) 21 22 // TODO improve cli 23 func runAdminCommand() { 24 args := flag.Args() // again? 25 if len(args) < 3 { 26 fmt.Println("Expected subcommand with parameter set-password|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 61 func 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 70 func 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 82 func 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 98 Welcome 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 101 And 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 104 Have fun!` 105 // Redundant cleanPath call just in case. 106 username = cleanPath(username)[1:] 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(` 113 Hi %s, Welcome to %s! You can now log into your account at 114 https://%s/login -- For more information about 115 using this site, check out https://admin.flounder.online/ 116 117 Let me know if you have any questions, and have fun!`, username, c.SiteTitle, c.Host)) 118 } 119 return nil 120 } 121 122 func 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 149 func 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 = cleanPath(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 }