all repos — flounder @ 3d84831d6976655373cf07b2c74052df69d2b7cb

A small site builder for the Gemini protocol

Basic (untested, probably insecure) auth WIP
alex wennerberg alex@alexwennerberg.com
Thu, 22 Oct 2020 00:20:40 -0700
commit

3d84831d6976655373cf07b2c74052df69d2b7cb

parent

e7763a0743e1b07d9e9b6925848975597bdfd3f3

9 files changed, 135 insertions(+), 1 deletions(-)

jump to
M README.mdREADME.md

@@ -4,6 +4,7 @@ A lightweight server to help users build simple Gemini sites over http(s)

Designed to help make the Gemini ecosystem more accessible. + ## Hosting Very simple to host -- a single binary with a gemini server, http server included.
A auth.go

@@ -0,0 +1,43 @@

+package main + +import ( + "bufio" + "fmt" + "golang.org/x/crypto/bcrypt" + "os" + "strings" +) + +func addUser(username string, password string) error { + file, err := os.OpenFile(c.PasswdFile, os.O_APPEND|os.O_CREATE, 0644) + if err != nil { + return err + } + defer file.Close() + hash, err := bcrypt.GenerateFromPassword([]byte(password), -1) + if err != nil { + return err + } + newUser := fmt.Sprintf("%s:%s\n", username, hash) + file.WriteString(newUser) + return nil +} +func checkAuth(username string, password string) error { + file, err := os.OpenFile(c.PasswdFile, os.O_CREATE, 0644) + if err != nil { + return err + } + defer file.Close() + scanner := bufio.NewScanner(file) + for scanner.Scan() { + line := scanner.Text() + parts := strings.Split(line, ":") + if len(parts) != 2 { + return fmt.Errorf("malformed line, no colon: %s", line) + } + if username == parts[0] { + return bcrypt.CompareHashAndPassword([]byte(parts[1]), []byte(password)) + } + } + return fmt.Errorf("User not found") +}
M config.goconfig.go

@@ -10,6 +10,7 @@ RootDomain string

