flounder

A simple gemini site builder
git clone git://git.alexwennerberg.com/flounder
Log | Files | Refs | README | LICENSE

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 }