From 5f592acc555fa32914d25af6001d3c876c32145b Mon Sep 17 00:00:00 2001 From: Jackson Date: Mon, 27 Nov 2023 14:55:46 +0100 Subject: [PATCH] implement user facing password change page similar to the admin password change, but with a few changes: - require current password verification the following still included: - empty password check - confirm password check --- settings/settings.go | 62 ++++++++++++++++++++++++++++++ settings/view.go | 47 ++++++++++++++++++++++ settings/view_change_password.html | 37 ++++++++++++++++++ web/web.go | 6 +++ 4 files changed, 152 insertions(+) create mode 100644 settings/settings.go create mode 100644 settings/view.go create mode 100644 settings/view_change_password.html diff --git a/settings/settings.go b/settings/settings.go new file mode 100644 index 0000000..fbb3eca --- /dev/null +++ b/settings/settings.go @@ -0,0 +1,62 @@ +package settings + +import ( + "fmt" + "mime" + "net/http" + "reflect" + + "github.com/bouncepaw/mycorrhiza/viewutil" + + "github.com/bouncepaw/mycorrhiza/user" + "github.com/bouncepaw/mycorrhiza/util" +) + +func handlerUserChangePassword(w http.ResponseWriter, rq *http.Request) { + u := user.FromRequest(rq) + // TODO: is there a better way? + if reflect.DeepEqual(u, user.EmptyUser()) || u == nil { + util.HTTP404Page(w, "404 page not found") + return + } + + f := util.FormDataFromRequest(rq, []string{"current_password", "password", "password_confirm"}) + currentPassword := f.Get("current_password") + + if user.CredentialsOK(u.Name, currentPassword) { + password := f.Get("password") + passwordConfirm := f.Get("password_confirm") + // server side validation + if password == "" { + err := fmt.Errorf("passwords should not be empty") + f = f.WithError(err) + } + if password == passwordConfirm { + previousPassword := u.Password // for rollback + if err := u.ChangePassword(password); err != nil { + f = f.WithError(err) + } else { + if err := user.SaveUserDatabase(); err != nil { + u.Password = previousPassword + f = f.WithError(err) + } else { + http.Redirect(w, rq, "/", http.StatusSeeOther) + return + } + } + } else { + err := fmt.Errorf("passwords do not match") + f = f.WithError(err) + } + } else { + err := fmt.Errorf("incorrect password") + f = f.WithError(err) + } + + if f.HasError() { + w.WriteHeader(http.StatusBadRequest) + } + w.Header().Set("Content-Type", mime.TypeByExtension(".html")) + + changePasswordPage(viewutil.MetaFrom(w, rq), f, u) +} diff --git a/settings/view.go b/settings/view.go new file mode 100644 index 0000000..338447c --- /dev/null +++ b/settings/view.go @@ -0,0 +1,47 @@ +package settings + +import ( + "embed" + "github.com/bouncepaw/mycorrhiza/util" + "github.com/bouncepaw/mycorrhiza/viewutil" + "github.com/gorilla/mux" + "net/http" + + "github.com/bouncepaw/mycorrhiza/user" +) + +// TODO: translate untranslated strings +const settingsTranslationRu = ` +{{define "change password"}}Change password{{end}} +{{define "confirm password"}}Confirm password{{end}} +{{define "current password"}}Current password{{end}} +{{define "non local password change"}}Non-local accounts cannot have their passwords changed.{{end}} +{{define "password"}}Password{{end}} +{{define "submit"}}Submit{{end}} +` + +var ( + //go:embed *.html + fs embed.FS + changePassowrdChain viewutil.Chain +) + +func Init(rtr *mux.Router) { + rtr.HandleFunc("/change-password", handlerUserChangePassword).Methods(http.MethodGet, http.MethodPost) + + changePassowrdChain = viewutil.CopyEnRuWith(fs, "view_change_password.html", settingsTranslationRu) +} + +func changePasswordPage(meta viewutil.Meta, form util.FormData, u *user.User) { + viewutil.ExecutePage(meta, changePassowrdChain, changePasswordData{ + BaseData: &viewutil.BaseData{}, + Form: form, + U: u, + }) +} + +type changePasswordData struct { + *viewutil.BaseData + Form util.FormData + U *user.User +} diff --git a/settings/view_change_password.html b/settings/view_change_password.html new file mode 100644 index 0000000..26a15af --- /dev/null +++ b/settings/view_change_password.html @@ -0,0 +1,37 @@ +{{/* TODO: translate title? */}} +{{define "title"}}Change password{{end}} +{{define "body"}} +
+ {{if .Form.HasError}} +
+ {{template "error"}}: + {{.Form.Error}} +
+ {{end}} + +

{{block "change password" .}}Change password{{end}}

+ + {{if eq .U.Source "local"}} +
+
+ + +
+
+ + +
+
+ + +
+ +
+ +
+
+ {{else}} +

{{block "non local password change" .}}Non-local accounts cannot have their passwords changed.{{end}}

+ {{end}} +
+{{end}} diff --git a/web/web.go b/web/web.go index 299f9e5..3ebfc14 100644 --- a/web/web.go +++ b/web/web.go @@ -7,6 +7,7 @@ import ( "net/url" "github.com/bouncepaw/mycorrhiza/admin" + "github.com/bouncepaw/mycorrhiza/settings" "github.com/bouncepaw/mycorrhiza/auth" "github.com/bouncepaw/mycorrhiza/backlinks" "github.com/bouncepaw/mycorrhiza/categories" @@ -67,6 +68,11 @@ func Handler() http.Handler { adminRouter := wikiRouter.PathPrefix("/admin").Subrouter() adminRouter.Use(groupMiddleware("admin")) admin.Init(adminRouter) + + settingsRouter := wikiRouter.PathPrefix("/settings").Subrouter() + // TODO: check if necessary? + //settingsRouter.Use(groupMiddleware("settings")) + settings.Init(settingsRouter) } // Index page