package main // Commands for administering your instance // reset user password -> generate link // delete user // Run some scripts to setup your instance import ( "flag" "fmt" "golang.org/x/crypto/bcrypt" "golang.org/x/crypto/ssh/terminal" "io/ioutil" "log" "os" "path" "path/filepath" "syscall" ) // TODO improve cli func runAdminCommand() { args := flag.Args() // again? if len(args) < 3 { fmt.Println("Expected subcommand with parameter activate-user|delete-user|make-admin|rename-user") os.Exit(1) } var err error switch args[1] { case "activate-user": username := args[2] err = activateUser(username) case "delete-user": username := args[2] // TODO add confirmation err = deleteUser(username) case "make-admin": username := args[2] err = makeAdmin(username) case "rename-user": username := args[2] newUsername := args[3] err = renameUser(username, newUsername) case "set-password": username := args[2] fmt.Print("Enter New Password: ") bytePassword, err := terminal.ReadPassword(int(syscall.Stdin)) if err != nil { log.Fatal(err) } err = setPassword(username, bytePassword) } if err != nil { log.Fatal(err) } // reset password } func makeAdmin(username string) error { _, err := DB.Exec("UPDATE user SET admin = true WHERE username = $1", username) if err != nil { return err } log.Println("Made admin user", username) return nil } func setPassword(username string, newPass []byte) error { // TODO rm code dup hashedPassword, err := bcrypt.GenerateFromPassword(newPass, 8) if err != nil { return err } _, err = DB.Exec("UPDATE user SET password_hash = ? WHERE username = ?", hashedPassword, username) if err != nil { return err } return nil } func activateUser(username string) error { // Not ideal here row := DB.QueryRow("SELECT email FROM user where username = ?", username) var email string err := row.Scan(&email) if err != nil { return err } _, err = DB.Exec("UPDATE user SET active = true WHERE username = ?", username) if err != nil { // TODO verify 1 row updated return err } log.Println("Activated user", username) baseIndex := fmt.Sprintf("# Welcome to %s!\n\n", c.SiteTitle) + `## About 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 .` + 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: => //admin.flounder.online 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. => //admin.flounder.online/gemini_text_guide.gmi Have fun!` // Redundant filepath.Clean call just in case. username = filepath.Clean(username) os.Mkdir(path.Join(c.FilesDirectory, username), os.ModePerm) ioutil.WriteFile(path.Join(c.FilesDirectory, username, "index.gmi"), []byte(baseIndex), 0644) os.Mkdir(path.Join(c.FilesDirectory, username), os.ModePerm) if c.SMTPUsername != "" { // TODO move into work queue SendEmail(email, fmt.Sprintf("Welcome to %s!", c.SiteTitle), fmt.Sprintf(` Hi %s, Welcome to %s! You can now log into your account at https://%s/login -- For more information about using this site, check out https://admin.flounder.online/ Let me know if you have any questions, and have fun!`, c.SiteTitle, c.Host, username)) } return nil } func renameUser(oldUsername string, newUsername string) error { err := isOkUsername(newUsername) if err != nil { return err } res, err := DB.Exec("UPDATE user set username = ? WHERE username = ?", newUsername, oldUsername) if err != nil { return err } rowsAffected, err := res.RowsAffected() if rowsAffected != 1 { return fmt.Errorf("No User updated %s %s", oldUsername, newUsername) } else if err != nil { return err } userFolder := path.Join(c.FilesDirectory, oldUsername) newUserFolder := path.Join(c.FilesDirectory, newUsername) err = os.Rename(userFolder, newUserFolder) if err != nil { // This would be bad. User in broken, insecure state. // TODO some sort of better handling? return err } log.Printf("Changed username from %s to %s", oldUsername, newUsername) return nil } func deleteUser(username string) error { _, err := DB.Exec("DELETE FROM user WHERE username = $1", username) if err != nil { return err } username = filepath.Clean(username) err = os.RemoveAll(path.Join(c.FilesDirectory, username)) if err != nil { // bad state return err } log.Println("Deleted user", username) return nil }