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:
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...)