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:
parent
3ee21e312d
commit
49b0a35304
273
web/admin.go
273
web/admin.go
@ -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")
|
|
||||||
}
|
}
|
||||||
|
41
web/web.go
41
web/web.go
@ -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")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user