all repos — flounder @ 9cf9bb53cbc58129e195c2ae937abce4216b3566

A small site builder for the Gemini protocol

some refactoring WIP
alex wennerberg alex@alexwennerberg.com
Sun, 06 Dec 2020 00:54:24 -0800
commit

9cf9bb53cbc58129e195c2ae937abce4216b3566

parent

4e70232a94d9c007adea317afd85d2f115160912

6 files changed, 118 insertions(+), 97 deletions(-)

jump to
M http.gohttp.go

@@ -56,32 +56,25 @@ http.ServeFile(w, r, fileName) // TODO better error handling

return } - authd, _, isAdmin := getAuthUser(r) - indexFiles, err := getIndexFiles(isAdmin) + user := newGetAuthUser(r) + indexFiles, err := getIndexFiles(user.IsAdmin) if err != nil { - log.Println(err) - renderDefaultError(w, http.StatusInternalServerError) - return + panic(err) } allUsers, err := getActiveUserNames() if err != nil { - log.Println(err) - renderDefaultError(w, http.StatusInternalServerError) - return + panic(err) } data := struct { Host string PageTitle string Files []*File Users []string - LoggedIn bool - IsAdmin bool - }{c.Host, c.SiteTitle, indexFiles, allUsers, authd, isAdmin} + AuthUser AuthUser + }{c.Host, c.SiteTitle, indexFiles, allUsers, user} err = t.ExecuteTemplate(w, "index.html", data) if err != nil { - log.Println(err) - renderDefaultError(w, http.StatusInternalServerError) - return + panic(err) } }

@@ -113,9 +106,7 @@ defer f.Close()

fileBytes, err = ioutil.ReadAll(f) } if err != nil { - log.Println(err) - renderDefaultError(w, http.StatusInternalServerError) - return + panic(err) } data := struct { FileName string

@@ -127,9 +118,7 @@ IsText bool

}{fileName, string(fileBytes), c.SiteTitle, authUser, c.Host, isText} err = t.ExecuteTemplate(w, "edit_file.html", data) if err != nil { - log.Println(err) - renderDefaultError(w, http.StatusInternalServerError) - return + panic(err) } } else if r.Method == "POST" { // get post body

@@ -156,9 +145,7 @@ renderError(w, fmt.Sprintf("Bad Request: Out of file space. Max space: %d.", c.MaxUserBytes), http.StatusBadRequest)

return } if err != nil { - log.Println(err) - renderDefaultError(w, http.StatusInternalServerError) - return + panic(err) } newName := filepath.Clean(r.Form.Get("rename")) err = checkIfValidFile(newName, fileBytes)

@@ -179,9 +166,8 @@ }

