mirror of
https://github.com/go-gitea/gitea
synced 2025-07-22 18:28:37 +00:00
Refactor auth package (#17962)
This commit is contained in:
346
routers/web/auth/password.go
Normal file
346
routers/web/auth/password.go
Normal file
@@ -0,0 +1,346 @@
|
||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package auth
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"code.gitea.io/gitea/models/auth"
|
||||
"code.gitea.io/gitea/models/db"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/base"
|
||||
"code.gitea.io/gitea/modules/context"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/password"
|
||||
"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(email)
|
||||
if err != nil {
|
||||
if user_model.IsErrUserNotExist(err) {
|
||||
ctx.Data["ResetPwdCodeLives"] = timeutil.MinutesToFriendly(setting.Service.ResetPwdCodeLives, ctx.Locale.Language())
|
||||
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 ctx.Cache.IsExist("MailResendLimit_" + u.LowerName) {
|
||||
ctx.Data["ResendLimited"] = true
|
||||
ctx.HTML(http.StatusOK, tplForgotPassword)
|
||||
return
|
||||
}
|
||||
|
||||
mailer.SendResetPasswordMail(u)
|
||||
|
||||
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.Language())
|
||||
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.User {
|
||||
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.User && u.ID != ctx.User.ID {
|
||||
ctx.Flash.Error(ctx.Tr("auth.reset_password_wrong_user", ctx.User.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), 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(db.DefaultContext, 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 his/her
|
||||
// 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.User
|
||||
// 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), 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(db.DefaultContext, 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.GetCookie("redirect_to"); len(redirectTo) > 0 && !utils.IsExternalURL(redirectTo) {
|
||||
middleware.DeleteRedirectToCookie(ctx.Resp)
|
||||
ctx.RedirectToFirst(redirectTo)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Redirect(setting.AppSubURL + "/")
|
||||
}
|
Reference in New Issue
Block a user