code.gitea.io/gitea@v1.22.3/routers/web/auth/password.go (about) 1 // Copyright 2019 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package auth 5 6 import ( 7 "errors" 8 "fmt" 9 "net/http" 10 11 "code.gitea.io/gitea/models/auth" 12 user_model "code.gitea.io/gitea/models/user" 13 "code.gitea.io/gitea/modules/auth/password" 14 "code.gitea.io/gitea/modules/base" 15 "code.gitea.io/gitea/modules/log" 16 "code.gitea.io/gitea/modules/optional" 17 "code.gitea.io/gitea/modules/setting" 18 "code.gitea.io/gitea/modules/timeutil" 19 "code.gitea.io/gitea/modules/web" 20 "code.gitea.io/gitea/modules/web/middleware" 21 "code.gitea.io/gitea/services/context" 22 "code.gitea.io/gitea/services/forms" 23 "code.gitea.io/gitea/services/mailer" 24 user_service "code.gitea.io/gitea/services/user" 25 ) 26 27 var ( 28 // tplMustChangePassword template for updating a user's password 29 tplMustChangePassword base.TplName = "user/auth/change_passwd" 30 tplForgotPassword base.TplName = "user/auth/forgot_passwd" 31 tplResetPassword base.TplName = "user/auth/reset_passwd" 32 ) 33 34 // ForgotPasswd render the forget password page 35 func ForgotPasswd(ctx *context.Context) { 36 ctx.Data["Title"] = ctx.Tr("auth.forgot_password_title") 37 38 if setting.MailService == nil { 39 log.Warn("no mail service configured") 40 ctx.Data["IsResetDisable"] = true 41 ctx.HTML(http.StatusOK, tplForgotPassword) 42 return 43 } 44 45 ctx.Data["Email"] = ctx.FormString("email") 46 47 ctx.Data["IsResetRequest"] = true 48 ctx.HTML(http.StatusOK, tplForgotPassword) 49 } 50 51 // ForgotPasswdPost response for forget password request 52 func ForgotPasswdPost(ctx *context.Context) { 53 ctx.Data["Title"] = ctx.Tr("auth.forgot_password_title") 54 55 if setting.MailService == nil { 56 ctx.NotFound("ForgotPasswdPost", nil) 57 return 58 } 59 ctx.Data["IsResetRequest"] = true 60 61 email := ctx.FormString("email") 62 ctx.Data["Email"] = email 63 64 u, err := user_model.GetUserByEmail(ctx, email) 65 if err != nil { 66 if user_model.IsErrUserNotExist(err) { 67 ctx.Data["ResetPwdCodeLives"] = timeutil.MinutesToFriendly(setting.Service.ResetPwdCodeLives, ctx.Locale) 68 ctx.Data["IsResetSent"] = true 69 ctx.HTML(http.StatusOK, tplForgotPassword) 70 return 71 } 72 73 ctx.ServerError("user.ResetPasswd(check existence)", err) 74 return 75 } 76 77 if !u.IsLocal() && !u.IsOAuth2() { 78 ctx.Data["Err_Email"] = true 79 ctx.RenderWithErr(ctx.Tr("auth.non_local_account"), tplForgotPassword, nil) 80 return 81 } 82 83 if ctx.Cache.IsExist("MailResendLimit_" + u.LowerName) { 84 ctx.Data["ResendLimited"] = true 85 ctx.HTML(http.StatusOK, tplForgotPassword) 86 return 87 } 88 89 mailer.SendResetPasswordMail(u) 90 91 if err = ctx.Cache.Put("MailResendLimit_"+u.LowerName, u.LowerName, 180); err != nil { 92 log.Error("Set cache(MailResendLimit) fail: %v", err) 93 } 94 95 ctx.Data["ResetPwdCodeLives"] = timeutil.MinutesToFriendly(setting.Service.ResetPwdCodeLives, ctx.Locale) 96 ctx.Data["IsResetSent"] = true 97 ctx.HTML(http.StatusOK, tplForgotPassword) 98 } 99 100 func commonResetPassword(ctx *context.Context) (*user_model.User, *auth.TwoFactor) { 101 code := ctx.FormString("code") 102 103 ctx.Data["Title"] = ctx.Tr("auth.reset_password") 104 ctx.Data["Code"] = code 105 106 if nil != ctx.Doer { 107 ctx.Data["user_signed_in"] = true 108 } 109 110 if len(code) == 0 { 111 ctx.Flash.Error(ctx.Tr("auth.invalid_code_forgot_password", fmt.Sprintf("%s/user/forgot_password", setting.AppSubURL)), true) 112 return nil, nil 113 } 114 115 // Fail early, don't frustrate the user 116 u := user_model.VerifyUserActiveCode(ctx, code) 117 if u == nil { 118 ctx.Flash.Error(ctx.Tr("auth.invalid_code_forgot_password", fmt.Sprintf("%s/user/forgot_password", setting.AppSubURL)), true) 119 return nil, nil 120 } 121 122 twofa, err := auth.GetTwoFactorByUID(ctx, u.ID) 123 if err != nil { 124 if !auth.IsErrTwoFactorNotEnrolled(err) { 125 ctx.Error(http.StatusInternalServerError, "CommonResetPassword", err.Error()) 126 return nil, nil 127 } 128 } else { 129 ctx.Data["has_two_factor"] = true 130 ctx.Data["scratch_code"] = ctx.FormBool("scratch_code") 131 } 132 133 // Show the user that they are affecting the account that they intended to 134 ctx.Data["user_email"] = u.Email 135 136 if nil != ctx.Doer && u.ID != ctx.Doer.ID { 137 ctx.Flash.Error(ctx.Tr("auth.reset_password_wrong_user", ctx.Doer.Email, u.Email), true) 138 return nil, nil 139 } 140 141 return u, twofa 142 } 143 144 // ResetPasswd render the account recovery page 145 func ResetPasswd(ctx *context.Context) { 146 ctx.Data["IsResetForm"] = true 147 148 commonResetPassword(ctx) 149 if ctx.Written() { 150 return 151 } 152 153 ctx.HTML(http.StatusOK, tplResetPassword) 154 } 155 156 // ResetPasswdPost response from account recovery request 157 func ResetPasswdPost(ctx *context.Context) { 158 u, twofa := commonResetPassword(ctx) 159 if ctx.Written() { 160 return 161 } 162 163 if u == nil { 164 // Flash error has been set 165 ctx.HTML(http.StatusOK, tplResetPassword) 166 return 167 } 168 169 // Handle two-factor 170 regenerateScratchToken := false 171 if twofa != nil { 172 if ctx.FormBool("scratch_code") { 173 if !twofa.VerifyScratchToken(ctx.FormString("token")) { 174 ctx.Data["IsResetForm"] = true 175 ctx.Data["Err_Token"] = true 176 ctx.RenderWithErr(ctx.Tr("auth.twofa_scratch_token_incorrect"), tplResetPassword, nil) 177 return 178 } 179 regenerateScratchToken = true 180 } else { 181 passcode := ctx.FormString("passcode") 182 ok, err := twofa.ValidateTOTP(passcode) 183 if err != nil { 184 ctx.Error(http.StatusInternalServerError, "ValidateTOTP", err.Error()) 185 return 186 } 187 if !ok || twofa.LastUsedPasscode == passcode { 188 ctx.Data["IsResetForm"] = true 189 ctx.Data["Err_Passcode"] = true 190 ctx.RenderWithErr(ctx.Tr("auth.twofa_passcode_incorrect"), tplResetPassword, nil) 191 return 192 } 193 194 twofa.LastUsedPasscode = passcode 195 if err = auth.UpdateTwoFactor(ctx, twofa); err != nil { 196 ctx.ServerError("ResetPasswdPost: UpdateTwoFactor", err) 197 return 198 } 199 } 200 } 201 202 opts := &user_service.UpdateAuthOptions{ 203 Password: optional.Some(ctx.FormString("password")), 204 MustChangePassword: optional.Some(false), 205 } 206 if err := user_service.UpdateAuth(ctx, u, opts); err != nil { 207 ctx.Data["IsResetForm"] = true 208 ctx.Data["Err_Password"] = true 209 switch { 210 case errors.Is(err, password.ErrMinLength): 211 ctx.RenderWithErr(ctx.Tr("auth.password_too_short", setting.MinPasswordLength), tplResetPassword, nil) 212 case errors.Is(err, password.ErrComplexity): 213 ctx.RenderWithErr(password.BuildComplexityError(ctx.Locale), tplResetPassword, nil) 214 case errors.Is(err, password.ErrIsPwned): 215 ctx.RenderWithErr(ctx.Tr("auth.password_pwned"), tplResetPassword, nil) 216 case password.IsErrIsPwnedRequest(err): 217 ctx.RenderWithErr(ctx.Tr("auth.password_pwned_err"), tplResetPassword, nil) 218 default: 219 ctx.ServerError("UpdateAuth", err) 220 } 221 return 222 } 223 224 log.Trace("User password reset: %s", u.Name) 225 ctx.Data["IsResetFailed"] = true 226 remember := len(ctx.FormString("remember")) != 0 227 228 if regenerateScratchToken { 229 // Invalidate the scratch token. 230 _, err := twofa.GenerateScratchToken() 231 if err != nil { 232 ctx.ServerError("UserSignIn", err) 233 return 234 } 235 if err = auth.UpdateTwoFactor(ctx, twofa); err != nil { 236 ctx.ServerError("UserSignIn", err) 237 return 238 } 239 240 handleSignInFull(ctx, u, remember, false) 241 if ctx.Written() { 242 return 243 } 244 ctx.Flash.Info(ctx.Tr("auth.twofa_scratch_used")) 245 ctx.Redirect(setting.AppSubURL + "/user/settings/security") 246 return 247 } 248 249 handleSignIn(ctx, u, remember) 250 } 251 252 // MustChangePassword renders the page to change a user's password 253 func MustChangePassword(ctx *context.Context) { 254 ctx.Data["Title"] = ctx.Tr("auth.must_change_password") 255 ctx.Data["ChangePasscodeLink"] = setting.AppSubURL + "/user/settings/change_password" 256 ctx.Data["MustChangePassword"] = true 257 ctx.HTML(http.StatusOK, tplMustChangePassword) 258 } 259 260 // MustChangePasswordPost response for updating a user's password after their 261 // account was created by an admin 262 func MustChangePasswordPost(ctx *context.Context) { 263 form := web.GetForm(ctx).(*forms.MustChangePasswordForm) 264 ctx.Data["Title"] = ctx.Tr("auth.must_change_password") 265 ctx.Data["ChangePasscodeLink"] = setting.AppSubURL + "/user/settings/change_password" 266 if ctx.HasError() { 267 ctx.HTML(http.StatusOK, tplMustChangePassword) 268 return 269 } 270 271 // Make sure only requests for users who are eligible to change their password via 272 // this method passes through 273 if !ctx.Doer.MustChangePassword { 274 ctx.ServerError("MustUpdatePassword", errors.New("cannot update password. Please visit the settings page")) 275 return 276 } 277 278 if form.Password != form.Retype { 279 ctx.Data["Err_Password"] = true 280 ctx.RenderWithErr(ctx.Tr("form.password_not_match"), tplMustChangePassword, &form) 281 return 282 } 283 284 opts := &user_service.UpdateAuthOptions{ 285 Password: optional.Some(form.Password), 286 MustChangePassword: optional.Some(false), 287 } 288 if err := user_service.UpdateAuth(ctx, ctx.Doer, opts); err != nil { 289 switch { 290 case errors.Is(err, password.ErrMinLength): 291 ctx.Data["Err_Password"] = true 292 ctx.RenderWithErr(ctx.Tr("auth.password_too_short", setting.MinPasswordLength), tplMustChangePassword, &form) 293 case errors.Is(err, password.ErrComplexity): 294 ctx.Data["Err_Password"] = true 295 ctx.RenderWithErr(password.BuildComplexityError(ctx.Locale), tplMustChangePassword, &form) 296 case errors.Is(err, password.ErrIsPwned): 297 ctx.Data["Err_Password"] = true 298 ctx.RenderWithErr(ctx.Tr("auth.password_pwned"), tplMustChangePassword, &form) 299 case password.IsErrIsPwnedRequest(err): 300 ctx.Data["Err_Password"] = true 301 ctx.RenderWithErr(ctx.Tr("auth.password_pwned_err"), tplMustChangePassword, &form) 302 default: 303 ctx.ServerError("UpdateAuth", err) 304 } 305 return 306 } 307 308 ctx.Flash.Success(ctx.Tr("settings.change_password_success")) 309 310 log.Trace("User updated password: %s", ctx.Doer.Name) 311 312 if redirectTo := ctx.GetSiteCookie("redirect_to"); redirectTo != "" { 313 middleware.DeleteRedirectToCookie(ctx.Resp) 314 ctx.RedirectToCurrentSite(redirectTo) 315 return 316 } 317 318 ctx.Redirect(setting.AppSubURL + "/") 319 }