all repos — flounder @ 48c4084ba652171948827c35a6803c8c54aa98da

A small site builder for the Gemini protocol

add registration handler
alex wennerberg alex@alexwennerberg.com
Sat, 24 Oct 2020 13:09:57 -0700
commit

48c4084ba652171948827c35a6803c8c54aa98da

parent

d6be68418a350fc835a86dd82bba9935d7a609ba

8 files changed, 92 insertions(+), 10 deletions(-)

jump to
A admin.go

@@ -0,0 +1,5 @@

+package main + +// Commands for administering your instance +// reset user password -> generate link +// delete user
M go.modgo.mod

@@ -6,6 +6,7 @@ require (

git.sr.ht/~adnano/gmi v0.1.0-alpha.2 github.com/BurntSushi/toml v0.3.1 github.com/gorilla/handlers v1.5.1 + github.com/mattn/go-sqlite3 v1.14.4 golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e )
M go.sumgo.sum

@@ -6,6 +6,8 @@ github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ=

github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= +github.com/mattn/go-sqlite3 v1.14.4 h1:4rQjbDxdu9fSgI/r3KN72G3c2goxknAqHHgPWWs8UlI= +github.com/mattn/go-sqlite3 v1.14.4/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d h1:2+ZP7EfsZV7Vvmx3TIqSlSzATMkTAKqM14YGFPoSKjI= golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
M http.gohttp.go

@@ -1,8 +1,11 @@

package main import ( + "database/sql" "git.sr.ht/~adnano/gmi" "github.com/gorilla/handlers" + _ "github.com/mattn/go-sqlite3" + "golang.org/x/crypto/bcrypt" "html/template" "io" "io/ioutil"

@@ -16,6 +19,7 @@ "time"

) var t *template.Template +var DB *sql.DB const InternalServerErrorMsg = "500: Internal Server Error"

@@ -211,6 +215,22 @@ // check for errors

} } +const ok = "-0123456789abcdefghijklmnopqrstuvwxyz" + +func isOkUsername(s string) bool { + if len(s) < 1 { + return false + } + if len(s) > 31 { + return false + } + for _, char := range s { + if !strings.Contains(ok, strings.ToLower(string(char))) { + return false + } + } + return true +} func registerHandler(w http.ResponseWriter, r *http.Request) { if r.Method == "GET" { data := struct {

@@ -225,6 +245,44 @@ renderError(w, InternalServerErrorMsg, 500)

return } } else if r.Method == "POST" { + r.ParseForm() + email := r.Form.Get("email") + password := r.Form.Get("password") + errors := []string{} + if !strings.Contains(email, "@") { + errors = append(errors, "Invalid Email") + } + if r.Form.Get("password") != r.Form.Get("password2") { + errors = append(errors, "Passwords don't match") + } + if len(password) < 6 { + errors = append(errors, "Password is too short") + } + username := strings.ToLower(r.Form.Get("username")) + if !isOkUsername(username) { + errors = append(errors, "Username is invalid: can only contain letters, numbers and hypens. Maximum 32 characters.") + } + hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), 8) // TODO handle error + _, err = DB.Exec("insert into user (username, email, password_hash) values ($1, $2, $3)", username, email, string(hashedPassword)) + if err != nil { + log.Println(err) + errors = append(errors, "Username or email is already used") + } + if len(errors) > 0 { + data := struct { + Domain string + Errors []string + PageTitle string + }{c.RootDomain, errors, "Register"} + t.ExecuteTemplate(w, "register.html", data) + } else { + data := struct { + Domain string + Message string + PageTitle string + }{c.RootDomain, "Registration complete! The server admin will approve your request before you can log in.", "Registration Complete"} + t.ExecuteTemplate(w, "message.html", data) + } } }
M main.gomain.go

@@ -1,6 +1,7 @@

package main import ( + "database/sql" "flag" "fmt" "io/ioutil"

@@ -15,10 +16,6 @@ "time"

) var c Config // global var to hold static configuration - -const ( // todo make configurable - userFilesPath = "./files" -) type File struct { Creator string

@@ -50,7 +47,7 @@ }

func getIndexFiles() ([]*File, error) { // cache this function result := []*File{} - err := filepath.Walk(userFilesPath, func(path string, info os.FileInfo, err error) error { + err := filepath.Walk(c.FilesDirectory, func(path string, info os.FileInfo, err error) error { if err != nil { log.Printf("Failure accessing a path %q: %v\n", path, err) return err // think about

@@ -79,7 +76,7 @@ } // todo clean up paths

func getUserFiles(user string) ([]*File, error) { result := []*File{} - files, err := ioutil.ReadDir(path.Join(userFilesPath, user)) + files, err := ioutil.ReadDir(path.Join(c.FilesDirectory, user)) if err != nil { return nil, err }

@@ -97,6 +94,11 @@ func main() {

configPath := flag.String("c", "flounder.toml", "path to config file") var err error c, err = getConfig(*configPath) + if err != nil { + log.Fatal(err) + } + + DB, err = sql.Open("sqlite3", c.DBFile) if err != nil { log.Fatal(err) }
A schema.sql

@@ -0,0 +1,9 @@

+CREATE TABLE user ( + id INTEGER PRIMARY KEY NOT NULL, + username TEXT NOT NULL UNIQUE, + email TEXT NOT NULL UNIQUE, + password_hash TEXT NOT NULL, + approved boolean NOT NULL DEFAULT false, + created_at INTEGER DEFAULT (strftime('%s', 'now')) +); +
A templates/message.html

@@ -0,0 +1,5 @@

+{{template "header" .}} +<h1>{{.PageTitle}}</h1> +{{ .Message }} +<a href="https://{{.Domain}}">Go home</a> +{{template "footer" .}}
M templates/register.htmltemplates/register.html

@@ -2,7 +2,7 @@ {{template "header" .}}

<h1>Register</h1> <form action="/register" method="post"> <div> - <label for="username">Username</label> + <label for="username">Username</label><br> <input id="username" name="username"

@@ -17,13 +17,13 @@ <input id="email" name="email" size="64" type="text" value="" />

</div> <div> <label for="password">Password</label> - <input id="password" name="password" size="32" type="password" value="" /> + <input id="password" name="password" size="55" type="password" value="" /> </div> <div> <label for="password2">Repeat Password</label> - <input id="password2" name="password2" size="32" type="password" value="" /> + <input id="password2" name="password2" size="55" type="password" value="" /> </div> - <div class="error">{{ range .Errors}}<p>{{.}}</p>{{end}} </div> + <div class="error">{{ range .Errors}}{{.}}<br>{{end}} </div> <div> <input class="button"