diff --git a/cfg/config.go b/cfg/config.go index affc8eb..fd784a6 100644 --- a/cfg/config.go +++ b/cfg/config.go @@ -23,13 +23,13 @@ var ( UserHypha string HeaderLinksHypha string - ListenAddr string - URL string - GeminiCertificatePath string + ListenAddr string + URL string UseAuth bool AllowRegistration bool RegistrationLimit uint64 + Locked bool CommonScripts []string ViewScripts []string @@ -82,6 +82,7 @@ type Authorization struct { UseAuth bool AllowRegistration bool RegistrationLimit uint64 `comment:"This field controls the maximum amount of allowed registrations."` + Locked bool `comment:"Set if users have to authorize to see anything on the wiki."` } // ReadConfigFile reads a config on the given path and stores the @@ -103,6 +104,7 @@ func ReadConfigFile(path string) error { UseAuth: false, AllowRegistration: false, RegistrationLimit: 0, + Locked: false, }, CustomScripts: CustomScripts{ CommonScripts: []string{}, @@ -135,7 +137,9 @@ func ReadConfigFile(path string) error { // Map the config file to the config struct. It'll do nothing if the file // doesn't exist or is empty. - f.MapTo(cfg) + if err := f.MapTo(cfg); err != nil { + return err + } // Map the struct to the global variables WikiName = cfg.WikiName @@ -150,6 +154,7 @@ func ReadConfigFile(path string) error { UseAuth = cfg.UseAuth AllowRegistration = cfg.AllowRegistration RegistrationLimit = cfg.RegistrationLimit + Locked = cfg.Locked && cfg.UseAuth // Makes no sense to have the lock but no auth CommonScripts = cfg.CommonScripts ViewScripts = cfg.ViewScripts EditScripts = cfg.EditScripts diff --git a/user/user.go b/user/user.go index 70db3e6..d9cbfac 100644 --- a/user/user.go +++ b/user/user.go @@ -1,6 +1,7 @@ package user import ( + "net/http" "sync" "time" @@ -96,3 +97,12 @@ func (user *User) isCorrectPassword(password string) bool { err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password)) return err == nil } + +// ShowLockMaybe redirects to the lock page if the user is anon and the wiki has been configured to use the lock. It returns true if the user was redirected. +func (user *User) ShowLockMaybe(w http.ResponseWriter, rq *http.Request) bool { + if cfg.Locked && user.Group == "anon" { + http.Redirect(w, rq, "/lock", http.StatusSeeOther) + return true + } + return false +} diff --git a/web/admin.go b/web/admin.go index 1be4c75..b22cf07 100644 --- a/web/admin.go +++ b/web/admin.go @@ -30,6 +30,9 @@ func initAdmin() { // handlerAdmin provides the admin panel. func handlerAdmin(w http.ResponseWriter, rq *http.Request) { util.PrepareRq(rq) + if shown := user.FromRequest(rq).ShowLockMaybe(w, rq); shown { + return + } if user.CanProceed(rq, "admin") { w.Header().Set("Content-Type", "text/html;charset=utf-8") w.WriteHeader(http.StatusOK) @@ -43,6 +46,9 @@ func handlerAdmin(w http.ResponseWriter, rq *http.Request) { // handlerAdminShutdown kills the wiki. func handlerAdminShutdown(w http.ResponseWriter, rq *http.Request) { util.PrepareRq(rq) + if shown := user.FromRequest(rq).ShowLockMaybe(w, rq); shown { + return + } if user.CanProceed(rq, "admin/shutdown") && rq.Method == "POST" { log.Fatal("An admin commanded the wiki to shutdown") } @@ -51,6 +57,9 @@ func handlerAdminShutdown(w http.ResponseWriter, rq *http.Request) { // handlerAdminReindexUsers reinitialises the user system. func handlerAdminReindexUsers(w http.ResponseWriter, rq *http.Request) { util.PrepareRq(rq) + if shown := user.FromRequest(rq).ShowLockMaybe(w, rq); shown { + return + } if user.CanProceed(rq, "admin") && rq.Method == "POST" { user.ReadUsersFromFilesystem() redirectTo := rq.Referer() @@ -61,10 +70,13 @@ func handlerAdminReindexUsers(w http.ResponseWriter, rq *http.Request) { } } -func handlerAdminUsers(w http.ResponseWriter, r *http.Request) { - util.PrepareRq(r) - if user.CanProceed(r, "admin") { - path := strings.TrimPrefix(r.URL.Path, "/admin/users") +func handlerAdminUsers(w http.ResponseWriter, rq *http.Request) { + util.PrepareRq(rq) + if shown := user.FromRequest(rq).ShowLockMaybe(w, rq); shown { + return + } + if user.CanProceed(rq, "admin") { + path := strings.TrimPrefix(rq.URL.Path, "/admin/users") parts := strings.Split(path, "/")[1:] // Users dashboard @@ -81,7 +93,7 @@ func handlerAdminUsers(w http.ResponseWriter, r *http.Request) { }) html := views.AdminUsersPanelHTML(userList) - html = views.BaseHTML("Manage users", html, user.FromRequest(r)) + 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 { @@ -103,9 +115,9 @@ func handlerAdminUsers(w http.ResponseWriter, r *http.Request) { switch parts[1] { case "edit": - f := util.FormDataFromRequest(r, []string{"group"}) + f := util.FormDataFromRequest(rq, []string{"group"}) - if r.Method == http.MethodPost { + if rq.Method == http.MethodPost { oldGroup := u.Group newGroup := f.Get("group") @@ -116,7 +128,7 @@ func handlerAdminUsers(w http.ResponseWriter, r *http.Request) { log.Println(err) f = f.WithError(err) } else { - http.Redirect(w, r, "/admin/users/", http.StatusSeeOther) + http.Redirect(w, rq, "/admin/users/", http.StatusSeeOther) return } } else { @@ -127,7 +139,7 @@ func handlerAdminUsers(w http.ResponseWriter, r *http.Request) { f.Put("group", u.Group) html := views.AdminUserEditHTML(u, f) - html = views.BaseHTML(fmt.Sprintf("User %s", u.Name), html, user.FromRequest(r)) + html = views.BaseHTML(fmt.Sprintf("User %s", u.Name), html, user.FromRequest(rq)) if f.HasError() { w.WriteHeader(http.StatusBadRequest) @@ -138,17 +150,17 @@ func handlerAdminUsers(w http.ResponseWriter, r *http.Request) { case "delete": f := util.NewFormData() - if r.Method == http.MethodPost { + if rq.Method == http.MethodPost { f = f.WithError(user.DeleteUser(u.Name)) if !f.HasError() { - http.Redirect(w, r, "/admin/users/", http.StatusSeeOther) + 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(r)) + html = views.BaseHTML(fmt.Sprintf("User %s", u.Name), html, user.FromRequest(rq)) if f.HasError() { w.WriteHeader(http.StatusBadRequest) @@ -162,32 +174,35 @@ func handlerAdminUsers(w http.ResponseWriter, r *http.Request) { } } -func handlerAdminUserNew(w http.ResponseWriter, r *http.Request) { - util.PrepareRq(r) - if user.CanProceed(r, "admin") { - if r.Method == http.MethodGet { +func handlerAdminUserNew(w http.ResponseWriter, rq *http.Request) { + util.PrepareRq(rq) + if shown := user.FromRequest(rq).ShowLockMaybe(w, rq); shown { + return + } + if user.CanProceed(rq, "admin") { + if rq.Method == http.MethodGet { // New user form html := views.AdminUserNewHTML(util.NewFormData()) - html = views.BaseHTML("New user", html, user.FromRequest(r)) + html = views.BaseHTML("New user", html, user.FromRequest(rq)) w.Header().Set("Content-Type", mime.TypeByExtension(".html")) io.WriteString(w, html) return - } else if r.Method == http.MethodPost { + } else if rq.Method == http.MethodPost { // Create a user - f := util.FormDataFromRequest(r, []string{"name", "password", "group"}) + f := util.FormDataFromRequest(rq, []string{"name", "password", "group"}) err := user.Register(f.Get("name"), f.Get("password"), f.Get("group"), true) if err != nil { html := views.AdminUserNewHTML(f.WithError(err)) - html = views.BaseHTML("New user", html, user.FromRequest(r)) + 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, r, "/admin/users/", http.StatusSeeOther) + http.Redirect(w, rq, "/admin/users/", http.StatusSeeOther) } return } diff --git a/web/auth.go b/web/auth.go index 7b5fba8..e74e174 100644 --- a/web/auth.go +++ b/web/auth.go @@ -33,6 +33,9 @@ func handlerLock(w http.ResponseWriter, rq *http.Request) { // handlerRegister both displays the register form (GET) and registers users (POST). func handlerRegister(w http.ResponseWriter, rq *http.Request) { + if shown := user.FromRequest(rq).ShowLockMaybe(w, rq); shown { + return + } util.PrepareRq(rq) if !cfg.AllowRegistration { w.WriteHeader(http.StatusForbidden) @@ -102,6 +105,9 @@ func handlerLogoutConfirm(w http.ResponseWriter, rq *http.Request) { // handlerLogin shows the login form. func handlerLogin(w http.ResponseWriter, rq *http.Request) { + if shown := user.FromRequest(rq).ShowLockMaybe(w, rq); shown { + return + } util.PrepareRq(rq) w.Header().Set("Content-Type", "text/html;charset=utf-8") if cfg.UseAuth { diff --git a/web/history.go b/web/history.go index 0ad71a8..eb5e2f8 100644 --- a/web/history.go +++ b/web/history.go @@ -24,6 +24,9 @@ func initHistory() { // handlerHistory lists all revisions of a hypha. func handlerHistory(w http.ResponseWriter, rq *http.Request) { util.PrepareRq(rq) + if shown := user.FromRequest(rq).ShowLockMaybe(w, rq); shown { + return + } hyphaName := util.HyphaNameFromRq(rq, "history") var list string @@ -41,6 +44,9 @@ func handlerHistory(w http.ResponseWriter, rq *http.Request) { // handlerRecentChanges displays the /recent-changes/ page. func handlerRecentChanges(w http.ResponseWriter, rq *http.Request) { util.PrepareRq(rq) + if shown := user.FromRequest(rq).ShowLockMaybe(w, rq); shown { + return + } var ( noPrefix = strings.TrimPrefix(rq.URL.String(), "/recent-changes/") n, err = strconv.Atoi(noPrefix) @@ -55,6 +61,9 @@ func handlerRecentChanges(w http.ResponseWriter, rq *http.Request) { // genericHandlerOfFeeds is a helper function for the web feed handlers. func genericHandlerOfFeeds(w http.ResponseWriter, rq *http.Request, f func() (string, error), name string) { util.PrepareRq(rq) + if shown := user.FromRequest(rq).ShowLockMaybe(w, rq); shown { + return + } if content, err := f(); err != nil { w.Header().Set("Content-Type", "text/plain;charset=utf-8") w.WriteHeader(http.StatusInternalServerError) diff --git a/web/mutators.go b/web/mutators.go index 0ec256a..a850e66 100644 --- a/web/mutators.go +++ b/web/mutators.go @@ -42,6 +42,9 @@ func factoryHandlerAsker( h = hyphae.ByName(hyphaName) u = user.FromRequest(rq) ) + if shown := u.ShowLockMaybe(w, rq); shown { + return + } if err, errtitle := asker(u, h); err != nil { httpErr( w, @@ -92,6 +95,9 @@ func factoryHandlerConfirmer( h = hyphae.ByName(hyphaName) u = user.FromRequest(rq) ) + if shown := u.ShowLockMaybe(w, rq); shown { + return + } if hop, errtitle := confirmer(h, u, rq); hop.HasErrors() { httpErr(w, http.StatusInternalServerError, hyphaName, errtitle, @@ -139,6 +145,9 @@ func handlerEdit(w http.ResponseWriter, rq *http.Request) { err error u = user.FromRequest(rq) ) + if shown := u.ShowLockMaybe(w, rq); shown { + return + } if err, errtitle := shroom.CanEdit(u, h); err != nil { httpErr(w, http.StatusInternalServerError, hyphaName, errtitle, @@ -178,6 +187,9 @@ func handlerUploadText(w http.ResponseWriter, rq *http.Request) { hop *history.HistoryOp errtitle string ) + if shown := u.ShowLockMaybe(w, rq); shown { + return + } if action != "Preview" { hop, errtitle = shroom.UploadText(h, []byte(textData), message, u) @@ -219,6 +231,9 @@ func handlerUploadBinary(w http.ResponseWriter, rq *http.Request) { u = user.FromRequest(rq) file, handler, err = rq.FormFile("binary") ) + if shown := u.ShowLockMaybe(w, rq); shown { + return + } if err != nil { httpErr(w, http.StatusInternalServerError, hyphaName, "Error", diff --git a/web/readers.go b/web/readers.go index 758f025..61f1868 100644 --- a/web/readers.go +++ b/web/readers.go @@ -37,6 +37,9 @@ func handlerAttachment(w http.ResponseWriter, rq *http.Request) { h = hyphae.ByName(hyphaName) u = user.FromRequest(rq) ) + if shown := u.ShowLockMaybe(w, rq); shown { + return + } util.HTTP200Page(w, views.BaseHTML( fmt.Sprintf("Attachment of %s", util.BeautifulName(hyphaName)), @@ -54,6 +57,9 @@ func handlerPrimitiveDiff(w http.ResponseWriter, rq *http.Request) { h = hyphae.ByName(hyphaName) u = user.FromRequest(rq) ) + if shown := u.ShowLockMaybe(w, rq); shown { + return + } util.HTTP200Page(w, views.BaseHTML( fmt.Sprintf("Diff of %s at %s", hyphaName, revHash), @@ -74,6 +80,9 @@ func handlerRevision(w http.ResponseWriter, rq *http.Request) { textContents, err = history.FileAtRevision(h.TextPath, revHash) u = user.FromRequest(rq) ) + if shown := u.ShowLockMaybe(w, rq); shown { + return + } if err == nil { ctx, _ := mycocontext.ContextFromStringInput(hyphaName, textContents) contents = mycomarkup.BlocksToHTML(ctx, mycomarkup.BlockTree(ctx)) @@ -92,6 +101,9 @@ func handlerRevision(w http.ResponseWriter, rq *http.Request) { // handlerText serves raw source text of the hypha. func handlerText(w http.ResponseWriter, rq *http.Request) { util.PrepareRq(rq) + if shown := user.FromRequest(rq).ShowLockMaybe(w, rq); shown { + return + } hyphaName := util.HyphaNameFromRq(rq, "text") if h := hyphae.ByName(hyphaName); h.Exists { log.Println("Serving", h.TextPath) @@ -103,6 +115,9 @@ func handlerText(w http.ResponseWriter, rq *http.Request) { // handlerBinary serves binary part of the hypha. func handlerBinary(w http.ResponseWriter, rq *http.Request) { util.PrepareRq(rq) + if shown := user.FromRequest(rq).ShowLockMaybe(w, rq); shown { + return + } hyphaName := util.HyphaNameFromRq(rq, "binary") if h := hyphae.ByName(hyphaName); h.Exists { log.Println("Serving", h.BinaryPath) @@ -121,6 +136,9 @@ func handlerHypha(w http.ResponseWriter, rq *http.Request) { openGraph string u = user.FromRequest(rq) ) + if shown := u.ShowLockMaybe(w, rq); shown { + return + } if h.Exists { fileContentsT, errT := os.ReadFile(h.TextPath) _, errB := os.Stat(h.BinaryPath) diff --git a/web/stuff.go b/web/stuff.go index 9928fb8..bf9ed49 100644 --- a/web/stuff.go +++ b/web/stuff.go @@ -30,13 +30,20 @@ func initStuff() { // handlerList shows a list of all hyphae in the wiki in random order. func handlerList(w http.ResponseWriter, rq *http.Request) { + u := user.FromRequest(rq) + if shown := u.ShowLockMaybe(w, rq); shown { + return + } util.PrepareRq(rq) - util.HTTP200Page(w, views.BaseHTML("List of pages", views.HyphaListHTML(), user.FromRequest(rq))) + util.HTTP200Page(w, views.BaseHTML("List of pages", views.HyphaListHTML(), u)) } // handlerReindex reindexes all hyphae by checking the wiki storage directory anew. func handlerReindex(w http.ResponseWriter, rq *http.Request) { util.PrepareRq(rq) + if shown := user.FromRequest(rq).ShowLockMaybe(w, rq); shown { + return + } if ok := user.CanProceed(rq, "reindex"); !ok { httpErr(w, http.StatusForbidden, cfg.HomeHypha, "Not enough rights", "You must be an admin to reindex hyphae.") log.Println("Rejected", rq.URL) @@ -54,6 +61,9 @@ func handlerReindex(w http.ResponseWriter, rq *http.Request) { // See https://mycorrhiza.wiki/hypha/configuration/header func handlerUpdateHeaderLinks(w http.ResponseWriter, rq *http.Request) { util.PrepareRq(rq) + if shown := user.FromRequest(rq).ShowLockMaybe(w, rq); shown { + return + } if ok := user.CanProceed(rq, "update-header-links"); !ok { httpErr(w, http.StatusForbidden, cfg.HomeHypha, "Not enough rights", "You must be a moderator to update header links.") log.Println("Rejected", rq.URL) @@ -66,6 +76,9 @@ func handlerUpdateHeaderLinks(w http.ResponseWriter, rq *http.Request) { // handlerRandom redirects to a random hypha. func handlerRandom(w http.ResponseWriter, rq *http.Request) { util.PrepareRq(rq) + if shown := user.FromRequest(rq).ShowLockMaybe(w, rq); shown { + return + } var ( randomHyphaName string amountOfHyphae = hyphae.Count() @@ -87,6 +100,9 @@ func handlerRandom(w http.ResponseWriter, rq *http.Request) { // handlerAbout shows a summary of wiki's software. func handlerAbout(w http.ResponseWriter, rq *http.Request) { + if shown := user.FromRequest(rq).ShowLockMaybe(w, rq); shown { + return + } w.Header().Set("Content-Type", "text/html;charset=utf-8") w.WriteHeader(http.StatusOK) _, err := io.WriteString(w, views.BaseHTML("About "+cfg.WikiName, views.AboutHTML(), user.FromRequest(rq))) diff --git a/web/web.go b/web/web.go index 478562a..a2c6741 100644 --- a/web/web.go +++ b/web/web.go @@ -54,6 +54,9 @@ func handlerStyle(w http.ResponseWriter, rq *http.Request) { } func handlerUserList(w http.ResponseWriter, rq *http.Request) { + if shown := user.FromRequest(rq).ShowLockMaybe(w, rq); shown { + return + } w.Header().Set("Content-Type", mime.TypeByExtension(".html")) w.WriteHeader(http.StatusOK) w.Write([]byte(views.BaseHTML("User list", views.UserListHTML(), user.FromRequest(rq))))