diff --git a/templates/base.html b/templates/base.html index a4b8a1c..dd3c763 100644 --- a/templates/base.html +++ b/templates/base.html @@ -77,7 +77,6 @@ .dialog label { display: flex; - margin-bottom: 1em; align-items: center; } @@ -112,6 +111,7 @@ {{ end }}
My Shares + Change Password
Logout {{ end }} diff --git a/templates/change-password.html b/templates/change-password.html new file mode 100644 index 0000000..396609d --- /dev/null +++ b/templates/change-password.html @@ -0,0 +1,32 @@ +{{ define "page-styles"}} + +{{ end }} + +{{ define "page-content" }} +
+
+ + + + + + + + + +
+
+ +
+
+{{ end }} \ No newline at end of file diff --git a/webadmin.go b/webadmin.go index 3ad4384..f62d5ac 100644 --- a/webadmin.go +++ b/webadmin.go @@ -36,25 +36,27 @@ import ( "time" "github.com/go-chi/chi" + passwordvalidator "github.com/lane-c-wagner/go-password-validator" uuid "github.com/satori/go.uuid" "github.com/tidwall/buntdb" "golang.org/x/crypto/bcrypt" ) type webAdminHandler struct { - router chi.Router - shareStore ShareStore - tplError *template.Template - tplConfirm *template.Template - tplLogin *template.Template - tplIndex *template.Template - tplUsers *template.Template - tplShares *template.Template - tplMyShares *template.Template - tplShareAddUser *template.Template - tplShareAddLogin *template.Template - tplCreateShare *template.Template - tplCreateUser *template.Template + router chi.Router + shareStore ShareStore + tplError *template.Template + tplConfirm *template.Template + tplLogin *template.Template + tplIndex *template.Template + tplUsers *template.Template + tplShares *template.Template + tplMyShares *template.Template + tplShareAddUser *template.Template + tplShareAddLogin *template.Template + tplCreateShare *template.Template + tplCreateUser *template.Template + tplChangePassword *template.Template } type sessionContext struct { @@ -72,6 +74,8 @@ const confirmRequested = 0 const confirmAccepted = 1 const confirmDenied = 2 +const minPasswordEntropy = 60 + func newWebAdminHandler(app *app) *webAdminHandler { sessionStore, err := buntdb.Open(":memory:") if err != nil { @@ -97,18 +101,19 @@ func newWebAdminHandler(app *app) *webAdminHandler { } h := &webAdminHandler{ - shareStore: app.shareStore, - tplError: loadTemplate("error"), - tplConfirm: loadTemplate("confirm"), - tplLogin: loadSingleTemplate("login"), - tplIndex: loadTemplate("index"), - tplUsers: loadTemplate("users"), - tplShares: loadTemplate("shares"), - tplMyShares: loadTemplate("my-shares"), - tplShareAddUser: loadTemplate("share-add-user"), - tplShareAddLogin: loadTemplate("share-add-login"), - tplCreateShare: loadTemplate("create-share"), - tplCreateUser: loadTemplate("create-user"), + shareStore: app.shareStore, + tplError: loadTemplate("error"), + tplConfirm: loadTemplate("confirm"), + tplLogin: loadSingleTemplate("login"), + tplIndex: loadTemplate("index"), + tplUsers: loadTemplate("users"), + tplShares: loadTemplate("shares"), + tplMyShares: loadTemplate("my-shares"), + tplShareAddUser: loadTemplate("share-add-user"), + tplShareAddLogin: loadTemplate("share-add-login"), + tplCreateShare: loadTemplate("create-share"), + tplCreateUser: loadTemplate("create-user"), + tplChangePassword: loadTemplate("change-password"), } r := chi.NewRouter() @@ -574,6 +579,8 @@ Are you sure you want to continue?`, share.UUID, share.Name) ar.Post("/delete-user", func(w http.ResponseWriter, r *http.Request) { sessionContext := h.buildSessionContext(w, r) + // TODO invalidate sessions + if sessionContext.user.Role != GlobalRoleAdmin { sessionContext.Unauthorized() return @@ -600,6 +607,50 @@ Are you sure you want to continue?`, user.Username, user.Role) } sessionContext.Redirect("users") }) + ar.Route("/change-password", func(r chi.Router) { + r.Get("/", func(w http.ResponseWriter, r *http.Request) { + sessionContext := h.buildSessionContext(w, r) + sessionContext.RenderPage(h.tplChangePassword, nil) + }) + r.Post("/", func(w http.ResponseWriter, r *http.Request) { + sessionContext := h.buildSessionContext(w, r) + + currentPassword := r.FormValue("password") + newPassword := r.FormValue("password-new") + repeatPassword := r.FormValue("password-repeat") + + if err := bcrypt.CompareHashAndPassword([]byte(sessionContext.user.Password), []byte(currentPassword)); err != nil { + sessionContext.RenderError(template.HTML("The current password is wrong. Please check."), "") + return + } + + if newPassword != repeatPassword { + sessionContext.RenderError(template.HTML("The new password doesn't match the repeated password."), "") + return + } + + if err := passwordvalidator.Validate(newPassword, minPasswordEntropy); err != nil { + sessionContext.RenderError(template.HTML("The new password is not strong enough."), "") + return + } + + pwHash, err := bcrypt.GenerateFromPassword([]byte(newPassword), bcrypt.DefaultCost) + if err != nil { + sessionContext.RenderError(template.HTML("Internal error: "+err.Error()), "") + return + } + + if err := app.userStore.UpdateUser(User{ + Username: sessionContext.user.Username, + Password: string(pwHash), + }); err != nil { + sessionContext.RenderError(template.HTML("Internal error: "+err.Error()), "") + return + } + + sessionContext.Redirect("./") + }) + }) h.router = r return h