123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346 |
- // Copyright 2019 The Gitea Authors. All rights reserved.
- // SPDX-License-Identifier: MIT
-
- package auth
-
- import (
- "errors"
- "net/http"
-
- "code.gitea.io/gitea/models/auth"
- user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/auth/password"
- "code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/context"
- "code.gitea.io/gitea/modules/log"
- "code.gitea.io/gitea/modules/setting"
- "code.gitea.io/gitea/modules/timeutil"
- "code.gitea.io/gitea/modules/web"
- "code.gitea.io/gitea/modules/web/middleware"
- "code.gitea.io/gitea/routers/utils"
- "code.gitea.io/gitea/services/forms"
- "code.gitea.io/gitea/services/mailer"
- )
-
- var (
- // tplMustChangePassword template for updating a user's password
- tplMustChangePassword base.TplName = "user/auth/change_passwd"
- tplForgotPassword base.TplName = "user/auth/forgot_passwd"
- tplResetPassword base.TplName = "user/auth/reset_passwd"
- )
-
- // ForgotPasswd render the forget password page
- func ForgotPasswd(ctx *context.Context) {
- ctx.Data["Title"] = ctx.Tr("auth.forgot_password_title")
-
- if setting.MailService == nil {
- log.Warn(ctx.Tr("auth.disable_forgot_password_mail_admin"))
- ctx.Data["IsResetDisable"] = true
- ctx.HTML(http.StatusOK, tplForgotPassword)
- return
- }
-
- ctx.Data["Email"] = ctx.FormString("email")
-
- ctx.Data["IsResetRequest"] = true
- ctx.HTML(http.StatusOK, tplForgotPassword)
- }
-
- // ForgotPasswdPost response for forget password request
- func ForgotPasswdPost(ctx *context.Context) {
- ctx.Data["Title"] = ctx.Tr("auth.forgot_password_title")
-
- if setting.MailService == nil {
- ctx.NotFound("ForgotPasswdPost", nil)
- return
- }
- ctx.Data["IsResetRequest"] = true
-
- email := ctx.FormString("email")
- ctx.Data["Email"] = email
-
- u, err := user_model.GetUserByEmail(ctx, email)
- if err != nil {
- if user_model.IsErrUserNotExist(err) {
- ctx.Data["ResetPwdCodeLives"] = timeutil.MinutesToFriendly(setting.Service.ResetPwdCodeLives, ctx.Locale)
- ctx.Data["IsResetSent"] = true
- ctx.HTML(http.StatusOK, tplForgotPassword)
- return
- }
-
- ctx.ServerError("user.ResetPasswd(check existence)", err)
- return
- }
-
- if !u.IsLocal() && !u.IsOAuth2() {
- ctx.Data["Err_Email"] = true
- ctx.RenderWithErr(ctx.Tr("auth.non_local_account"), tplForgotPassword, nil)
- return
- }
-
- if setting.CacheService.Enabled && ctx.Cache.IsExist("MailResendLimit_"+u.LowerName) {
- ctx.Data["ResendLimited"] = true
- ctx.HTML(http.StatusOK, tplForgotPassword)
- return
- }
-
- mailer.SendResetPasswordMail(u)
-
- if setting.CacheService.Enabled {
- if err = ctx.Cache.Put("MailResendLimit_"+u.LowerName, u.LowerName, 180); err != nil {
- log.Error("Set cache(MailResendLimit) fail: %v", err)
- }
- }
-
- ctx.Data["ResetPwdCodeLives"] = timeutil.MinutesToFriendly(setting.Service.ResetPwdCodeLives, ctx.Locale)
- ctx.Data["IsResetSent"] = true
- ctx.HTML(http.StatusOK, tplForgotPassword)
- }
-
- func commonResetPassword(ctx *context.Context) (*user_model.User, *auth.TwoFactor) {
- code := ctx.FormString("code")
-
- ctx.Data["Title"] = ctx.Tr("auth.reset_password")
- ctx.Data["Code"] = code
-
- if nil != ctx.Doer {
- ctx.Data["user_signed_in"] = true
- }
-
- if len(code) == 0 {
- ctx.Flash.Error(ctx.Tr("auth.invalid_code"))
- return nil, nil
- }
-
- // Fail early, don't frustrate the user
- u := user_model.VerifyUserActiveCode(code)
- if u == nil {
- ctx.Flash.Error(ctx.Tr("auth.invalid_code"))
- return nil, nil
- }
-
- twofa, err := auth.GetTwoFactorByUID(u.ID)
- if err != nil {
- if !auth.IsErrTwoFactorNotEnrolled(err) {
- ctx.Error(http.StatusInternalServerError, "CommonResetPassword", err.Error())
- return nil, nil
- }
- } else {
- ctx.Data["has_two_factor"] = true
- ctx.Data["scratch_code"] = ctx.FormBool("scratch_code")
- }
-
- // Show the user that they are affecting the account that they intended to
- ctx.Data["user_email"] = u.Email
-
- if nil != ctx.Doer && u.ID != ctx.Doer.ID {
- ctx.Flash.Error(ctx.Tr("auth.reset_password_wrong_user", ctx.Doer.Email, u.Email))
- return nil, nil
- }
-
- return u, twofa
- }
-
- // ResetPasswd render the account recovery page
- func ResetPasswd(ctx *context.Context) {
- ctx.Data["IsResetForm"] = true
-
- commonResetPassword(ctx)
- if ctx.Written() {
- return
- }
-
- ctx.HTML(http.StatusOK, tplResetPassword)
- }
-
- // ResetPasswdPost response from account recovery request
- func ResetPasswdPost(ctx *context.Context) {
- u, twofa := commonResetPassword(ctx)
- if ctx.Written() {
- return
- }
-
- if u == nil {
- // Flash error has been set
- ctx.HTML(http.StatusOK, tplResetPassword)
- return
- }
-
- // Validate password length.
- passwd := ctx.FormString("password")
- if len(passwd) < setting.MinPasswordLength {
- ctx.Data["IsResetForm"] = true
- ctx.Data["Err_Password"] = true
- ctx.RenderWithErr(ctx.Tr("auth.password_too_short", setting.MinPasswordLength), tplResetPassword, nil)
- return
- } else if !password.IsComplexEnough(passwd) {
- ctx.Data["IsResetForm"] = true
- ctx.Data["Err_Password"] = true
- ctx.RenderWithErr(password.BuildComplexityError(ctx.Locale), tplResetPassword, nil)
- return
- } else if pwned, err := password.IsPwned(ctx, passwd); pwned || err != nil {
- errMsg := ctx.Tr("auth.password_pwned")
- if err != nil {
- log.Error(err.Error())
- errMsg = ctx.Tr("auth.password_pwned_err")
- }
- ctx.Data["IsResetForm"] = true
- ctx.Data["Err_Password"] = true
- ctx.RenderWithErr(errMsg, tplResetPassword, nil)
- return
- }
-
- // Handle two-factor
- regenerateScratchToken := false
- if twofa != nil {
- if ctx.FormBool("scratch_code") {
- if !twofa.VerifyScratchToken(ctx.FormString("token")) {
- ctx.Data["IsResetForm"] = true
- ctx.Data["Err_Token"] = true
- ctx.RenderWithErr(ctx.Tr("auth.twofa_scratch_token_incorrect"), tplResetPassword, nil)
- return
- }
- regenerateScratchToken = true
- } else {
- passcode := ctx.FormString("passcode")
- ok, err := twofa.ValidateTOTP(passcode)
- if err != nil {
- ctx.Error(http.StatusInternalServerError, "ValidateTOTP", err.Error())
- return
- }
- if !ok || twofa.LastUsedPasscode == passcode {
- ctx.Data["IsResetForm"] = true
- ctx.Data["Err_Passcode"] = true
- ctx.RenderWithErr(ctx.Tr("auth.twofa_passcode_incorrect"), tplResetPassword, nil)
- return
- }
-
- twofa.LastUsedPasscode = passcode
- if err = auth.UpdateTwoFactor(twofa); err != nil {
- ctx.ServerError("ResetPasswdPost: UpdateTwoFactor", err)
- return
- }
- }
- }
- var err error
- if u.Rands, err = user_model.GetUserSalt(); err != nil {
- ctx.ServerError("UpdateUser", err)
- return
- }
- if err = u.SetPassword(passwd); err != nil {
- ctx.ServerError("UpdateUser", err)
- return
- }
- u.MustChangePassword = false
- if err := user_model.UpdateUserCols(ctx, u, "must_change_password", "passwd", "passwd_hash_algo", "rands", "salt"); err != nil {
- ctx.ServerError("UpdateUser", err)
- return
- }
-
- log.Trace("User password reset: %s", u.Name)
- ctx.Data["IsResetFailed"] = true
- remember := len(ctx.FormString("remember")) != 0
-
- if regenerateScratchToken {
- // Invalidate the scratch token.
- _, err = twofa.GenerateScratchToken()
- if err != nil {
- ctx.ServerError("UserSignIn", err)
- return
- }
- if err = auth.UpdateTwoFactor(twofa); err != nil {
- ctx.ServerError("UserSignIn", err)
- return
- }
-
- handleSignInFull(ctx, u, remember, false)
- if ctx.Written() {
- return
- }
- ctx.Flash.Info(ctx.Tr("auth.twofa_scratch_used"))
- ctx.Redirect(setting.AppSubURL + "/user/settings/security")
- return
- }
-
- handleSignIn(ctx, u, remember)
- }
-
- // MustChangePassword renders the page to change a user's password
- func MustChangePassword(ctx *context.Context) {
- ctx.Data["Title"] = ctx.Tr("auth.must_change_password")
- ctx.Data["ChangePasscodeLink"] = setting.AppSubURL + "/user/settings/change_password"
- ctx.Data["MustChangePassword"] = true
- ctx.HTML(http.StatusOK, tplMustChangePassword)
- }
-
- // MustChangePasswordPost response for updating a user's password after their
- // account was created by an admin
- func MustChangePasswordPost(ctx *context.Context) {
- form := web.GetForm(ctx).(*forms.MustChangePasswordForm)
- ctx.Data["Title"] = ctx.Tr("auth.must_change_password")
- ctx.Data["ChangePasscodeLink"] = setting.AppSubURL + "/user/settings/change_password"
- if ctx.HasError() {
- ctx.HTML(http.StatusOK, tplMustChangePassword)
- return
- }
- u := ctx.Doer
- // Make sure only requests for users who are eligible to change their password via
- // this method passes through
- if !u.MustChangePassword {
- ctx.ServerError("MustUpdatePassword", errors.New("cannot update password.. Please visit the settings page"))
- return
- }
-
- if form.Password != form.Retype {
- ctx.Data["Err_Password"] = true
- ctx.RenderWithErr(ctx.Tr("form.password_not_match"), tplMustChangePassword, &form)
- return
- }
-
- if len(form.Password) < setting.MinPasswordLength {
- ctx.Data["Err_Password"] = true
- ctx.RenderWithErr(ctx.Tr("auth.password_too_short", setting.MinPasswordLength), tplMustChangePassword, &form)
- return
- }
-
- if !password.IsComplexEnough(form.Password) {
- ctx.Data["Err_Password"] = true
- ctx.RenderWithErr(password.BuildComplexityError(ctx.Locale), tplMustChangePassword, &form)
- return
- }
- pwned, err := password.IsPwned(ctx, form.Password)
- if pwned {
- ctx.Data["Err_Password"] = true
- errMsg := ctx.Tr("auth.password_pwned")
- if err != nil {
- log.Error(err.Error())
- errMsg = ctx.Tr("auth.password_pwned_err")
- }
- ctx.RenderWithErr(errMsg, tplMustChangePassword, &form)
- return
- }
-
- if err = u.SetPassword(form.Password); err != nil {
- ctx.ServerError("UpdateUser", err)
- return
- }
-
- u.MustChangePassword = false
-
- if err := user_model.UpdateUserCols(ctx, u, "must_change_password", "passwd", "passwd_hash_algo", "salt"); err != nil {
- ctx.ServerError("UpdateUser", err)
- return
- }
-
- ctx.Flash.Success(ctx.Tr("settings.change_password_success"))
-
- log.Trace("User updated password: %s", u.Name)
-
- if redirectTo := ctx.GetSiteCookie("redirect_to"); len(redirectTo) > 0 && !utils.IsExternalURL(redirectTo) {
- middleware.DeleteRedirectToCookie(ctx.Resp)
- ctx.RedirectToFirst(redirectTo)
- return
- }
-
- ctx.Redirect(setting.AppSubURL + "/")
- }
|