mirror of
https://github.com/dnote/dnote
synced 2026-03-14 22:45:50 +01:00
Password change
This commit is contained in:
parent
7af65c1eee
commit
0a40fef084
13 changed files with 138 additions and 25 deletions
|
|
@ -51,4 +51,10 @@ var (
|
|||
|
||||
// ErrPasswordResetTokenExpired is an error for expired password reset token
|
||||
ErrPasswordResetTokenExpired appError = "this link has been expired. Please request a new password reset link."
|
||||
|
||||
// ErrInvalidPasswordChangeInput is an error for changing password
|
||||
ErrInvalidPasswordChangeInput appError = "Both current and new passwords are required to change the password."
|
||||
|
||||
// ErrInvalidCurrentPassword is an error for invalid current password while changing password
|
||||
ErrInvalidCurrentPassword appError = "Current password does not match."
|
||||
)
|
||||
|
|
|
|||
|
|
@ -34,8 +34,7 @@
|
|||
|
||||
.alert {
|
||||
position: relative;
|
||||
padding: 0.75rem 1.25rem;
|
||||
margin-bottom: 1rem;
|
||||
padding: 1.75rem 1.25rem;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
||||
|
|
@ -48,7 +47,7 @@
|
|||
}
|
||||
|
||||
.alert-dismissible {
|
||||
padding-right: 4rem;
|
||||
// padding-right: 4rem;
|
||||
}
|
||||
|
||||
.alert-dismissible .close {
|
||||
|
|
@ -170,3 +169,8 @@
|
|||
.alert-dark .alert-link {
|
||||
color: #040505;
|
||||
}
|
||||
|
||||
// custom
|
||||
.alert-slim {
|
||||
padding: 0.75rem 1.25rem;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -81,4 +81,8 @@
|
|||
margin-top: rem(16px);
|
||||
}
|
||||
}
|
||||
|
||||
.alert {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -183,9 +183,10 @@ type SessionResponse struct {
|
|||
|
||||
func logError(err error, msg string) {
|
||||
// log if internal error
|
||||
if _, ok := err.(views.PublicError); !ok {
|
||||
log.ErrorWrap(err, msg)
|
||||
}
|
||||
// if _, ok := err.(views.PublicError); !ok {
|
||||
// log.ErrorWrap(err, msg)
|
||||
// }
|
||||
log.ErrorWrap(err, msg)
|
||||
}
|
||||
|
||||
func getStatusCode(err error) int {
|
||||
|
|
@ -214,6 +215,10 @@ func getStatusCode(err error) int {
|
|||
return http.StatusGone
|
||||
case app.ErrPasswordConfirmationMismatch:
|
||||
return http.StatusBadRequest
|
||||
case app.ErrInvalidPasswordChangeInput:
|
||||
return http.StatusBadRequest
|
||||
case app.ErrInvalidCurrentPassword:
|
||||
return http.StatusBadRequest
|
||||
}
|
||||
|
||||
return http.StatusInternalServerError
|
||||
|
|
|
|||
|
|
@ -37,8 +37,8 @@ func NewWebRoutes(a *app.App, c *Controllers) []Route {
|
|||
|
||||
{"GET", "/password-reset", c.Users.PasswordResetView.ServeHTTP, true},
|
||||
{"PATCH", "/password-reset", c.Users.PasswordReset, true},
|
||||
{"PATCH", "/account/profile", c.Users.PasswordReset, true},
|
||||
{"PATCH", "/account/password", c.Users.PasswordReset, true},
|
||||
{"PATCH", "/account/profile", mw.Auth(a, c.Users.PasswordReset, nil), true},
|
||||
{"PATCH", "/account/password", mw.Auth(a, c.Users.PasswordUpdate, nil), true},
|
||||
{"GET", "/password-reset/{token}", c.Users.PasswordResetConfirm, true},
|
||||
{"POST", "/reset-token", c.Users.CreateResetToken, true},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,14 @@
|
|||
package controllers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/dnote/dnote/pkg/server/app"
|
||||
"github.com/dnote/dnote/pkg/server/buildinfo"
|
||||
"github.com/dnote/dnote/pkg/server/context"
|
||||
"github.com/dnote/dnote/pkg/server/database"
|
||||
"github.com/dnote/dnote/pkg/server/helpers"
|
||||
"github.com/dnote/dnote/pkg/server/log"
|
||||
|
|
@ -51,11 +53,11 @@ func NewUsers(app *app.App) *Users {
|
|||
"users/password_reset_confirm",
|
||||
),
|
||||
SettingView: views.NewView(app,
|
||||
views.Config{Layout: "base", HelperFuncs: commonHelpers, AlertInBody: true, HeaderTemplate: "navbar"},
|
||||
views.Config{Layout: "base", HelperFuncs: commonHelpers, HeaderTemplate: "navbar"},
|
||||
"users/settings",
|
||||
),
|
||||
AboutView: views.NewView(app,
|
||||
views.Config{Title: "About", Layout: "base", HelperFuncs: commonHelpers, AlertInBody: true, HeaderTemplate: "navbar"},
|
||||
views.Config{Title: "About", Layout: "base", HelperFuncs: commonHelpers, HeaderTemplate: "navbar"},
|
||||
"users/settings_about",
|
||||
),
|
||||
app: app,
|
||||
|
|
@ -447,3 +449,81 @@ func (u *Users) About(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
u.AboutView.Render(w, r, &vd, http.StatusOK)
|
||||
}
|
||||
|
||||
type updatePasswordForm struct {
|
||||
OldPassword string `schema:"old_password"`
|
||||
NewPassword string `schema:"new_password"`
|
||||
NewPasswordConfirmation string `schema:"new_password_confirmation"`
|
||||
}
|
||||
|
||||
func (u *Users) PasswordUpdate(w http.ResponseWriter, r *http.Request) {
|
||||
vd := views.Data{}
|
||||
|
||||
user := context.User(r.Context())
|
||||
if user == nil {
|
||||
handleHTMLError(w, r, app.ErrLoginRequired, "No authenticated user found", u.SettingView, vd)
|
||||
return
|
||||
}
|
||||
|
||||
var form updatePasswordForm
|
||||
if err := parseRequestData(r, &form); err != nil {
|
||||
handleHTMLError(w, r, err, "parsing payload", u.LoginView, vd)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println(form)
|
||||
|
||||
if form.OldPassword == "" || form.NewPassword == "" {
|
||||
handleHTMLError(w, r, app.ErrInvalidPasswordChangeInput, "invalid params", u.SettingView, vd)
|
||||
return
|
||||
}
|
||||
if form.NewPassword != form.NewPasswordConfirmation {
|
||||
handleHTMLError(w, r, app.ErrPasswordConfirmationMismatch, "passwords do not match", u.SettingView, vd)
|
||||
return
|
||||
}
|
||||
|
||||
var account database.Account
|
||||
if err := u.app.DB.Where("user_id = ?", user.ID).First(&account).Error; err != nil {
|
||||
handleHTMLError(w, r, err, "getting account", u.SettingView, vd)
|
||||
return
|
||||
}
|
||||
|
||||
password := []byte(form.OldPassword)
|
||||
if err := bcrypt.CompareHashAndPassword([]byte(account.Password.String), password); err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
"user_id": user.ID,
|
||||
}).Warn("invalid password update attempt")
|
||||
handleHTMLError(w, r, app.ErrInvalidCurrentPassword, "invalid password", u.SettingView, vd)
|
||||
return
|
||||
}
|
||||
|
||||
if err := validatePassword(form.NewPassword); err != nil {
|
||||
handleHTMLError(w, r, err, "invalid password", u.SettingView, vd)
|
||||
return
|
||||
}
|
||||
|
||||
hashedNewPassword, err := bcrypt.GenerateFromPassword([]byte(form.NewPassword), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
handleHTMLError(w, r, err, "hashing password", u.SettingView, vd)
|
||||
return
|
||||
}
|
||||
|
||||
if err := u.app.DB.Model(&account).Update("password", string(hashedNewPassword)).Error; err != nil {
|
||||
handleHTMLError(w, r, err, "updating password", u.SettingView, vd)
|
||||
return
|
||||
}
|
||||
|
||||
alert := views.Alert{
|
||||
Level: views.AlertLvlSuccess,
|
||||
Message: "Password change successful",
|
||||
}
|
||||
views.RedirectAlert(w, r, "/", http.StatusFound, alert)
|
||||
}
|
||||
|
||||
func validatePassword(password string) error {
|
||||
if len(password) < 8 {
|
||||
return app.ErrPasswordTooShort
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -1,7 +1,6 @@
|
|||
package views
|
||||
|
||||
import (
|
||||
// "html/template"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
|
|
@ -92,12 +91,14 @@ func persistAlert(w http.ResponseWriter, alert Alert) {
|
|||
Name: "alert_level",
|
||||
Value: alert.Level,
|
||||
Expires: expiresAt,
|
||||
Path: "/",
|
||||
HttpOnly: true,
|
||||
}
|
||||
msg := http.Cookie{
|
||||
Name: "alert_message",
|
||||
Value: alert.Message,
|
||||
Expires: expiresAt,
|
||||
Path: "/",
|
||||
HttpOnly: true,
|
||||
}
|
||||
http.SetCookie(w, &lvl)
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
{{define "alert"}}
|
||||
{{if .}}
|
||||
<div class="alert alert-{{.Level}} alert-dismissible" role="alert">
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
{{.Message}}
|
||||
<div class="alert alert-{{.Level}}" role="alert">
|
||||
<div class="container">
|
||||
{{.Message}}
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
|
|
|||
|
|
@ -9,14 +9,14 @@
|
|||
|
||||
<div class="body">
|
||||
{{if .Referrer}}
|
||||
<div class="alert alert-info" role="alert">
|
||||
<div class="alert alert-info alert-slim" role="alert">
|
||||
Please sign in to continue to that page.
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
<div class="panel">
|
||||
{{if .Alert}}
|
||||
<div class="alert alert-{{.Alert.Level}}" role="alert">
|
||||
<div class="alert alert-{{.Alert.Level}} alert-slim" role="alert">
|
||||
{{.Alert.Message}}
|
||||
</div>
|
||||
{{end}}
|
||||
|
|
|
|||
|
|
@ -18,11 +18,11 @@
|
|||
<div class="setting-row">
|
||||
<div class="setting-row-summary">
|
||||
<div>
|
||||
<h3 class="setting-name">Email</h3>
|
||||
<h3 class="setting-name">Current Email</h3>
|
||||
</div>
|
||||
|
||||
<div class="setting-right">
|
||||
{{.Account.Email.String}}
|
||||
{{.Email}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -34,7 +34,7 @@
|
|||
</div>
|
||||
|
||||
<div class="setting-right">
|
||||
{{if .Account.EmailVerified}}
|
||||
{{if .EmailVerified}}
|
||||
Yes
|
||||
{{else}}
|
||||
No
|
||||
|
|
@ -125,8 +125,9 @@
|
|||
{{end}}
|
||||
|
||||
{{define "passwordChangeForm"}}
|
||||
<form action="/account/password">
|
||||
<input type="password" style="display: none;" readOnly />
|
||||
<form action="/account/password" method="POST">
|
||||
<input type="password" style="display: none;" readonly />
|
||||
<input type="hidden" name="_method" value="PATCH" />
|
||||
|
||||
<div class="input-row">
|
||||
<label class="input-label" for="old-password-input">
|
||||
|
|
@ -134,6 +135,7 @@
|
|||
</label>
|
||||
<input
|
||||
id="old-password-input"
|
||||
name="old_password"
|
||||
type="password"
|
||||
placeholder="********"
|
||||
class="form-control"
|
||||
|
|
@ -147,6 +149,7 @@
|
|||
</label>
|
||||
<input
|
||||
id="new-password-input"
|
||||
name="new_password"
|
||||
type="password"
|
||||
placeholder="********"
|
||||
class="form-control"
|
||||
|
|
@ -162,6 +165,7 @@
|
|||
</label>
|
||||
<input
|
||||
id="new-password-confirmation-input"
|
||||
name="new_password_confirmation"
|
||||
type="password"
|
||||
placeholder="********"
|
||||
class="form-control"
|
||||
|
|
|
|||
|
|
@ -128,11 +128,19 @@ func (v *View) Render(w http.ResponseWriter, r *http.Request, data *Data, status
|
|||
if vd.Yield == nil {
|
||||
vd.Yield = map[string]interface{}{}
|
||||
}
|
||||
vd.Yield["Account"] = &vd.Account
|
||||
vd.Yield["User"] = &vd.User
|
||||
if vd.Account != nil {
|
||||
vd.Yield["Email"] = &vd.Account.Email.String
|
||||
vd.Yield["EmailVerified"] = &vd.Account.EmailVerified
|
||||
}
|
||||
if vd.User != nil {
|
||||
vd.Yield["Cloud"] = &vd.User.Cloud
|
||||
}
|
||||
vd.Yield["CurrentPath"] = r.URL.Path
|
||||
vd.Yield["Standalone"] = buildinfo.Standalone
|
||||
|
||||
fmt.Println("######")
|
||||
fmt.Println(vd.Alert)
|
||||
|
||||
var buf bytes.Buffer
|
||||
csrfField := csrf.TemplateField(r)
|
||||
tpl := v.Template.Funcs(template.FuncMap{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue