1
0
mirror of https://github.com/osmarks/mycorrhiza.git synced 2025-01-06 10:00:26 +00:00

Add -create-admin flag

It allows for interactive creation of admin account from the terminal
for new wikis. Because we have a chicken-and-egg problem here with the
user panel -- you need an admin account to appoint someone as an admin,
right?
This commit is contained in:
handlerug 2021-07-02 17:25:13 +07:00
parent d9008bcb3f
commit d4fea3d24a
No known key found for this signature in database
GPG Key ID: 38009F0605051491
5 changed files with 76 additions and 23 deletions

63
flag.go
View File

@ -4,20 +4,20 @@ import (
_ "embed"
"flag"
"fmt"
"io"
"log"
"os"
"path/filepath"
"syscall"
"github.com/bouncepaw/mycorrhiza/cfg"
"github.com/bouncepaw/mycorrhiza/files"
"github.com/bouncepaw/mycorrhiza/user"
"golang.org/x/term"
)
// CLI options are read and parsed here.
func init() {
flag.StringVar(&cfg.HTTPPort, "port", "", "Listen on another port. This option also updates the config file for your convenience.")
flag.Usage = printHelp
}
// printHelp prints the help message.
func printHelp() {
fmt.Fprintf(
@ -28,13 +28,22 @@ func printHelp() {
flag.PrintDefaults()
}
type CreateUserCommand struct {
name string
}
// parseCliArgs parses CLI options and sets several important global variables. Call it early.
func parseCliArgs() {
var createAdminName string
flag.StringVar(&createAdminName, "create-admin", "", "Create a new admin. The password will be prompted in the terminal.")
flag.StringVar(&cfg.HTTPPort, "port", "", "Listen on another port. This option also updates the config file for your convenience.")
flag.Usage = printHelp
flag.Parse()
args := flag.Args()
if len(args) == 0 {
log.Fatal("Error: pass a wiki directory")
log.Fatal("error: pass a wiki directory")
}
wikiDir, err := filepath.Abs(args[0])
@ -43,4 +52,46 @@ func parseCliArgs() {
}
cfg.WikiDir = wikiDir
if createAdminName != "" {
createAdminCommand(createAdminName)
os.Exit(0)
}
}
func createAdminCommand(name string) {
wr := log.Writer()
log.SetFlags(0)
if err := files.PrepareWikiRoot(); err != nil {
log.Fatal("error: ", err)
}
cfg.UseAuth = true
cfg.AllowRegistration = true
log.SetOutput(io.Discard)
user.InitUserDatabase()
log.SetOutput(wr)
handle := int(syscall.Stdin)
if !term.IsTerminal(handle) {
log.Fatal("error: not a terminal")
}
fmt.Print("Password: ")
passwordBytes, err := term.ReadPassword(handle)
fmt.Print("\n")
if err != nil {
log.Fatal("error: ", err)
}
password := string(passwordBytes)
log.SetOutput(io.Discard)
err = user.Register(name, password, "admin", true)
log.SetOutput(wr)
if err != nil {
log.Fatal("error: ", err)
}
}

1
go.mod
View File

@ -10,6 +10,7 @@ require (
github.com/smartystreets/goconvey v1.6.4 // indirect
github.com/valyala/quicktemplate v1.6.3
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b
gopkg.in/ini.v1 v1.62.0 // indirect
)

4
go.sum
View File

@ -36,6 +36,10 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b h1:9zKuko04nR4gjZ4+DNjHqRlAJqbJETHwiNKDqTfOjfE=
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU=

View File

@ -35,36 +35,31 @@ func LogoutFromRequest(w http.ResponseWriter, rq *http.Request) {
}
// Register registers the given user. If it fails, a non-nil error is returned.
func Register(username, password string) error {
func Register(username, password, group string, force bool) error {
username = util.CanonicalName(username)
log.Println("Attempt to register user", username)
switch {
case cfg.RegistrationLimit > 0 && Count() >= cfg.RegistrationLimit:
log.Printf("Limit reached: %d", cfg.RegistrationLimit)
return fmt.Errorf("Reached the limit of registered users: %d", cfg.RegistrationLimit)
case HasUsername(username):
log.Println("Username taken")
return fmt.Errorf("Username \"%s\" is taken already.", username)
case !util.IsPossibleUsername(username):
log.Println("Illegal username:", username)
return fmt.Errorf("Illegal username \"%s\".", username)
return fmt.Errorf("illegal username \"%s\"", username)
case !force && cfg.RegistrationLimit > 0 && Count() >= cfg.RegistrationLimit:
return fmt.Errorf("reached the limit of registered users (%d)", cfg.RegistrationLimit)
case !force && HasUsername(username):
return fmt.Errorf("username \"%s\" is already taken", username)
}
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
return err
}
u := User{
Name: username,
Group: "editor",
Group: group,
Password: string(hash),
RegisteredAt: time.Now(),
}
users.Store(username, &u)
err = SaveUserDatabase()
if err != nil {
return err
}
return nil
return SaveUserDatabase()
}
// LoginDataHTTP logs such user in and returns string representation of an error if there is any.

View File

@ -45,9 +45,10 @@ func handlerRegister(w http.ResponseWriter, rq *http.Request) {
var (
username = rq.PostFormValue("username")
password = rq.PostFormValue("password")
err = user.Register(username, password)
err = user.Register(username, password, "editor", false)
)
if err != nil {
log.Printf("Failed to register \"%s\": %s", username, err.Error())
w.Header().Set("Content-Type", mime.TypeByExtension(".html"))
w.WriteHeader(http.StatusBadRequest)
fmt.Fprint(
@ -62,6 +63,7 @@ func handlerRegister(w http.ResponseWriter, rq *http.Request) {
),
)
} else {
log.Printf("Successfully registered \"%s\"", username)
user.LoginDataHTTP(w, rq, username, password)
http.Redirect(w, rq, "/"+rq.URL.RawQuery, http.StatusSeeOther)
}