code.gitea.io/gitea@v1.22.3/routers/web/user/setting/account.go (about)

     1  // Copyright 2014 The Gogs Authors. All rights reserved.
     2  // Copyright 2018 The Gitea Authors. All rights reserved.
     3  // SPDX-License-Identifier: MIT
     4  
     5  package setting
     6  
     7  import (
     8  	"errors"
     9  	"net/http"
    10  	"time"
    11  
    12  	"code.gitea.io/gitea/models"
    13  	user_model "code.gitea.io/gitea/models/user"
    14  	"code.gitea.io/gitea/modules/auth/password"
    15  	"code.gitea.io/gitea/modules/base"
    16  	"code.gitea.io/gitea/modules/log"
    17  	"code.gitea.io/gitea/modules/optional"
    18  	"code.gitea.io/gitea/modules/setting"
    19  	"code.gitea.io/gitea/modules/timeutil"
    20  	"code.gitea.io/gitea/modules/web"
    21  	"code.gitea.io/gitea/services/auth"
    22  	"code.gitea.io/gitea/services/auth/source/db"
    23  	"code.gitea.io/gitea/services/auth/source/smtp"
    24  	"code.gitea.io/gitea/services/context"
    25  	"code.gitea.io/gitea/services/forms"
    26  	"code.gitea.io/gitea/services/mailer"
    27  	"code.gitea.io/gitea/services/user"
    28  )
    29  
    30  const (
    31  	tplSettingsAccount base.TplName = "user/settings/account"
    32  )
    33  
    34  // Account renders change user's password, user's email and user suicide page
    35  func Account(ctx *context.Context) {
    36  	ctx.Data["Title"] = ctx.Tr("settings.account")
    37  	ctx.Data["PageIsSettingsAccount"] = true
    38  	ctx.Data["Email"] = ctx.Doer.Email
    39  	ctx.Data["EnableNotifyMail"] = setting.Service.EnableNotifyMail
    40  
    41  	loadAccountData(ctx)
    42  
    43  	ctx.HTML(http.StatusOK, tplSettingsAccount)
    44  }
    45  
    46  // AccountPost response for change user's password
    47  func AccountPost(ctx *context.Context) {
    48  	form := web.GetForm(ctx).(*forms.ChangePasswordForm)
    49  	ctx.Data["Title"] = ctx.Tr("settings")
    50  	ctx.Data["PageIsSettingsAccount"] = true
    51  
    52  	if ctx.HasError() {
    53  		loadAccountData(ctx)
    54  
    55  		ctx.HTML(http.StatusOK, tplSettingsAccount)
    56  		return
    57  	}
    58  
    59  	if ctx.Doer.IsPasswordSet() && !ctx.Doer.ValidatePassword(form.OldPassword) {
    60  		ctx.Flash.Error(ctx.Tr("settings.password_incorrect"))
    61  	} else if form.Password != form.Retype {
    62  		ctx.Flash.Error(ctx.Tr("form.password_not_match"))
    63  	} else {
    64  		opts := &user.UpdateAuthOptions{
    65  			Password:           optional.Some(form.Password),
    66  			MustChangePassword: optional.Some(false),
    67  		}
    68  		if err := user.UpdateAuth(ctx, ctx.Doer, opts); err != nil {
    69  			switch {
    70  			case errors.Is(err, password.ErrMinLength):
    71  				ctx.Flash.Error(ctx.Tr("auth.password_too_short", setting.MinPasswordLength))
    72  			case errors.Is(err, password.ErrComplexity):
    73  				ctx.Flash.Error(password.BuildComplexityError(ctx.Locale))
    74  			case errors.Is(err, password.ErrIsPwned):
    75  				ctx.Flash.Error(ctx.Tr("auth.password_pwned"))
    76  			case password.IsErrIsPwnedRequest(err):
    77  				ctx.Flash.Error(ctx.Tr("auth.password_pwned_err"))
    78  			default:
    79  				ctx.ServerError("UpdateAuth", err)
    80  				return
    81  			}
    82  		} else {
    83  			ctx.Flash.Success(ctx.Tr("settings.change_password_success"))
    84  		}
    85  	}
    86  
    87  	ctx.Redirect(setting.AppSubURL + "/user/settings/account")
    88  }
    89  
    90  // EmailPost response for change user's email
    91  func EmailPost(ctx *context.Context) {
    92  	form := web.GetForm(ctx).(*forms.AddEmailForm)
    93  	ctx.Data["Title"] = ctx.Tr("settings")
    94  	ctx.Data["PageIsSettingsAccount"] = true
    95  
    96  	// Make email address primary.
    97  	if ctx.FormString("_method") == "PRIMARY" {
    98  		if err := user_model.MakeActiveEmailPrimary(ctx, ctx.FormInt64("id")); err != nil {
    99  			ctx.ServerError("MakeEmailPrimary", err)
   100  			return
   101  		}
   102  
   103  		log.Trace("Email made primary: %s", ctx.Doer.Name)
   104  		ctx.Redirect(setting.AppSubURL + "/user/settings/account")
   105  		return
   106  	}
   107  	// Send activation Email
   108  	if ctx.FormString("_method") == "SENDACTIVATION" {
   109  		var address string
   110  		if ctx.Cache.IsExist("MailResendLimit_" + ctx.Doer.LowerName) {
   111  			log.Error("Send activation: activation still pending")
   112  			ctx.Redirect(setting.AppSubURL + "/user/settings/account")
   113  			return
   114  		}
   115  
   116  		id := ctx.FormInt64("id")
   117  		email, err := user_model.GetEmailAddressByID(ctx, ctx.Doer.ID, id)
   118  		if err != nil {
   119  			log.Error("GetEmailAddressByID(%d,%d) error: %v", ctx.Doer.ID, id, err)
   120  			ctx.Redirect(setting.AppSubURL + "/user/settings/account")
   121  			return
   122  		}
   123  		if email == nil {
   124  			log.Warn("Send activation failed: EmailAddress[%d] not found for user: %-v", id, ctx.Doer)
   125  			ctx.Redirect(setting.AppSubURL + "/user/settings/account")
   126  			return
   127  		}
   128  		if email.IsActivated {
   129  			log.Debug("Send activation failed: email %s is already activated for user: %-v", email.Email, ctx.Doer)
   130  			ctx.Redirect(setting.AppSubURL + "/user/settings/account")
   131  			return
   132  		}
   133  		if email.IsPrimary {
   134  			if ctx.Doer.IsActive && !setting.Service.RegisterEmailConfirm {
   135  				log.Debug("Send activation failed: email %s is already activated for user: %-v", email.Email, ctx.Doer)
   136  				ctx.Redirect(setting.AppSubURL + "/user/settings/account")
   137  				return
   138  			}
   139  			// Only fired when the primary email is inactive (Wrong state)
   140  			mailer.SendActivateAccountMail(ctx.Locale, ctx.Doer)
   141  		} else {
   142  			mailer.SendActivateEmailMail(ctx.Doer, email.Email)
   143  		}
   144  		address = email.Email
   145  
   146  		if err := ctx.Cache.Put("MailResendLimit_"+ctx.Doer.LowerName, ctx.Doer.LowerName, 180); err != nil {
   147  			log.Error("Set cache(MailResendLimit) fail: %v", err)
   148  		}
   149  
   150  		ctx.Flash.Info(ctx.Tr("settings.add_email_confirmation_sent", address, timeutil.MinutesToFriendly(setting.Service.ActiveCodeLives, ctx.Locale)))
   151  		ctx.Redirect(setting.AppSubURL + "/user/settings/account")
   152  		return
   153  	}
   154  	// Set Email Notification Preference
   155  	if ctx.FormString("_method") == "NOTIFICATION" {
   156  		preference := ctx.FormString("preference")
   157  		if !(preference == user_model.EmailNotificationsEnabled ||
   158  			preference == user_model.EmailNotificationsOnMention ||
   159  			preference == user_model.EmailNotificationsDisabled ||
   160  			preference == user_model.EmailNotificationsAndYourOwn) {
   161  			log.Error("Email notifications preference change returned unrecognized option %s: %s", preference, ctx.Doer.Name)
   162  			ctx.ServerError("SetEmailPreference", errors.New("option unrecognized"))
   163  			return
   164  		}
   165  		opts := &user.UpdateOptions{
   166  			EmailNotificationsPreference: optional.Some(preference),
   167  		}
   168  		if err := user.UpdateUser(ctx, ctx.Doer, opts); err != nil {
   169  			log.Error("Set Email Notifications failed: %v", err)
   170  			ctx.ServerError("UpdateUser", err)
   171  			return
   172  		}
   173  		log.Trace("Email notifications preference made %s: %s", preference, ctx.Doer.Name)
   174  		ctx.Flash.Success(ctx.Tr("settings.email_preference_set_success"))
   175  		ctx.Redirect(setting.AppSubURL + "/user/settings/account")
   176  		return
   177  	}
   178  
   179  	if ctx.HasError() {
   180  		loadAccountData(ctx)
   181  
   182  		ctx.HTML(http.StatusOK, tplSettingsAccount)
   183  		return
   184  	}
   185  
   186  	if err := user.AddEmailAddresses(ctx, ctx.Doer, []string{form.Email}); err != nil {
   187  		if user_model.IsErrEmailAlreadyUsed(err) {
   188  			loadAccountData(ctx)
   189  
   190  			ctx.RenderWithErr(ctx.Tr("form.email_been_used"), tplSettingsAccount, &form)
   191  		} else if user_model.IsErrEmailCharIsNotSupported(err) || user_model.IsErrEmailInvalid(err) {
   192  			loadAccountData(ctx)
   193  
   194  			ctx.RenderWithErr(ctx.Tr("form.email_invalid"), tplSettingsAccount, &form)
   195  		} else {
   196  			ctx.ServerError("AddEmailAddresses", err)
   197  		}
   198  		return
   199  	}
   200  
   201  	// Send confirmation email
   202  	if setting.Service.RegisterEmailConfirm {
   203  		mailer.SendActivateEmailMail(ctx.Doer, form.Email)
   204  		if err := ctx.Cache.Put("MailResendLimit_"+ctx.Doer.LowerName, ctx.Doer.LowerName, 180); err != nil {
   205  			log.Error("Set cache(MailResendLimit) fail: %v", err)
   206  		}
   207  
   208  		ctx.Flash.Info(ctx.Tr("settings.add_email_confirmation_sent", form.Email, timeutil.MinutesToFriendly(setting.Service.ActiveCodeLives, ctx.Locale)))
   209  	} else {
   210  		ctx.Flash.Success(ctx.Tr("settings.add_email_success"))
   211  	}
   212  
   213  	log.Trace("Email address added: %s", form.Email)
   214  	ctx.Redirect(setting.AppSubURL + "/user/settings/account")
   215  }
   216  
   217  // DeleteEmail response for delete user's email
   218  func DeleteEmail(ctx *context.Context) {
   219  	email, err := user_model.GetEmailAddressByID(ctx, ctx.Doer.ID, ctx.FormInt64("id"))
   220  	if err != nil || email == nil {
   221  		ctx.ServerError("GetEmailAddressByID", err)
   222  		return
   223  	}
   224  
   225  	if err := user.DeleteEmailAddresses(ctx, ctx.Doer, []string{email.Email}); err != nil {
   226  		ctx.ServerError("DeleteEmailAddresses", err)
   227  		return
   228  	}
   229  	log.Trace("Email address deleted: %s", ctx.Doer.Name)
   230  
   231  	ctx.Flash.Success(ctx.Tr("settings.email_deletion_success"))
   232  	ctx.JSONRedirect(setting.AppSubURL + "/user/settings/account")
   233  }
   234  
   235  // DeleteAccount render user suicide page and response for delete user himself
   236  func DeleteAccount(ctx *context.Context) {
   237  	if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureDeletion) {
   238  		ctx.Error(http.StatusNotFound)
   239  		return
   240  	}
   241  
   242  	ctx.Data["Title"] = ctx.Tr("settings")
   243  	ctx.Data["PageIsSettingsAccount"] = true
   244  
   245  	if _, _, err := auth.UserSignIn(ctx, ctx.Doer.Name, ctx.FormString("password")); err != nil {
   246  		switch {
   247  		case user_model.IsErrUserNotExist(err):
   248  			loadAccountData(ctx)
   249  
   250  			ctx.RenderWithErr(ctx.Tr("form.user_not_exist"), tplSettingsAccount, nil)
   251  		case errors.Is(err, smtp.ErrUnsupportedLoginType):
   252  			loadAccountData(ctx)
   253  
   254  			ctx.RenderWithErr(ctx.Tr("form.unsupported_login_type"), tplSettingsAccount, nil)
   255  		case errors.As(err, &db.ErrUserPasswordNotSet{}):
   256  			loadAccountData(ctx)
   257  
   258  			ctx.RenderWithErr(ctx.Tr("form.unset_password"), tplSettingsAccount, nil)
   259  		case errors.As(err, &db.ErrUserPasswordInvalid{}):
   260  			loadAccountData(ctx)
   261  
   262  			ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_password"), tplSettingsAccount, nil)
   263  		default:
   264  			ctx.ServerError("UserSignIn", err)
   265  		}
   266  		return
   267  	}
   268  
   269  	// admin should not delete themself
   270  	if ctx.Doer.IsAdmin {
   271  		ctx.Flash.Error(ctx.Tr("form.admin_cannot_delete_self"))
   272  		ctx.Redirect(setting.AppSubURL + "/user/settings/account")
   273  		return
   274  	}
   275  
   276  	if err := user.DeleteUser(ctx, ctx.Doer, false); err != nil {
   277  		switch {
   278  		case models.IsErrUserOwnRepos(err):
   279  			ctx.Flash.Error(ctx.Tr("form.still_own_repo"))
   280  			ctx.Redirect(setting.AppSubURL + "/user/settings/account")
   281  		case models.IsErrUserHasOrgs(err):
   282  			ctx.Flash.Error(ctx.Tr("form.still_has_org"))
   283  			ctx.Redirect(setting.AppSubURL + "/user/settings/account")
   284  		case models.IsErrUserOwnPackages(err):
   285  			ctx.Flash.Error(ctx.Tr("form.still_own_packages"))
   286  			ctx.Redirect(setting.AppSubURL + "/user/settings/account")
   287  		case models.IsErrDeleteLastAdminUser(err):
   288  			ctx.Flash.Error(ctx.Tr("auth.last_admin"))
   289  			ctx.Redirect(setting.AppSubURL + "/user/settings/account")
   290  		default:
   291  			ctx.ServerError("DeleteUser", err)
   292  		}
   293  	} else {
   294  		log.Trace("Account deleted: %s", ctx.Doer.Name)
   295  		ctx.Redirect(setting.AppSubURL + "/")
   296  	}
   297  }
   298  
   299  func loadAccountData(ctx *context.Context) {
   300  	emlist, err := user_model.GetEmailAddresses(ctx, ctx.Doer.ID)
   301  	if err != nil {
   302  		ctx.ServerError("GetEmailAddresses", err)
   303  		return
   304  	}
   305  	type UserEmail struct {
   306  		user_model.EmailAddress
   307  		CanBePrimary bool
   308  	}
   309  	pendingActivation := ctx.Cache.IsExist("MailResendLimit_" + ctx.Doer.LowerName)
   310  	emails := make([]*UserEmail, len(emlist))
   311  	for i, em := range emlist {
   312  		var email UserEmail
   313  		email.EmailAddress = *em
   314  		email.CanBePrimary = em.IsActivated
   315  		emails[i] = &email
   316  	}
   317  	ctx.Data["Emails"] = emails
   318  	ctx.Data["EmailNotificationsPreference"] = ctx.Doer.EmailNotificationsPreference
   319  	ctx.Data["ActivationsPending"] = pendingActivation
   320  	ctx.Data["CanAddEmails"] = !pendingActivation || !setting.Service.RegisterEmailConfirm
   321  	ctx.Data["UserDisabledFeatures"] = user_model.DisabledFeaturesWithLoginType(ctx.Doer)
   322  
   323  	if setting.Service.UserDeleteWithCommentsMaxTime != 0 {
   324  		ctx.Data["UserDeleteWithCommentsMaxTime"] = setting.Service.UserDeleteWithCommentsMaxTime.String()
   325  		ctx.Data["UserDeleteWithComments"] = ctx.Doer.CreatedUnix.AsTime().Add(setting.Service.UserDeleteWithCommentsMaxTime).After(time.Now())
   326  	}
   327  }