1
0
mirror of https://github.com/osmarks/mycorrhiza.git synced 2025-08-05 05:13:50 +00:00

Refactor admin routes

They're not perfect, I still don't like them, but I can't think of a
good solution right now. I'm going to thinking about the best way of
doing web stuff for some time.
This commit is contained in:
handlerug 2021-07-16 01:58:27 +07:00
parent 3ee21e312d
commit 49b0a35304
2 changed files with 154 additions and 160 deletions

View File

@ -2,12 +2,12 @@ package web
import ( import (
"fmt" "fmt"
"os"
"io" "io"
"log" "log"
"mime" "mime"
"net/http" "net/http"
"sort" "sort"
"strings"
"github.com/gorilla/mux" "github.com/gorilla/mux"
@ -19,181 +19,154 @@ import (
// initAdmin sets up /admin routes if auth is used. Call it after you have decided if you want to use auth. // initAdmin sets up /admin routes if auth is used. Call it after you have decided if you want to use auth.
func initAdmin(r *mux.Router) { func initAdmin(r *mux.Router) {
if cfg.UseAuth { r.HandleFunc("/shutdown", handlerAdminShutdown).Methods(http.MethodPost)
r.HandleFunc("/admin/shutdown", handlerAdminShutdown) r.HandleFunc("/reindex-users", handlerAdminReindexUsers).Methods(http.MethodPost)
r.HandleFunc("/admin/reindex-users", handlerAdminReindexUsers)
r.PathPrefix("/admin/users/").HandlerFunc(handlerAdminUsers) r.HandleFunc("/user/new", handlerAdminUserNew).Methods(http.MethodGet, http.MethodPost)
r.HandleFunc("/admin/user/new", handlerAdminUserNew) r.HandleFunc("/users/{username}/edit", handlerAdminUserEdit).Methods(http.MethodGet, http.MethodPost)
r.HandleFunc("/admin", handlerAdmin) r.HandleFunc("/users/{username}/delete", handlerAdminUserDelete).Methods(http.MethodGet, http.MethodPost)
} r.HandleFunc("/users", handlerAdminUsers)
r.HandleFunc("/", handlerAdmin)
} }
// handlerAdmin provides the admin panel. // handlerAdmin provides the admin panel.
func handlerAdmin(w http.ResponseWriter, rq *http.Request) { func handlerAdmin(w http.ResponseWriter, rq *http.Request) {
util.PrepareRq(rq) w.Header().Set("Content-Type", "text/html;charset=utf-8")
if user.CanProceed(rq, "admin") { w.WriteHeader(http.StatusOK)
w.Header().Set("Content-Type", "text/html;charset=utf-8") io.WriteString(w, views.BaseHTML("Admin panel", views.AdminPanelHTML(), user.FromRequest(rq)))
w.WriteHeader(http.StatusOK)
_, err := io.WriteString(w, views.BaseHTML("Admin panel", views.AdminPanelHTML(), user.FromRequest(rq)))
if err != nil {
log.Println(err)
}
}
} }
// handlerAdminShutdown kills the wiki. // handlerAdminShutdown kills the wiki.
func handlerAdminShutdown(w http.ResponseWriter, rq *http.Request) { func handlerAdminShutdown(w http.ResponseWriter, rq *http.Request) {
util.PrepareRq(rq) if user.CanProceed(rq, "admin/shutdown") {
if user.CanProceed(rq, "admin/shutdown") && rq.Method == "POST" { log.Println("An admin commanded the wiki to shutdown")
log.Fatal("An admin commanded the wiki to shutdown") os.Exit(0)
} }
} }
// handlerAdminReindexUsers reinitialises the user system. // handlerAdminReindexUsers reinitialises the user system.
func handlerAdminReindexUsers(w http.ResponseWriter, rq *http.Request) { func handlerAdminReindexUsers(w http.ResponseWriter, rq *http.Request) {
util.PrepareRq(rq) user.ReadUsersFromFilesystem()
if user.CanProceed(rq, "admin") && rq.Method == "POST" { redirectTo := rq.Referer()
user.ReadUsersFromFilesystem() if redirectTo == "" {
redirectTo := rq.Referer() redirectTo = "/hypha/" + cfg.UserHypha
if redirectTo == "" {
redirectTo = "/hypha/" + cfg.UserHypha
}
http.Redirect(w, rq, redirectTo, http.StatusSeeOther)
} }
http.Redirect(w, rq, redirectTo, http.StatusSeeOther)
} }
func handlerAdminUsers(w http.ResponseWriter, rq *http.Request) { func handlerAdminUsers(w http.ResponseWriter, rq *http.Request) {
util.PrepareRq(rq) // Get a sorted list of users
if user.CanProceed(rq, "admin") { var userList []*user.User
path := strings.TrimPrefix(rq.URL.Path, "/admin/users") for u := range user.YieldUsers() {
parts := strings.Split(path, "/")[1:] userList = append(userList, u)
// Users dashboard
if len(parts) == 0 {
// Get a sorted list of users
var userList []*user.User
for u := range user.YieldUsers() {
userList = append(userList, u)
}
sort.Slice(userList, func(i, j int) bool {
less := userList[i].RegisteredAt.Before(userList[j].RegisteredAt)
return less
})
html := views.AdminUsersPanelHTML(userList)
html = views.BaseHTML("Manage users", html, user.FromRequest(rq))
w.Header().Set("Content-Type", mime.TypeByExtension(".html"))
if _, err := io.WriteString(w, html); err != nil {
log.Println(err)
}
return
}
if len(parts) != 2 {
util.HTTP404Page(w, "404 page not found")
return
}
u := user.UserByName(parts[0])
if u == nil {
util.HTTP404Page(w, "404 page not found")
return
}
switch parts[1] {
case "edit":
f := util.FormDataFromRequest(rq, []string{"group"})
if rq.Method == http.MethodPost {
oldGroup := u.Group
newGroup := f.Get("group")
if user.ValidGroup(newGroup) {
u.Group = newGroup
if err := user.SaveUserDatabase(); err != nil {
u.Group = oldGroup
log.Println(err)
f = f.WithError(err)
} else {
http.Redirect(w, rq, "/admin/users/", http.StatusSeeOther)
return
}
} else {
f = f.WithError(fmt.Errorf("invalid group \"%s\"", newGroup))
}
}
f.Put("group", u.Group)
html := views.AdminUserEditHTML(u, f)
html = views.BaseHTML(fmt.Sprintf("User %s", u.Name), html, user.FromRequest(rq))
if f.HasError() {
w.WriteHeader(http.StatusBadRequest)
}
w.Header().Set("Content-Type", mime.TypeByExtension(".html"))
io.WriteString(w, html)
return
case "delete":
f := util.NewFormData()
if rq.Method == http.MethodPost {
f = f.WithError(user.DeleteUser(u.Name))
if !f.HasError() {
http.Redirect(w, rq, "/admin/users/", http.StatusSeeOther)
} else {
log.Println(f.Error())
}
}
html := views.AdminUserDeleteHTML(u, util.NewFormData())
html = views.BaseHTML(fmt.Sprintf("User %s", u.Name), html, user.FromRequest(rq))
if f.HasError() {
w.WriteHeader(http.StatusBadRequest)
}
w.Header().Set("Content-Type", mime.TypeByExtension(".html"))
io.WriteString(w, html)
return
}
util.HTTP404Page(w, "404 page not found")
} }
sort.Slice(userList, func(i, j int) bool {
less := userList[i].RegisteredAt.Before(userList[j].RegisteredAt)
return less
})
html := views.AdminUsersPanelHTML(userList)
html = views.BaseHTML("Manage users", html, user.FromRequest(rq))
w.Header().Set("Content-Type", mime.TypeByExtension(".html"))
io.WriteString(w, html)
}
func handlerAdminUserEdit(w http.ResponseWriter, rq *http.Request) {
vars := mux.Vars(rq)
u := user.UserByName(vars["username"])
if u == nil {
util.HTTP404Page(w, "404 page not found")
return
}
f := util.FormDataFromRequest(rq, []string{"group"})
if rq.Method == http.MethodPost {
oldGroup := u.Group
newGroup := f.Get("group")
if user.ValidGroup(newGroup) {
u.Group = newGroup
if err := user.SaveUserDatabase(); err != nil {
u.Group = oldGroup
log.Println(err)
f = f.WithError(err)
} else {
http.Redirect(w, rq, "/admin/users/", http.StatusSeeOther)
return
}
} else {
f = f.WithError(fmt.Errorf("invalid group \"%s\"", newGroup))
}
}
f.Put("group", u.Group)
html := views.AdminUserEditHTML(u, f)
html = views.BaseHTML(fmt.Sprintf("User %s", u.Name), html, user.FromRequest(rq))
if f.HasError() {
w.WriteHeader(http.StatusBadRequest)
}
w.Header().Set("Content-Type", mime.TypeByExtension(".html"))
io.WriteString(w, html)
}
func handlerAdminUserDelete(w http.ResponseWriter, rq *http.Request) {
vars := mux.Vars(rq)
u := user.UserByName(vars["username"])
if u == nil {
util.HTTP404Page(w, "404 page not found")
return
}
f := util.NewFormData()
if rq.Method == http.MethodPost {
f = f.WithError(user.DeleteUser(u.Name))
if !f.HasError() {
http.Redirect(w, rq, "/admin/users/", http.StatusSeeOther)
} else {
log.Println(f.Error())
}
}
html := views.AdminUserDeleteHTML(u, util.NewFormData())
html = views.BaseHTML(fmt.Sprintf("User %s", u.Name), html, user.FromRequest(rq))
if f.HasError() {
w.WriteHeader(http.StatusBadRequest)
}
w.Header().Set("Content-Type", mime.TypeByExtension(".html"))
io.WriteString(w, html)
} }
func handlerAdminUserNew(w http.ResponseWriter, rq *http.Request) { func handlerAdminUserNew(w http.ResponseWriter, rq *http.Request) {
util.PrepareRq(rq) if rq.Method == http.MethodGet {
if user.CanProceed(rq, "admin") { // New user form
if rq.Method == http.MethodGet { html := views.AdminUserNewHTML(util.NewFormData())
// New user form html = views.BaseHTML("New user", html, user.FromRequest(rq))
html := views.AdminUserNewHTML(util.NewFormData())
w.Header().Set("Content-Type", mime.TypeByExtension(".html"))
io.WriteString(w, html)
} else if rq.Method == http.MethodPost {
// Create a user
f := util.FormDataFromRequest(rq, []string{"name", "password", "group"})
err := user.Register(f.Get("name"), f.Get("password"), f.Get("group"), "local", true)
if err != nil {
html := views.AdminUserNewHTML(f.WithError(err))
html = views.BaseHTML("New user", html, user.FromRequest(rq)) html = views.BaseHTML("New user", html, user.FromRequest(rq))
w.WriteHeader(http.StatusBadRequest)
w.Header().Set("Content-Type", mime.TypeByExtension(".html")) w.Header().Set("Content-Type", mime.TypeByExtension(".html"))
io.WriteString(w, html) io.WriteString(w, html)
return } else {
} else if rq.Method == http.MethodPost { http.Redirect(w, rq, "/admin/users/", http.StatusSeeOther)
// Create a user
f := util.FormDataFromRequest(rq, []string{"name", "password", "group"})
err := user.Register(f.Get("name"), f.Get("password"), f.Get("group"), "local", true)
if err != nil {
html := views.AdminUserNewHTML(f.WithError(err))
html = views.BaseHTML("New user", html, user.FromRequest(rq))
w.WriteHeader(http.StatusBadRequest)
w.Header().Set("Content-Type", mime.TypeByExtension(".html"))
io.WriteString(w, html)
} else {
http.Redirect(w, rq, "/admin/users/", http.StatusSeeOther)
}
return
} }
} }
util.HTTP404Page(w, "404 page not found")
} }

View File

@ -65,8 +65,7 @@ func handlerRobotsTxt(w http.ResponseWriter, rq *http.Request) {
w.Header().Set("Content-Type", "text/plain; charset=utf-8") w.Header().Set("Content-Type", "text/plain; charset=utf-8")
file, err := static.FS.Open("robots.txt") file, err := static.FS.Open("robots.txt")
if err != nil { if err != nil { return
return
} }
io.Copy(w, file) io.Copy(w, file)
file.Close() file.Close()
@ -75,22 +74,21 @@ func handlerRobotsTxt(w http.ResponseWriter, rq *http.Request) {
func Handler() http.Handler { func Handler() http.Handler {
router := mux.NewRouter() router := mux.NewRouter()
router.Use(func(next http.Handler) http.Handler { router.Use(func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, rq *http.Request) {
// Do stuff here util.PrepareRq(rq)
log.Println(r.RequestURI) next.ServeHTTP(w, rq)
// Call the next handler, which can be another middleware in the chain, or the final handler.
next.ServeHTTP(w, r)
}) })
}) })
router.StrictSlash(true)
// Public routes // Public routes. They're always accessible regardless of the user status.
initAuth(router) initAuth(router)
router.HandleFunc("/robots.txt", handlerRobotsTxt) router.HandleFunc("/robots.txt", handlerRobotsTxt)
router.HandleFunc("/static/style.css", handlerStyle) router.HandleFunc("/static/style.css", handlerStyle)
router.PathPrefix("/static/"). router.PathPrefix("/static/").
Handler(http.StripPrefix("/static/", http.FileServer(http.FS(static.FS)))) Handler(http.StripPrefix("/static/", http.FileServer(http.FS(static.FS))))
// Wiki routes. They may be locked or restricted // Wiki routes. They may be locked or restricted.
wikiRouter := router.PathPrefix("").Subrouter() wikiRouter := router.PathPrefix("").Subrouter()
wikiRouter.Use(func(next http.Handler) http.Handler { wikiRouter.Use(func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, rq *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, rq *http.Request) {
@ -103,11 +101,17 @@ func Handler() http.Handler {
initReaders(wikiRouter) initReaders(wikiRouter)
initMutators(wikiRouter) initMutators(wikiRouter)
initAdmin(wikiRouter)
initHistory(wikiRouter) initHistory(wikiRouter)
initStuff(wikiRouter) initStuff(wikiRouter)
initSearch(wikiRouter) initSearch(wikiRouter)
// Admin routes.
if cfg.UseAuth {
adminRouter := wikiRouter.PathPrefix("/admin").Subrouter()
adminRouter.Use(groupMiddleware("admin"))
initAdmin(adminRouter)
}
// Miscellaneous // Miscellaneous
wikiRouter.HandleFunc("/user-list", handlerUserList) wikiRouter.HandleFunc("/user-list", handlerUserList)
@ -121,3 +125,20 @@ func Handler() http.Handler {
return router return router
} }
func groupMiddleware(group string) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, rq *http.Request) {
if cfg.UseAuth && user.CanProceed(rq, group) {
next.ServeHTTP(w, rq)
return
}
// TODO: handle this better. Merge this code with all other
// authorization code in this project.
w.WriteHeader(http.StatusForbidden)
io.WriteString(w, "403 forbidden")
})
}
}