cerca

lean forum software (pmc local branch)
Log | Files | Refs | README | LICENSE

commit 6f8802509f09162b9725f1fa1816bbd7ae9cda1a
parent 34a86544b6b3e4d67dea1cb6cea5a1eaa572f911
Author: cblgh <cblgh@cblgh.org>
Date:   Tue, 27 Sep 2022 16:39:26 +0200

add change password view on /reset when logged in

Diffstat:
Mdatabase/database.go | 10++++++++++
Ahtml/change-password-success.html | 12++++++++++++
Ahtml/change-password.html | 23+++++++++++++++++++++++
Mserver/server.go | 108+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
4 files changed, 142 insertions(+), 11 deletions(-)

diff --git a/database/database.go b/database/database.go @@ -348,6 +348,16 @@ func (d DB) GetUserID(name string) (int, error) { return userid, nil } +func (d DB) GetUsername(uid int) (string, error) { + stmt := `SELECT name FROM users where id = ?` + var username string + err := d.db.QueryRow(stmt, uid).Scan(&username) + if err != nil { + return "", util.Eout(err, "get username") + } + return username, nil +} + func (d DB) GetPasswordHash(username string) (string, int, error) { stmt := `SELECT passwordhash, id FROM users where name = ?` var hash string diff --git a/html/change-password-success.html b/html/change-password-success.html @@ -0,0 +1,12 @@ +{{ template "head" . }} +<h1>Change password</h1> +<p>Your password was successfully changed! Please store it somewhere safe.</p> + +{{ if ne .Data.Keypair "" }} +<p> Here is your newly regenerated keypair, please save it somewhere in case you forget your password and need to reset it.</p> +<pre class="selectable"> +{{ .Data.Keypair }} +</pre> +{{ end }} +<p>Go back to the <a href="/">index</a>.</p> +{{ template "footer" . }} diff --git a/html/change-password.html b/html/change-password.html @@ -0,0 +1,23 @@ +{{ template "head" . }} +<h1>Change password</h1> +<p>Use this page to change your password. If needed, you can also regenerate your password reset keypair—used to reset a +forgotten password without admin help.</p> +<form method="post" action="{{.Data.Action}}"> + <div> + <label type="text" for="password-old">Current password:</label> + <input type="password" minlength="9" required id="password-old" name="password-old" aria-describedby="password-help"> + </div> + <div> + <label type="text" for="password-new">New password:</label> + <input type="password" style="margin-bottom: 0;" minlength="9" required id="password-new" name="password-new" aria-describedby="password-help"> + <div><small id="password-help">Must be at least 9 characters long.</small></div> + </div> + <div> + <input type="checkbox" value="true" name ="reset-keypair" id="reset-keypair"> + <label for="reset-keypair" style="display: inline-block;">I also want to generate a new keypair</label> + </div> + <div> + <input type="submit" value="Submit"> + </div> +</form> +{{ template "footer" . }} diff --git a/server/server.go b/server/server.go @@ -40,6 +40,11 @@ type PasswordResetData struct { Payload string } +type ChangePasswordData struct { + Action string + Keypair string +} + type IndexData struct { Threads []database.Thread } @@ -146,6 +151,8 @@ func generateTemplates() (*template.Template, error) { "register-success", "thread", "password-reset", + "change-password", + "change-password-success", } rootTemplate := template.New("root") @@ -304,19 +311,98 @@ func hasVerificationCode(link, verification string) bool { return strings.Contains(strings.TrimSpace(linkBody), strings.TrimSpace(verification)) } -func (h RequestHandler) ResetPasswordRoute(res http.ResponseWriter, req *http.Request) { - ed := util.Describe("password proof route") - loggedIn, _ := h.IsLoggedIn(req) - if loggedIn { +func (h RequestHandler) handleChangePassword(res http.ResponseWriter, req *http.Request) { + renderErr := func(errFmt string, args ...interface{}) { + errMessage := fmt.Sprintf(errFmt, args...) + fmt.Println(errMessage) data := GenericMessageData{ - Title: "Reset password", - Message: "You are logged in, log out to reset password using proof", - Link: "/logout", - LinkText: "Logout", + Title: "Change password", + Message: errMessage, + Link: "/reset", + LinkText: "Go back", } - h.renderView(res, "generic-message", TemplateData{Data: data, LoggedIn: loggedIn, Title: "Reset password"}) - return - } + h.renderView(res, "generic-message", TemplateData{Data: data, Title: "change password"}) + } + _, uid := h.IsLoggedIn(req) + + ed := util.Describe("change password") + switch req.Method { + case "GET": + switch req.URL.Path { + default: + h.renderView(res, "change-password", TemplateData{LoggedIn: true, Data: ChangePasswordData{Action: "/reset/submit"}}) + } + case "POST": + switch req.URL.Path { + case "/reset/submit": + oldPassword := req.PostFormValue("password-old") + newPassword := req.PostFormValue("password-new") + resetKeypair := (req.PostFormValue("reset-keypair") == "true") + var keypairString string + + // check if we're resetting keypair + if resetKeypair { + // if so: generate new keypair + kp, err := crypto.GenerateKeypair() + ed.Check(err, "generate keypair") + kpBytes, err := kp.Marshal() + ed.Check(err, "marshal keypair") + pubkey, err := kp.PublicString() + ed.Check(err, "get pubkey string") + // and set it in db + err = h.db.SetPubkey(uid, pubkey) + ed.Check(err, "set new pubkey in database") + keypairString = string(kpBytes) + } + + // check that the submitted, old password is valid + username, err := h.db.GetUsername(uid) + if err != nil { + dump(ed.Eout(err, "get username")) + return + } + + pwhashOld, _, err := h.db.GetPasswordHash(username) + if err != nil { + dump(ed.Eout(err, "get old password hash")) + return + } + + oldPasswordValid := crypto.ValidatePasswordHash(oldPassword, pwhashOld) + if !oldPasswordValid { + renderErr("old password did not match what was in database; not changing password") + return + } + + // let's set the new password in the database. first, hash it + pwhashNew, err := crypto.HashPassword(newPassword) + if err != nil { + dump(ed.Eout(err, "hash new password")) + return + } + // then save the hash + h.db.UpdateUserPasswordHash(uid, pwhashNew) + // render a success message & show a link to the login page :') + h.renderView(res, "change-password-success", TemplateData{LoggedIn: true, Data: ChangePasswordData{Keypair: keypairString}}) + default: + fmt.Printf("unsupported POST route (%s), redirecting to /\n", req.URL.Path) + IndexRedirect(res, req) + } + default: + fmt.Println("non get/post method, redirecting to index") + IndexRedirect(res, req) + } +} + +func (h RequestHandler) ResetPasswordRoute(res http.ResponseWriter, req *http.Request) { + ed := util.Describe("password proof route") + loggedIn, _ := h.IsLoggedIn(req) + + // change password functionality, handle this in another function + if loggedIn { + h.handleChangePassword(res, req) + return + } renderErr := func(errFmt string, args ...interface{}) { errMessage := fmt.Sprintf(errFmt, args...)