func uploadFilesHandler(w http.ResponseWriter, r *http.Request) { if r.Method == "POST" { - session, _ := SessionStore.Get(r, "cookie-session") - authUser, ok := session.Values["auth_user"].(string) - if !ok { + user := newGetAuthUser(r) + if !user.LoggedIn { renderDefaultError(w, http.StatusForbidden) return }

@@ -201,16 +187,14 @@ log.Println(err)

renderError(w, err.Error(), http.StatusBadRequest) return } - destPath := path.Join(c.FilesDirectory, authUser, fileName) + destPath := path.Join(c.FilesDirectory, user.Username, fileName) f, err := os.OpenFile(destPath, os.O_WRONLY|os.O_CREATE, 0644) if err != nil { - log.Println(err) - renderDefaultError(w, http.StatusInternalServerError) - return + panic(err) } defer f.Close() - if userHasSpace(authUser, c.MaxFileBytes) { // Not quite right + if userHasSpace(user.Username, c.MaxFileBytes) { // Not quite right io.Copy(f, bytes.NewReader(dest)) } else { renderError(w, fmt.Sprintf("Bad Request: Out of file space. Max space: %d.", c.MaxUserBytes), http.StatusBadRequest)

@@ -220,6 +204,28 @@ }

http.Redirect(w, r, "/my_site", http.StatusSeeOther) } +// TODO use this +type AuthUser struct { + LoggedIn bool + Username string + IsAdmin bool + ImpersonatingUser string // used if impersonating +} + +func newGetAuthUser(r *http.Request) AuthUser { + session, _ := SessionStore.Get(r, "cookie-session") + user, ok := session.Values["auth_user"].(string) + impers, _ := session.Values["impersonating_user"].(string) + isAdmin, _ := session.Values["admin"].(bool) + return AuthUser{ + LoggedIn: ok, + Username: user, + IsAdmin: isAdmin, + ImpersonatingUser: impers, + } +} + +//TODO deprecate func getAuthUser(r *http.Request) (bool, string, bool) { session, _ := SessionStore.Get(r, "cookie-session") user, ok := session.Values["auth_user"].(string)

@@ -227,62 +233,56 @@ isAdmin, _ := session.Values["admin"].(bool)

return ok, user, isAdmin } func deleteFileHandler(w http.ResponseWriter, r *http.Request) { - authd, authUser, _ := getAuthUser(r) - if !authd { + user := newGetAuthUser(r) + if !user.LoggedIn { renderDefaultError(w, http.StatusForbidden) return } - fileName := filepath.Clean(r.URL.Path[len("/delete/"):]) - filePath := path.Join(c.FilesDirectory, authUser, fileName) + filePath := safeGetFilePath(user.Username, r.URL.Path[len("/delete/"):]) if r.Method == "POST" { - os.Remove(filePath) // suppress error + os.Remove(filePath) // TODO handle error } http.Redirect(w, r, "/my_site", http.StatusSeeOther) } func mySiteHandler(w http.ResponseWriter, r *http.Request) { - authd, authUser, isAdmin := getAuthUser(r) - if !authd { + user := newGetAuthUser(r) + if !user.LoggedIn { renderDefaultError(w, http.StatusForbidden) return } // check auth - userFolder := path.Join(c.FilesDirectory, authUser) - files, _ := getMyFilesRecursive(userFolder, authUser) + userFolder := getUserDirectory(user.Username) + files, _ := getMyFilesRecursive(userFolder, user.Username) data := struct { Host string PageTitle string - AuthUser string Files []*File - LoggedIn bool - IsAdmin bool - }{c.Host, c.SiteTitle, authUser, files, authd, isAdmin} + AuthUser AuthUser + }{c.Host, c.SiteTitle, files, user} _ = t.ExecuteTemplate(w, "my_site.html", data) } func myAccountHandler(w http.ResponseWriter, r *http.Request) { - authd, authUser, isAdmin := getAuthUser(r) - if !authd { + user := newGetAuthUser(r) + authUser := user.Username + if !user.LoggedIn { renderDefaultError(w, http.StatusForbidden) return } - me, _ := getUserByName(authUser) + me, _ := getUserByName(user.Username) type pageData struct { PageTitle string - LoggedIn bool - AuthUser string - IsAdmin bool + AuthUser AuthUser Email string Errors []string } - data := pageData{"My Account", true, authUser, isAdmin, me.Email, nil} + data := pageData{"My Account", user, me.Email, nil} if r.Method == "GET" { err := t.ExecuteTemplate(w, "me.html", data) if err != nil { - log.Println(err) - renderDefaultError(w, http.StatusInternalServerError) - return + panic(err) } } else if r.Method == "POST" { r.ParseForm()

@@ -312,9 +312,9 @@ session.Save(r, w)

} } // reset auth - authd, authUser, isAdmin = getAuthUser(r) + user = newGetAuthUser(r) data.Errors = errors - data.AuthUser = authUser + data.AuthUser = user data.Email = newEmail _ = t.ExecuteTemplate(w, "me.html", data) }

@@ -330,9 +330,7 @@ if r.Method == "GET" {

userFolder := filepath.Join(c.FilesDirectory, filepath.Clean(authUser)) err := zipit(userFolder, w) if err != nil { - log.Println(err) - renderDefaultError(w, http.StatusInternalServerError) - return + panic(err) } }

@@ -346,9 +344,7 @@ PageTitle string

}{"", "Login"} err := t.ExecuteTemplate(w, "login.html", data) if err != nil { - log.Println(err) - renderDefaultError(w, http.StatusInternalServerError) - return + panic(err) } } else if r.Method == "POST" { r.ParseForm()

@@ -382,9 +378,7 @@ PageTitle string

}{"Invalid login or password", c.SiteTitle} err := t.ExecuteTemplate(w, "login.html", data) if err != nil { - log.Println(err) - renderDefaultError(w, http.StatusInternalServerError) - return + panic(err) } } }

@@ -422,9 +416,7 @@ PageTitle string

}{c.Host, nil, "Register"} err := t.ExecuteTemplate(w, "register.html", data) if err != nil { - log.Println(err) - renderDefaultError(w, http.StatusInternalServerError) - return + panic(err) } } else if r.Method == "POST" { r.ParseForm()

@@ -489,9 +481,7 @@ Host string

}{allUsers, true, true, "Admin", c.Host} err = t.ExecuteTemplate(w, "admin.html", data) if err != nil { - log.Println(err) - renderDefaultError(w, http.StatusInternalServerError) - return + panic(err) } }

@@ -558,13 +548,19 @@ }

