Skip to content

Commit f8cf890

Browse files
author
AJ ONeal
committed
handle reset password edge cases properly and consistently
1 parent d3a4d76 commit f8cf890

File tree

3 files changed

+79
-47
lines changed

3 files changed

+79
-47
lines changed

options/locale/locale_en-US.ini

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,8 @@ email_not_associate = The email address is not associated with any account.
219219
send_reset_mail = Click here to resend your password reset email
220220
reset_password = Reset Your Password
221221
invalid_code = Your confirmation code is invalid or has expired.
222-
reset_password_helper = Click here to reset your password
222+
reset_password_helper = Reset Password
223+
reset_password_wrong_user = You are signed in as %s, but the password reset link is for %s
223224
password_too_short = Password length cannot be less than %d characters.
224225
non_local_account = Non-local users can not update their password through the Gitea web interface.
225226
verify = Verify

routers/user/auth.go

Lines changed: 62 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1139,73 +1139,89 @@ func ForgotPasswdPost(ctx *context.Context) {
11391139
ctx.HTML(200, tplForgotPassword)
11401140
}
11411141

1142-
// ResetPasswd render the reset password page
1143-
func ResetPasswd(ctx *context.Context) {
1142+
func commonResetPassword(ctx *context.Context) *models.User {
1143+
code := ctx.Query("code")
1144+
11441145
ctx.Data["Title"] = ctx.Tr("auth.reset_password")
1146+
ctx.Data["Code"] = code
11451147

1146-
// TODO for security and convenience, show the username / email here
1148+
if nil != ctx.User {
1149+
ctx.Data["user_signed_in"] = true
1150+
}
11471151

1148-
code := ctx.Query("code")
11491152
if len(code) == 0 {
1150-
ctx.Error(404)
1151-
return
1153+
ctx.Flash.Error(ctx.Tr("auth.invalid_code"))
1154+
return nil
11521155
}
1153-
ctx.Data["Code"] = code
1156+
1157+
// Fail early, don't frustrate the user
1158+
u := models.VerifyUserActiveCode(code)
1159+
if u == nil {
1160+
ctx.Flash.Error(ctx.Tr("auth.invalid_code"))
1161+
return nil
1162+
}
1163+
1164+
// Show the user that they are affecting the account that they intended to
1165+
ctx.Data["user_email"] = u.Email
1166+
1167+
if nil != ctx.User && u.ID != ctx.User.ID {
1168+
ctx.Flash.Error(ctx.Tr("auth.reset_password_wrong_user", ctx.User.Email, u.Email))
1169+
return nil
1170+
}
1171+
1172+
return u
1173+
}
1174+
1175+
// ResetPasswd render the reset password page
1176+
func ResetPasswd(ctx *context.Context) {
11541177
ctx.Data["IsResetForm"] = true
1178+
1179+
_ = commonResetPassword(ctx)
1180+
11551181
ctx.HTML(200, tplResetPassword)
11561182
}
11571183

11581184
// ResetPasswdPost response from reset password request
11591185
func ResetPasswdPost(ctx *context.Context) {
1160-
ctx.Data["Title"] = ctx.Tr("auth.reset_password")
1186+
u := commonResetPassword(ctx)
11611187

1162-
code := ctx.Query("code")
1163-
if len(code) == 0 {
1164-
ctx.Error(404)
1188+
if u == nil {
1189+
// Flash error has been set
1190+
ctx.HTML(200, tplResetPassword)
11651191
return
11661192
}
1167-
ctx.Data["Code"] = code
1168-
1169-
if u := models.VerifyUserActiveCode(code); u != nil {
1170-
// Validate password length.
1171-
passwd := ctx.Query("password")
1172-
if len(passwd) < setting.MinPasswordLength {
1173-
ctx.Data["IsResetForm"] = true
1174-
ctx.Data["Err_Password"] = true
1175-
ctx.RenderWithErr(ctx.Tr("auth.password_too_short", setting.MinPasswordLength), tplResetPassword, nil)
1176-
return
1177-
}
11781193

1179-
var err error
1180-
if u.Rands, err = models.GetUserSalt(); err != nil {
1181-
ctx.ServerError("UpdateUser", err)
1182-
return
1183-
}
1184-
if u.Salt, err = models.GetUserSalt(); err != nil {
1185-
ctx.ServerError("UpdateUser", err)
1186-
return
1187-
}
1188-
1189-
// Just in case the user is signed in to another account
1190-
handleSignOut(ctx)
1191-
1192-
u.HashPassword(passwd)
1193-
u.MustChangePassword = false
1194-
if err := models.UpdateUserCols(u, "must_change_password", "passwd", "rands", "salt"); err != nil {
1195-
ctx.ServerError("UpdateUser", err)
1196-
return
1197-
}
1194+
// Validate password length.
1195+
passwd := ctx.Query("password")
1196+
if len(passwd) < setting.MinPasswordLength {
1197+
ctx.Data["IsResetForm"] = true
1198+
ctx.Data["Err_Password"] = true
1199+
ctx.RenderWithErr(ctx.Tr("auth.password_too_short", setting.MinPasswordLength), tplResetPassword, nil)
1200+
return
1201+
}
11981202

1199-
log.Trace("User password reset: %s", u.Name)
1203+
var err error
1204+
if u.Rands, err = models.GetUserSalt(); err != nil {
1205+
ctx.ServerError("UpdateUser", err)
1206+
return
1207+
}
1208+
if u.Salt, err = models.GetUserSalt(); err != nil {
1209+
ctx.ServerError("UpdateUser", err)
1210+
return
1211+
}
12001212

1201-
// TODO change the former form to have password retype and remember me,
1202-
// then sign in here instead of redirecting
1203-
ctx.Redirect(setting.AppSubURL + "/user/login")
1213+
u.HashPassword(passwd)
1214+
u.MustChangePassword = false
1215+
if err := models.UpdateUserCols(u, "must_change_password", "passwd", "rands", "salt"); err != nil {
1216+
ctx.ServerError("UpdateUser", err)
12041217
return
12051218
}
12061219

1220+
log.Trace("User password reset: %s", u.Name)
1221+
12071222
ctx.Data["IsResetFailed"] = true
1208-
ctx.HTML(200, tplResetPassword)
1223+
remember := len(ctx.Query("remember")) != 0
1224+
handleSignInFull(ctx, u, remember, true)
12091225
}
12101226

12111227
// MustChangePassword renders the page to change a user's password

templates/user/auth/reset_passwd.tmpl

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,26 @@
1010
</h2>
1111
<div class="ui attached segment">
1212
{{template "base/alert" .}}
13+
{{if .user_email }}
14+
<div class="inline field">
15+
<label for="user_name">{{.i18n.Tr "email"}}</label>
16+
<input id="user_name" type="text" value="{{ .user_email }}" disabled>
17+
</div>
18+
{{end}}
1319
{{if .IsResetForm}}
1420
<div class="required inline field {{if .Err_Password}}error{{end}}">
1521
<label for="password">{{.i18n.Tr "password"}}</label>
1622
<input id="password" name="password" type="password" value="{{.password}}" autocomplete="off" autofocus required>
1723
</div>
24+
{{if not .user_signed_in}}
25+
<div class="inline field">
26+
<label></label>
27+
<div class="ui checkbox">
28+
<label>{{.i18n.Tr "auth.remember_me"}}</label>
29+
<input name="remember" type="checkbox">
30+
</div>
31+
</div>
32+
{{end}}
1833
<div class="ui divider"></div>
1934
<div class="inline field">
2035
<label></label>

0 commit comments

Comments
 (0)