mirror of
https://github.com/go-gitea/gitea
synced 2025-07-22 18:28:37 +00:00
Unify user update methods (#28733)
Fixes #28660 Fixes an admin api bug related to `user.LoginSource` Fixed `/user/emails` response not identical to GitHub api This PR unifies the user update methods. The goal is to keep the logic only at one place (having audit logs in mind). For example, do the password checks only in one method not everywhere a password is updated. After that PR is merged, the user creation should be next.
This commit is contained in:
@@ -15,6 +15,7 @@ import (
|
||||
"code.gitea.io/gitea/modules/base"
|
||||
"code.gitea.io/gitea/modules/context"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
@@ -53,33 +54,33 @@ func AccountPost(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if len(form.Password) < setting.MinPasswordLength {
|
||||
ctx.Flash.Error(ctx.Tr("auth.password_too_short", setting.MinPasswordLength))
|
||||
} else if ctx.Doer.IsPasswordSet() && !ctx.Doer.ValidatePassword(form.OldPassword) {
|
||||
if ctx.Doer.IsPasswordSet() && !ctx.Doer.ValidatePassword(form.OldPassword) {
|
||||
ctx.Flash.Error(ctx.Tr("settings.password_incorrect"))
|
||||
} else if form.Password != form.Retype {
|
||||
ctx.Flash.Error(ctx.Tr("form.password_not_match"))
|
||||
} else if !password.IsComplexEnough(form.Password) {
|
||||
ctx.Flash.Error(password.BuildComplexityError(ctx.Locale))
|
||||
} else if pwned, err := password.IsPwned(ctx, form.Password); pwned || err != nil {
|
||||
errMsg := ctx.Tr("auth.password_pwned")
|
||||
if err != nil {
|
||||
log.Error(err.Error())
|
||||
errMsg = ctx.Tr("auth.password_pwned_err")
|
||||
}
|
||||
ctx.Flash.Error(errMsg)
|
||||
} else {
|
||||
var err error
|
||||
if err = ctx.Doer.SetPassword(form.Password); err != nil {
|
||||
ctx.ServerError("UpdateUser", err)
|
||||
return
|
||||
opts := &user.UpdateAuthOptions{
|
||||
Password: optional.Some(form.Password),
|
||||
MustChangePassword: optional.Some(false),
|
||||
}
|
||||
if err := user_model.UpdateUserCols(ctx, ctx.Doer, "salt", "passwd_hash_algo", "passwd"); err != nil {
|
||||
ctx.ServerError("UpdateUser", err)
|
||||
return
|
||||
if err := user.UpdateAuth(ctx, ctx.Doer, opts); err != nil {
|
||||
switch {
|
||||
case errors.Is(err, password.ErrMinLength):
|
||||
ctx.Flash.Error(ctx.Tr("auth.password_too_short", setting.MinPasswordLength))
|
||||
case errors.Is(err, password.ErrComplexity):
|
||||
ctx.Flash.Error(password.BuildComplexityError(ctx.Locale))
|
||||
case errors.Is(err, password.ErrIsPwned):
|
||||
ctx.Flash.Error(ctx.Tr("auth.password_pwned"))
|
||||
case password.IsErrIsPwnedRequest(err):
|
||||
log.Error("%s", err.Error())
|
||||
ctx.Flash.Error(ctx.Tr("auth.password_pwned_err"))
|
||||
default:
|
||||
ctx.ServerError("UpdateAuth", err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
ctx.Flash.Success(ctx.Tr("settings.change_password_success"))
|
||||
}
|
||||
log.Trace("User password updated: %s", ctx.Doer.Name)
|
||||
ctx.Flash.Success(ctx.Tr("settings.change_password_success"))
|
||||
}
|
||||
|
||||
ctx.Redirect(setting.AppSubURL + "/user/settings/account")
|
||||
@@ -137,7 +138,7 @@ func EmailPost(ctx *context.Context) {
|
||||
// Only fired when the primary email is inactive (Wrong state)
|
||||
mailer.SendActivateAccountMail(ctx.Locale, ctx.Doer)
|
||||
} else {
|
||||
mailer.SendActivateEmailMail(ctx.Doer, email)
|
||||
mailer.SendActivateEmailMail(ctx.Doer, email.Email)
|
||||
}
|
||||
address = email.Email
|
||||
|
||||
@@ -160,9 +161,12 @@ func EmailPost(ctx *context.Context) {
|
||||
ctx.ServerError("SetEmailPreference", errors.New("option unrecognized"))
|
||||
return
|
||||
}
|
||||
if err := user_model.SetEmailNotifications(ctx, ctx.Doer, preference); err != nil {
|
||||
opts := &user.UpdateOptions{
|
||||
EmailNotificationsPreference: optional.Some(preference),
|
||||
}
|
||||
if err := user.UpdateUser(ctx, ctx.Doer, opts); err != nil {
|
||||
log.Error("Set Email Notifications failed: %v", err)
|
||||
ctx.ServerError("SetEmailNotifications", err)
|
||||
ctx.ServerError("UpdateUser", err)
|
||||
return
|
||||
}
|
||||
log.Trace("Email notifications preference made %s: %s", preference, ctx.Doer.Name)
|
||||
@@ -178,48 +182,47 @@ func EmailPost(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
email := &user_model.EmailAddress{
|
||||
UID: ctx.Doer.ID,
|
||||
Email: form.Email,
|
||||
IsActivated: !setting.Service.RegisterEmailConfirm,
|
||||
}
|
||||
if err := user_model.AddEmailAddress(ctx, email); err != nil {
|
||||
if err := user.AddEmailAddresses(ctx, ctx.Doer, []string{form.Email}); err != nil {
|
||||
if user_model.IsErrEmailAlreadyUsed(err) {
|
||||
loadAccountData(ctx)
|
||||
|
||||
ctx.RenderWithErr(ctx.Tr("form.email_been_used"), tplSettingsAccount, &form)
|
||||
return
|
||||
} else if user_model.IsErrEmailCharIsNotSupported(err) ||
|
||||
user_model.IsErrEmailInvalid(err) {
|
||||
} else if user_model.IsErrEmailCharIsNotSupported(err) || user_model.IsErrEmailInvalid(err) {
|
||||
loadAccountData(ctx)
|
||||
|
||||
ctx.RenderWithErr(ctx.Tr("form.email_invalid"), tplSettingsAccount, &form)
|
||||
return
|
||||
} else {
|
||||
ctx.ServerError("AddEmailAddresses", err)
|
||||
}
|
||||
ctx.ServerError("AddEmailAddress", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Send confirmation email
|
||||
if setting.Service.RegisterEmailConfirm {
|
||||
mailer.SendActivateEmailMail(ctx.Doer, email)
|
||||
mailer.SendActivateEmailMail(ctx.Doer, form.Email)
|
||||
if err := ctx.Cache.Put("MailResendLimit_"+ctx.Doer.LowerName, ctx.Doer.LowerName, 180); err != nil {
|
||||
log.Error("Set cache(MailResendLimit) fail: %v", err)
|
||||
}
|
||||
|
||||
ctx.Flash.Info(ctx.Tr("settings.add_email_confirmation_sent", email.Email, timeutil.MinutesToFriendly(setting.Service.ActiveCodeLives, ctx.Locale)))
|
||||
ctx.Flash.Info(ctx.Tr("settings.add_email_confirmation_sent", form.Email, timeutil.MinutesToFriendly(setting.Service.ActiveCodeLives, ctx.Locale)))
|
||||
} else {
|
||||
ctx.Flash.Success(ctx.Tr("settings.add_email_success"))
|
||||
}
|
||||
|
||||
log.Trace("Email address added: %s", email.Email)
|
||||
log.Trace("Email address added: %s", form.Email)
|
||||
ctx.Redirect(setting.AppSubURL + "/user/settings/account")
|
||||
}
|
||||
|
||||
// DeleteEmail response for delete user's email
|
||||
func DeleteEmail(ctx *context.Context) {
|
||||
if err := user_model.DeleteEmailAddress(ctx, &user_model.EmailAddress{ID: ctx.FormInt64("id"), UID: ctx.Doer.ID}); err != nil {
|
||||
ctx.ServerError("DeleteEmail", err)
|
||||
email, err := user_model.GetEmailAddressByID(ctx, ctx.Doer.ID, ctx.FormInt64("id"))
|
||||
if err != nil || email == nil {
|
||||
ctx.ServerError("GetEmailAddressByID", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := user.DeleteEmailAddresses(ctx, ctx.Doer, []string{email.Email}); err != nil {
|
||||
ctx.ServerError("DeleteEmailAddresses", err)
|
||||
return
|
||||
}
|
||||
log.Trace("Email address deleted: %s", ctx.Doer.Name)
|
||||
@@ -293,7 +296,7 @@ func loadAccountData(ctx *context.Context) {
|
||||
emails[i] = &email
|
||||
}
|
||||
ctx.Data["Emails"] = emails
|
||||
ctx.Data["EmailNotificationsPreference"] = ctx.Doer.EmailNotifications()
|
||||
ctx.Data["EmailNotificationsPreference"] = ctx.Doer.EmailNotificationsPreference
|
||||
ctx.Data["ActivationsPending"] = pendingActivation
|
||||
ctx.Data["CanAddEmails"] = !pendingActivation || !setting.Service.RegisterEmailConfirm
|
||||
|
||||
|
@@ -22,6 +22,7 @@ import (
|
||||
"code.gitea.io/gitea/modules/base"
|
||||
"code.gitea.io/gitea/modules/context"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/translation"
|
||||
"code.gitea.io/gitea/modules/typesniffer"
|
||||
@@ -49,40 +50,8 @@ func Profile(ctx *context.Context) {
|
||||
ctx.HTML(http.StatusOK, tplSettingsProfile)
|
||||
}
|
||||
|
||||
// HandleUsernameChange handle username changes from user settings and admin interface
|
||||
func HandleUsernameChange(ctx *context.Context, user *user_model.User, newName string) error {
|
||||
oldName := user.Name
|
||||
// rename user
|
||||
if err := user_service.RenameUser(ctx, user, newName); err != nil {
|
||||
switch {
|
||||
// Noop as username is not changed
|
||||
case user_model.IsErrUsernameNotChanged(err):
|
||||
ctx.Flash.Error(ctx.Tr("form.username_has_not_been_changed"))
|
||||
// Non-local users are not allowed to change their username.
|
||||
case user_model.IsErrUserIsNotLocal(err):
|
||||
ctx.Flash.Error(ctx.Tr("form.username_change_not_local_user"))
|
||||
case user_model.IsErrUserAlreadyExist(err):
|
||||
ctx.Flash.Error(ctx.Tr("form.username_been_taken"))
|
||||
case user_model.IsErrEmailAlreadyUsed(err):
|
||||
ctx.Flash.Error(ctx.Tr("form.email_been_used"))
|
||||
case db.IsErrNameReserved(err):
|
||||
ctx.Flash.Error(ctx.Tr("user.form.name_reserved", newName))
|
||||
case db.IsErrNamePatternNotAllowed(err):
|
||||
ctx.Flash.Error(ctx.Tr("user.form.name_pattern_not_allowed", newName))
|
||||
case db.IsErrNameCharsNotAllowed(err):
|
||||
ctx.Flash.Error(ctx.Tr("user.form.name_chars_not_allowed", newName))
|
||||
default:
|
||||
ctx.ServerError("ChangeUserName", err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
log.Trace("User name changed: %s -> %s", oldName, newName)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ProfilePost response for change user's profile
|
||||
func ProfilePost(ctx *context.Context) {
|
||||
form := web.GetForm(ctx).(*forms.UpdateProfileForm)
|
||||
ctx.Data["Title"] = ctx.Tr("settings")
|
||||
ctx.Data["PageIsSettingsProfile"] = true
|
||||
ctx.Data["AllowedUserVisibilityModes"] = setting.Service.AllowedUserVisibilityModesSlice.ToVisibleTypeSlice()
|
||||
@@ -93,29 +62,40 @@ func ProfilePost(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if len(form.Name) != 0 && ctx.Doer.Name != form.Name {
|
||||
log.Debug("Changing name for %s to %s", ctx.Doer.Name, form.Name)
|
||||
if err := HandleUsernameChange(ctx, ctx.Doer, form.Name); err != nil {
|
||||
form := web.GetForm(ctx).(*forms.UpdateProfileForm)
|
||||
|
||||
if form.Name != "" {
|
||||
if err := user_service.RenameUser(ctx, ctx.Doer, form.Name); err != nil {
|
||||
switch {
|
||||
case user_model.IsErrUserIsNotLocal(err):
|
||||
ctx.Flash.Error(ctx.Tr("form.username_change_not_local_user"))
|
||||
case user_model.IsErrUserAlreadyExist(err):
|
||||
ctx.Flash.Error(ctx.Tr("form.username_been_taken"))
|
||||
case db.IsErrNameReserved(err):
|
||||
ctx.Flash.Error(ctx.Tr("user.form.name_reserved", form.Name))
|
||||
case db.IsErrNamePatternNotAllowed(err):
|
||||
ctx.Flash.Error(ctx.Tr("user.form.name_pattern_not_allowed", form.Name))
|
||||
case db.IsErrNameCharsNotAllowed(err):
|
||||
ctx.Flash.Error(ctx.Tr("user.form.name_chars_not_allowed", form.Name))
|
||||
default:
|
||||
ctx.ServerError("RenameUser", err)
|
||||
return
|
||||
}
|
||||
ctx.Redirect(setting.AppSubURL + "/user/settings")
|
||||
return
|
||||
}
|
||||
ctx.Doer.Name = form.Name
|
||||
ctx.Doer.LowerName = strings.ToLower(form.Name)
|
||||
}
|
||||
|
||||
ctx.Doer.FullName = form.FullName
|
||||
ctx.Doer.KeepEmailPrivate = form.KeepEmailPrivate
|
||||
ctx.Doer.Website = form.Website
|
||||
ctx.Doer.Location = form.Location
|
||||
ctx.Doer.Description = form.Description
|
||||
ctx.Doer.KeepActivityPrivate = form.KeepActivityPrivate
|
||||
ctx.Doer.Visibility = form.Visibility
|
||||
if err := user_model.UpdateUserSetting(ctx, ctx.Doer); err != nil {
|
||||
if _, ok := err.(user_model.ErrEmailAlreadyUsed); ok {
|
||||
ctx.Flash.Error(ctx.Tr("form.email_been_used"))
|
||||
ctx.Redirect(setting.AppSubURL + "/user/settings")
|
||||
return
|
||||
}
|
||||
opts := &user_service.UpdateOptions{
|
||||
FullName: optional.Some(form.FullName),
|
||||
KeepEmailPrivate: optional.Some(form.KeepEmailPrivate),
|
||||
Description: optional.Some(form.Description),
|
||||
Website: optional.Some(form.Website),
|
||||
Location: optional.Some(form.Location),
|
||||
Visibility: optional.Some(form.Visibility),
|
||||
KeepActivityPrivate: optional.Some(form.KeepActivityPrivate),
|
||||
}
|
||||
if err := user_service.UpdateUser(ctx, ctx.Doer, opts); err != nil {
|
||||
ctx.ServerError("UpdateUser", err)
|
||||
return
|
||||
}
|
||||
@@ -170,7 +150,7 @@ func UpdateAvatarSetting(ctx *context.Context, form *forms.AvatarForm, ctxUser *
|
||||
}
|
||||
|
||||
if err := user_model.UpdateUserCols(ctx, ctxUser, "avatar", "avatar_email", "use_custom_avatar"); err != nil {
|
||||
return fmt.Errorf("UpdateUser: %w", err)
|
||||
return fmt.Errorf("UpdateUserCols: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -371,14 +351,15 @@ func UpdateUIThemePost(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if err := user_model.UpdateUserTheme(ctx, ctx.Doer, form.Theme); err != nil {
|
||||
opts := &user_service.UpdateOptions{
|
||||
Theme: optional.Some(form.Theme),
|
||||
}
|
||||
if err := user_service.UpdateUser(ctx, ctx.Doer, opts); err != nil {
|
||||
ctx.Flash.Error(ctx.Tr("settings.theme_update_error"))
|
||||
ctx.Redirect(setting.AppSubURL + "/user/settings/appearance")
|
||||
return
|
||||
} else {
|
||||
ctx.Flash.Success(ctx.Tr("settings.theme_update_success"))
|
||||
}
|
||||
|
||||
log.Trace("Update user theme: %s", ctx.Doer.Name)
|
||||
ctx.Flash.Success(ctx.Tr("settings.theme_update_success"))
|
||||
ctx.Redirect(setting.AppSubURL + "/user/settings/appearance")
|
||||
}
|
||||
|
||||
@@ -388,17 +369,19 @@ func UpdateUserLang(ctx *context.Context) {
|
||||
ctx.Data["Title"] = ctx.Tr("settings")
|
||||
ctx.Data["PageIsSettingsAppearance"] = true
|
||||
|
||||
if len(form.Language) != 0 {
|
||||
if form.Language != "" {
|
||||
if !util.SliceContainsString(setting.Langs, form.Language) {
|
||||
ctx.Flash.Error(ctx.Tr("settings.update_language_not_found", form.Language))
|
||||
ctx.Redirect(setting.AppSubURL + "/user/settings/appearance")
|
||||
return
|
||||
}
|
||||
ctx.Doer.Language = form.Language
|
||||
}
|
||||
|
||||
if err := user_model.UpdateUserSetting(ctx, ctx.Doer); err != nil {
|
||||
ctx.ServerError("UpdateUserSetting", err)
|
||||
opts := &user_service.UpdateOptions{
|
||||
Language: optional.Some(form.Language),
|
||||
}
|
||||
if err := user_service.UpdateUser(ctx, ctx.Doer, opts); err != nil {
|
||||
ctx.ServerError("UpdateUser", err)
|
||||
return
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user