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