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