all repos — flounder @ 8127760596cb0d3678a306b2a70a03bcd2fb09b3

A small site builder for the Gemini protocol

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!`, username, c.SiteTitle, c.Host))
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}