SiteTitle string Debug bool SecretKey string + PasswdFile string } func getConfig(filename string) (Config, error) {
M flounder.tomlflounder.toml

@@ -1,4 +1,4 @@

SiteTitle="🐟flounder" RootDomain="localhost" FilesDirectory="./files" - +PasswdFile="accounts.htpasswd"
M gemini.gogemini.go

@@ -39,6 +39,7 @@ func gmiPage(w *gmi.ResponseWriter, r *gmi.Request) {

userName := strings.Split(r.URL.Host, ".")[0] fileName := path.Join(c.FilesDirectory, userName, r.URL.Path) data, err := ioutil.ReadFile(fileName) + // TODO write mimetype if err != nil { // TODO return 404 equivalent log.Fatal(err)
M go.modgo.mod

@@ -5,5 +5,8 @@

require ( git.sr.ht/~adnano/gmi v0.1.0-alpha.2 github.com/BurntSushi/toml v0.3.1 + github.com/foomo/htpasswd v0.0.0-20200116085101-e3a90e78da9c // TODO audit properly github.com/mattn/go-sqlite3 v1.14.4 + github.com/tg123/go-htpasswd v1.0.0 + golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d )
M go.sumgo.sum

@@ -4,7 +4,21 @@ git.sr.ht/~adnano/gmi v0.1.0-alpha.2 h1:5/wzImYT3mJmZ27lazJ8YAdpiVN3QNJruLX7PXOITeo=

git.sr.ht/~adnano/gmi v0.1.0-alpha.2/go.mod h1:t/m2KtH+7lXIF7jjVN+bNvwPbE0nxHOpvlA/WZ/KeLQ= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/GehirnInc/crypt v0.0.0-20190301055215-6c0105aabd46 h1:rs0kDBt2zF4/CM9rO5/iH+U22jnTygPlqWgX55Ufcxg= +github.com/GehirnInc/crypt v0.0.0-20190301055215-6c0105aabd46/go.mod h1:kC29dT1vFpj7py2OvG1khBdQpo3kInWP+6QipLbdngo= +github.com/foomo/htpasswd v0.0.0-20200116085101-e3a90e78da9c h1:DBGU7zCwrrPPDsD6+gqKG8UfMxenWg9BOJE/Nmfph+4= +github.com/foomo/htpasswd v0.0.0-20200116085101-e3a90e78da9c/go.mod h1:SHawtolbB0ZOFoRWgDwakX5WpwuIWAK88bUXVZqK0Ss= github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/mattn/go-sqlite3 v1.14.4 h1:4rQjbDxdu9fSgI/r3KN72G3c2goxknAqHHgPWWs8UlI= github.com/mattn/go-sqlite3 v1.14.4/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI= +github.com/tg123/go-htpasswd v1.0.0 h1:Ze/pZsz73JiCwXIyJBPvNs75asKBgfodCf8iTEkgkXs= +github.com/tg123/go-htpasswd v1.0.0/go.mod h1:eQTgl67UrNKQvEPKrDLGBssjVwYQClFZjALVLhIv8C0= +golang.org/x/crypto v0.0.0-20190228161510-8dd112bcdc25/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +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= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
M http.gohttp.go

@@ -68,10 +68,49 @@ fmt.Fprintf(w, "%s\n", file.Name)

} } +func loginHandler(w http.ResponseWriter, r *http.Request) { + if r.Method == "GET" { + // show page + data := struct { + Error error + PageTitle string + }{nil, c.SiteTitle} + err := t.ExecuteTemplate(w, "login.html", data) + if err != nil { + log.Println(err) + renderError(w, InternalServerErrorMsg, 500) + return + } + } else if r.Method == "POST" { + r.ParseForm() + name := r.Form.Get("username") + password := r.Form.Get("password") + err := checkAuth(name, password) + if err == nil { + log.Println("logged in") + // redirect home + } else { + log.Println(err) + } + // create session + // redirect home + // verify login + // check for errors + } +} + +func register(w http.ResponseWriter, r *http.Request) { + if r.Method == "GET" { + } else if r.Method == "POST" { + } +} + // Server a user's file func userFile(w http.ResponseWriter, r *http.Request) { userName := strings.Split(r.Host, ".")[0] fileName := path.Join(c.FilesDirectory, userName, r.URL.Path) + // if gemini -- parse, convert, serve + // else http.ServeFile(w, r, fileName) }

@@ -85,8 +124,11 @@ }

http.HandleFunc(c.RootDomain+"/", indexHandler) http.HandleFunc(c.RootDomain+"/my_site", mySiteHandler) http.HandleFunc(c.RootDomain+"/edit/", editFileHandler) + http.HandleFunc(c.RootDomain+"/login", loginHandler) // http.HandleFunc("/delete/", deleteFileHandler) // login+register functions + + // handle user files based on subdomain http.HandleFunc("/", userFile) log.Fatal(http.ListenAndServe(":8080", logRequest(http.DefaultServeMux))) }

@@ -94,5 +136,6 @@

func logRequest(handler http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { log.Printf("%s %s %s\n", r.RemoteAddr, r.Method, r.URL) + handler.ServeHTTP(w, r) }) }
A templates/login.html

@@ -0,0 +1,28 @@

+{{template "header" .}} +<h1>{{.PageTitle}}</h1> +<h1>Log in</h1> +<form action="/login" method="post"> + <p> + <label for="username">Username</label> + <input id="username" name="username" size="32" type="text" value="" /> + </p> + <p> + <label for="password">Password</label> + <input id="password" name="password" size="32" type="password" value="" /> + </p> + {{ if .Error }} + <div class="error"> + <p>{{.}}</p> + </div> + {{ end}} + <p> + <input + class="button" + id="submit" + name="submit" + type="submit" + value="Log in" + /> + </p> +</form> +{{template "footer" .}}