func deleteAccountHandler(w http.ResponseWriter, r *http.Request) { _, authUser, _ := getAuthUser(r) - err := deleteUser(authUser) - if err != nil { - log.Println(err) - renderDefaultError(w, http.StatusInternalServerError) - return + if r.Method == "POST" { + err := deleteUser(authUser) + if err != nil { + log.Println(err) + renderDefaultError(w, http.StatusInternalServerError) + return + } + logoutHandler(w, r) } - logoutHandler(w, r) +} + +func resetPasswordHandler(w http.ResponseWriter, r *http.Request) { + getAuthUser(r) } func adminUserHandler(w http.ResponseWriter, r *http.Request) {

@@ -621,13 +617,14 @@ serveMux.HandleFunc(hostname+"/logout", logoutHandler)

serveMux.HandleFunc(hostname+"/register", registerHandler) serveMux.HandleFunc(hostname+"/delete/", deleteFileHandler) serveMux.HandleFunc(hostname+"/delete-account", deleteAccountHandler) + serveMux.HandleFunc(hostname+"/reset-password", resetPasswordHandler) // admin commands serveMux.HandleFunc(hostname+"/admin/user/", adminUserHandler) // TODO rate limit login https://github.com/ulule/limiter - wrapped := handlers.LoggingHandler(log.Writer(), serveMux) + wrapped := (handlers.LoggingHandler(log.Writer(), handlers.RecoveryHandler()(serveMux))) // handle user files based on subdomain serveMux.HandleFunc("/", userFile)
M templates/me.htmltemplates/me.html

@@ -9,7 +9,7 @@ id="username"

name="username" size="32" type="text" - value="{{.AuthUser}}" + value="{{.AuthUser.Username}}" /> </div> <div>

@@ -27,7 +27,7 @@ value="Save"

/> </div> </form> -<a href="/reset_password">Reset password</a> +<a href="/reset-password">Reset password</a> <p><a href="/my_site/flounder-archive.zip">🗄️ Download my site archive (.zip)</a></p> <form action="/delete-account" method="POST" class="inline"> <input
M templates/my_site.htmltemplates/my_site.html

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

{{$domain := .Host}} -{{$authUser := .AuthUser}} +{{$authUser := .AuthUser.Username}} {{template "header" .}} <h1>Managing <a href="//{{$authUser}}.{{$domain}}"> - {{.AuthUser}}.{{$domain}} + {{$authUser}}.{{$domain}} </a> </h1> {{template "nav.html" .}}
M templates/nav.htmltemplates/nav.html

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

<nav> <a href="/">home</a> -{{ if .LoggedIn }} +{{ if .AuthUser.LoggedIn }} <a href="/my_site">my_site</a> <a href="/me">me</a> - {{ if .IsAdmin }} + {{ if .AuthUser.IsAdmin }} <a href="/admin">admin</a> {{ end }} <a href="/logout">logout</a>
M templates/reset_pass.htmltemplates/reset_pass.html

@@ -2,29 +2,42 @@ {{template "header" .}}

<h1>Reset Password</h1> <form action="/reset_password" method="post"> <div> - <label for="username">Username</label><br> + <label for="password">Current Password</label><br> <input - id="username" - name="username" + id="password" + name="password" size="32" type="text" value="{{.AuthUser}}" /> </div> <div> - <label for="email">Email</label> - <input id="email" name="email" size="64" type="text" value="{{.Email}}" /> + <label for="new_password1">New Password</label><br> + <input + id="new_password1" + name="new_password1" + size="32" + type="text" + value="" + /> </div> - <div class="error">{{ range .Errors}}{{.}}<br>{{end}} </div> <div> + <label for="new_password2">New Password (repeat)</label><br> <input - class="button" - id="submit" - name="submit" - type="submit" - value="Save" + id="new_password2" + name="new_password2" + size="32" + type="text" + value="" /> - </div> + </div> + <input + class="button" + id="submit" + name="submit" + type="submit" + value="Change" +/> + </form> -<a href="/reset_password">Reset password</a> {{template "footer" .}}
M utils.goutils.go

@@ -40,6 +40,17 @@ return fmt.Sprintf("%d days ago", days)

} } +// safe +func getUserDirectory(username string) string { + // extra filepath.clean just to be safe + userFolder := path.Join(c.FilesDirectory, filepath.Clean(username)) + return userFolder +} + +func safeGetFilePath(username string, filename string) string { + return path.Join(getUserDirectory(username), filepath.Clean(filename)) +} + // TODO move into checkIfValidFile. rename it func userHasSpace(user string, newBytes int) bool { userPath := path.Join(c.FilesDirectory, user)