code.gitea.io/gitea@v1.22.3/routers/web/auth/auth.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 auth
     6  
     7  import (
     8  	"errors"
     9  	"fmt"
    10  	"html/template"
    11  	"net/http"
    12  	"strings"
    13  
    14  	"code.gitea.io/gitea/models/auth"
    15  	"code.gitea.io/gitea/models/db"
    16  	user_model "code.gitea.io/gitea/models/user"
    17  	"code.gitea.io/gitea/modules/auth/password"
    18  	"code.gitea.io/gitea/modules/base"
    19  	"code.gitea.io/gitea/modules/eventsource"
    20  	"code.gitea.io/gitea/modules/httplib"
    21  	"code.gitea.io/gitea/modules/log"
    22  	"code.gitea.io/gitea/modules/optional"
    23  	"code.gitea.io/gitea/modules/session"
    24  	"code.gitea.io/gitea/modules/setting"
    25  	"code.gitea.io/gitea/modules/timeutil"
    26  	"code.gitea.io/gitea/modules/util"
    27  	"code.gitea.io/gitea/modules/web"
    28  	"code.gitea.io/gitea/modules/web/middleware"
    29  	auth_service "code.gitea.io/gitea/services/auth"
    30  	"code.gitea.io/gitea/services/auth/source/oauth2"
    31  	"code.gitea.io/gitea/services/context"
    32  	"code.gitea.io/gitea/services/externalaccount"
    33  	"code.gitea.io/gitea/services/forms"
    34  	"code.gitea.io/gitea/services/mailer"
    35  	user_service "code.gitea.io/gitea/services/user"
    36  
    37  	"github.com/markbates/goth"
    38  )
    39  
    40  const (
    41  	tplSignIn         base.TplName = "user/auth/signin"          // for sign in page
    42  	tplSignUp         base.TplName = "user/auth/signup"          // for sign up page
    43  	TplActivate       base.TplName = "user/auth/activate"        // for activate user
    44  	TplActivatePrompt base.TplName = "user/auth/activate_prompt" // for showing a message for user activation
    45  )
    46  
    47  // autoSignIn reads cookie and try to auto-login.
    48  func autoSignIn(ctx *context.Context) (bool, error) {
    49  	isSucceed := false
    50  	defer func() {
    51  		if !isSucceed {
    52  			ctx.DeleteSiteCookie(setting.CookieRememberName)
    53  		}
    54  	}()
    55  
    56  	if err := auth.DeleteExpiredAuthTokens(ctx); err != nil {
    57  		log.Error("Failed to delete expired auth tokens: %v", err)
    58  	}
    59  
    60  	t, err := auth_service.CheckAuthToken(ctx, ctx.GetSiteCookie(setting.CookieRememberName))
    61  	if err != nil {
    62  		switch err {
    63  		case auth_service.ErrAuthTokenInvalidFormat, auth_service.ErrAuthTokenExpired:
    64  			return false, nil
    65  		}
    66  		return false, err
    67  	}
    68  	if t == nil {
    69  		return false, nil
    70  	}
    71  
    72  	u, err := user_model.GetUserByID(ctx, t.UserID)
    73  	if err != nil {
    74  		if !user_model.IsErrUserNotExist(err) {
    75  			return false, fmt.Errorf("GetUserByID: %w", err)
    76  		}
    77  		return false, nil
    78  	}
    79  
    80  	isSucceed = true
    81  
    82  	nt, token, err := auth_service.RegenerateAuthToken(ctx, t)
    83  	if err != nil {
    84  		return false, err
    85  	}
    86  
    87  	ctx.SetSiteCookie(setting.CookieRememberName, nt.ID+":"+token, setting.LogInRememberDays*timeutil.Day)
    88  
    89  	if err := updateSession(ctx, nil, map[string]any{
    90  		// Set session IDs
    91  		"uid":   u.ID,
    92  		"uname": u.Name,
    93  	}); err != nil {
    94  		return false, fmt.Errorf("unable to updateSession: %w", err)
    95  	}
    96  
    97  	if err := resetLocale(ctx, u); err != nil {
    98  		return false, err
    99  	}
   100  
   101  	ctx.Csrf.DeleteCookie(ctx)
   102  	return true, nil
   103  }
   104  
   105  func resetLocale(ctx *context.Context, u *user_model.User) error {
   106  	// Language setting of the user overwrites the one previously set
   107  	// If the user does not have a locale set, we save the current one.
   108  	if u.Language == "" {
   109  		opts := &user_service.UpdateOptions{
   110  			Language: optional.Some(ctx.Locale.Language()),
   111  		}
   112  		if err := user_service.UpdateUser(ctx, u, opts); err != nil {
   113  			return err
   114  		}
   115  	}
   116  
   117  	middleware.SetLocaleCookie(ctx.Resp, u.Language, 0)
   118  
   119  	if ctx.Locale.Language() != u.Language {
   120  		ctx.Locale = middleware.Locale(ctx.Resp, ctx.Req)
   121  	}
   122  
   123  	return nil
   124  }
   125  
   126  func RedirectAfterLogin(ctx *context.Context) {
   127  	redirectTo := ctx.FormString("redirect_to")
   128  	if redirectTo == "" {
   129  		redirectTo = ctx.GetSiteCookie("redirect_to")
   130  	}
   131  	middleware.DeleteRedirectToCookie(ctx.Resp)
   132  	nextRedirectTo := setting.AppSubURL + string(setting.LandingPageURL)
   133  	if setting.LandingPageURL == setting.LandingPageLogin {
   134  		nextRedirectTo = setting.AppSubURL + "/" // do not cycle-redirect to the login page
   135  	}
   136  	ctx.RedirectToCurrentSite(redirectTo, nextRedirectTo)
   137  }
   138  
   139  func CheckAutoLogin(ctx *context.Context) bool {
   140  	isSucceed, err := autoSignIn(ctx) // try to auto-login
   141  	if err != nil {
   142  		if errors.Is(err, auth_service.ErrAuthTokenInvalidHash) {
   143  			ctx.Flash.Error(ctx.Tr("auth.remember_me.compromised"), true)
   144  			return false
   145  		}
   146  		ctx.ServerError("autoSignIn", err)
   147  		return true
   148  	}
   149  
   150  	redirectTo := ctx.FormString("redirect_to")
   151  	if len(redirectTo) > 0 {
   152  		middleware.SetRedirectToCookie(ctx.Resp, redirectTo)
   153  	}
   154  
   155  	if isSucceed {
   156  		RedirectAfterLogin(ctx)
   157  		return true
   158  	}
   159  
   160  	return false
   161  }
   162  
   163  // SignIn render sign in page
   164  func SignIn(ctx *context.Context) {
   165  	ctx.Data["Title"] = ctx.Tr("sign_in")
   166  
   167  	if CheckAutoLogin(ctx) {
   168  		return
   169  	}
   170  
   171  	if ctx.IsSigned {
   172  		RedirectAfterLogin(ctx)
   173  		return
   174  	}
   175  
   176  	oauth2Providers, err := oauth2.GetOAuth2Providers(ctx, optional.Some(true))
   177  	if err != nil {
   178  		ctx.ServerError("UserSignIn", err)
   179  		return
   180  	}
   181  	ctx.Data["OAuth2Providers"] = oauth2Providers
   182  	ctx.Data["Title"] = ctx.Tr("sign_in")
   183  	ctx.Data["SignInLink"] = setting.AppSubURL + "/user/login"
   184  	ctx.Data["PageIsSignIn"] = true
   185  	ctx.Data["PageIsLogin"] = true
   186  	ctx.Data["EnableSSPI"] = auth.IsSSPIEnabled(ctx)
   187  
   188  	if setting.Service.EnableCaptcha && setting.Service.RequireCaptchaForLogin {
   189  		context.SetCaptchaData(ctx)
   190  	}
   191  
   192  	ctx.HTML(http.StatusOK, tplSignIn)
   193  }
   194  
   195  // SignInPost response for sign in request
   196  func SignInPost(ctx *context.Context) {
   197  	ctx.Data["Title"] = ctx.Tr("sign_in")
   198  
   199  	oauth2Providers, err := oauth2.GetOAuth2Providers(ctx, optional.Some(true))
   200  	if err != nil {
   201  		ctx.ServerError("UserSignIn", err)
   202  		return
   203  	}
   204  	ctx.Data["OAuth2Providers"] = oauth2Providers
   205  	ctx.Data["Title"] = ctx.Tr("sign_in")
   206  	ctx.Data["SignInLink"] = setting.AppSubURL + "/user/login"
   207  	ctx.Data["PageIsSignIn"] = true
   208  	ctx.Data["PageIsLogin"] = true
   209  	ctx.Data["EnableSSPI"] = auth.IsSSPIEnabled(ctx)
   210  
   211  	if ctx.HasError() {
   212  		ctx.HTML(http.StatusOK, tplSignIn)
   213  		return
   214  	}
   215  
   216  	form := web.GetForm(ctx).(*forms.SignInForm)
   217  
   218  	if setting.Service.EnableCaptcha && setting.Service.RequireCaptchaForLogin {
   219  		context.SetCaptchaData(ctx)
   220  
   221  		context.VerifyCaptcha(ctx, tplSignIn, form)
   222  		if ctx.Written() {
   223  			return
   224  		}
   225  	}
   226  
   227  	u, source, err := auth_service.UserSignIn(ctx, form.UserName, form.Password)
   228  	if err != nil {
   229  		if errors.Is(err, util.ErrNotExist) || errors.Is(err, util.ErrInvalidArgument) {
   230  			ctx.RenderWithErr(ctx.Tr("form.username_password_incorrect"), tplSignIn, &form)
   231  			log.Info("Failed authentication attempt for %s from %s: %v", form.UserName, ctx.RemoteAddr(), err)
   232  		} else if user_model.IsErrEmailAlreadyUsed(err) {
   233  			ctx.RenderWithErr(ctx.Tr("form.email_been_used"), tplSignIn, &form)
   234  			log.Info("Failed authentication attempt for %s from %s: %v", form.UserName, ctx.RemoteAddr(), err)
   235  		} else if user_model.IsErrUserProhibitLogin(err) {
   236  			log.Info("Failed authentication attempt for %s from %s: %v", form.UserName, ctx.RemoteAddr(), err)
   237  			ctx.Data["Title"] = ctx.Tr("auth.prohibit_login")
   238  			ctx.HTML(http.StatusOK, "user/auth/prohibit_login")
   239  		} else if user_model.IsErrUserInactive(err) {
   240  			if setting.Service.RegisterEmailConfirm {
   241  				ctx.Data["Title"] = ctx.Tr("auth.active_your_account")
   242  				ctx.HTML(http.StatusOK, TplActivate)
   243  			} else {
   244  				log.Info("Failed authentication attempt for %s from %s: %v", form.UserName, ctx.RemoteAddr(), err)
   245  				ctx.Data["Title"] = ctx.Tr("auth.prohibit_login")
   246  				ctx.HTML(http.StatusOK, "user/auth/prohibit_login")
   247  			}
   248  		} else {
   249  			ctx.ServerError("UserSignIn", err)
   250  		}
   251  		return
   252  	}
   253  
   254  	// Now handle 2FA:
   255  
   256  	// First of all if the source can skip local two fa we're done
   257  	if skipper, ok := source.Cfg.(auth_service.LocalTwoFASkipper); ok && skipper.IsSkipLocalTwoFA() {
   258  		handleSignIn(ctx, u, form.Remember)
   259  		return
   260  	}
   261  
   262  	// If this user is enrolled in 2FA TOTP, we can't sign the user in just yet.
   263  	// Instead, redirect them to the 2FA authentication page.
   264  	hasTOTPtwofa, err := auth.HasTwoFactorByUID(ctx, u.ID)
   265  	if err != nil {
   266  		ctx.ServerError("UserSignIn", err)
   267  		return
   268  	}
   269  
   270  	// Check if the user has webauthn registration
   271  	hasWebAuthnTwofa, err := auth.HasWebAuthnRegistrationsByUID(ctx, u.ID)
   272  	if err != nil {
   273  		ctx.ServerError("UserSignIn", err)
   274  		return
   275  	}
   276  
   277  	if !hasTOTPtwofa && !hasWebAuthnTwofa {
   278  		// No two factor auth configured we can sign in the user
   279  		handleSignIn(ctx, u, form.Remember)
   280  		return
   281  	}
   282  
   283  	updates := map[string]any{
   284  		// User will need to use 2FA TOTP or WebAuthn, save data
   285  		"twofaUid":      u.ID,
   286  		"twofaRemember": form.Remember,
   287  	}
   288  	if hasTOTPtwofa {
   289  		// User will need to use WebAuthn, save data
   290  		updates["totpEnrolled"] = u.ID
   291  	}
   292  	if err := updateSession(ctx, nil, updates); err != nil {
   293  		ctx.ServerError("UserSignIn: Unable to update session", err)
   294  		return
   295  	}
   296  
   297  	// If we have WebAuthn redirect there first
   298  	if hasWebAuthnTwofa {
   299  		ctx.Redirect(setting.AppSubURL + "/user/webauthn")
   300  		return
   301  	}
   302  
   303  	// Fallback to 2FA
   304  	ctx.Redirect(setting.AppSubURL + "/user/two_factor")
   305  }
   306  
   307  // This handles the final part of the sign-in process of the user.
   308  func handleSignIn(ctx *context.Context, u *user_model.User, remember bool) {
   309  	redirect := handleSignInFull(ctx, u, remember, true)
   310  	if ctx.Written() {
   311  		return
   312  	}
   313  	ctx.Redirect(redirect)
   314  }
   315  
   316  func handleSignInFull(ctx *context.Context, u *user_model.User, remember, obeyRedirect bool) string {
   317  	if remember {
   318  		nt, token, err := auth_service.CreateAuthTokenForUserID(ctx, u.ID)
   319  		if err != nil {
   320  			ctx.ServerError("CreateAuthTokenForUserID", err)
   321  			return setting.AppSubURL + "/"
   322  		}
   323  
   324  		ctx.SetSiteCookie(setting.CookieRememberName, nt.ID+":"+token, setting.LogInRememberDays*timeutil.Day)
   325  	}
   326  
   327  	if err := updateSession(ctx, []string{
   328  		// Delete the openid, 2fa and linkaccount data
   329  		"openid_verified_uri",
   330  		"openid_signin_remember",
   331  		"openid_determined_email",
   332  		"openid_determined_username",
   333  		"twofaUid",
   334  		"twofaRemember",
   335  		"linkAccount",
   336  	}, map[string]any{
   337  		"uid":   u.ID,
   338  		"uname": u.Name,
   339  	}); err != nil {
   340  		ctx.ServerError("RegenerateSession", err)
   341  		return setting.AppSubURL + "/"
   342  	}
   343  
   344  	// Language setting of the user overwrites the one previously set
   345  	// If the user does not have a locale set, we save the current one.
   346  	if u.Language == "" {
   347  		opts := &user_service.UpdateOptions{
   348  			Language: optional.Some(ctx.Locale.Language()),
   349  		}
   350  		if err := user_service.UpdateUser(ctx, u, opts); err != nil {
   351  			ctx.ServerError("UpdateUser Language", fmt.Errorf("Error updating user language [user: %d, locale: %s]", u.ID, ctx.Locale.Language()))
   352  			return setting.AppSubURL + "/"
   353  		}
   354  	}
   355  
   356  	middleware.SetLocaleCookie(ctx.Resp, u.Language, 0)
   357  
   358  	if ctx.Locale.Language() != u.Language {
   359  		ctx.Locale = middleware.Locale(ctx.Resp, ctx.Req)
   360  	}
   361  
   362  	// Clear whatever CSRF cookie has right now, force to generate a new one
   363  	ctx.Csrf.DeleteCookie(ctx)
   364  
   365  	// Register last login
   366  	if err := user_service.UpdateUser(ctx, u, &user_service.UpdateOptions{SetLastLogin: true}); err != nil {
   367  		ctx.ServerError("UpdateUser", err)
   368  		return setting.AppSubURL + "/"
   369  	}
   370  
   371  	if redirectTo := ctx.GetSiteCookie("redirect_to"); redirectTo != "" && httplib.IsCurrentGiteaSiteURL(ctx, redirectTo) {
   372  		middleware.DeleteRedirectToCookie(ctx.Resp)
   373  		if obeyRedirect {
   374  			ctx.RedirectToCurrentSite(redirectTo)
   375  		}
   376  		return redirectTo
   377  	}
   378  
   379  	if obeyRedirect {
   380  		ctx.Redirect(setting.AppSubURL + "/")
   381  	}
   382  	return setting.AppSubURL + "/"
   383  }
   384  
   385  // extractUserNameFromOAuth2 tries to extract a normalized username from the given OAuth2 user.
   386  // It returns ("", nil) if the required field doesn't exist.
   387  func extractUserNameFromOAuth2(gothUser *goth.User) (string, error) {
   388  	switch setting.OAuth2Client.Username {
   389  	case setting.OAuth2UsernameEmail:
   390  		return user_model.NormalizeUserName(gothUser.Email)
   391  	case setting.OAuth2UsernamePreferredUsername:
   392  		if preferredUsername, ok := gothUser.RawData["preferred_username"].(string); ok {
   393  			return user_model.NormalizeUserName(preferredUsername)
   394  		}
   395  		return "", nil
   396  	case setting.OAuth2UsernameNickname:
   397  		return user_model.NormalizeUserName(gothUser.NickName)
   398  	default: // OAuth2UsernameUserid
   399  		return gothUser.UserID, nil
   400  	}
   401  }
   402  
   403  // HandleSignOut resets the session and sets the cookies
   404  func HandleSignOut(ctx *context.Context) {
   405  	_ = ctx.Session.Flush()
   406  	_ = ctx.Session.Destroy(ctx.Resp, ctx.Req)
   407  	ctx.DeleteSiteCookie(setting.CookieRememberName)
   408  	ctx.Csrf.DeleteCookie(ctx)
   409  	middleware.DeleteRedirectToCookie(ctx.Resp)
   410  }
   411  
   412  // SignOut sign out from login status
   413  func SignOut(ctx *context.Context) {
   414  	if ctx.Doer != nil {
   415  		eventsource.GetManager().SendMessageBlocking(ctx.Doer.ID, &eventsource.Event{
   416  			Name: "logout",
   417  			Data: ctx.Session.ID(),
   418  		})
   419  	}
   420  	HandleSignOut(ctx)
   421  	ctx.JSONRedirect(setting.AppSubURL + "/")
   422  }
   423  
   424  // SignUp render the register page
   425  func SignUp(ctx *context.Context) {
   426  	ctx.Data["Title"] = ctx.Tr("sign_up")
   427  
   428  	ctx.Data["SignUpLink"] = setting.AppSubURL + "/user/sign_up"
   429  
   430  	oauth2Providers, err := oauth2.GetOAuth2Providers(ctx, optional.Some(true))
   431  	if err != nil {
   432  		ctx.ServerError("UserSignUp", err)
   433  		return
   434  	}
   435  
   436  	ctx.Data["OAuth2Providers"] = oauth2Providers
   437  	context.SetCaptchaData(ctx)
   438  
   439  	ctx.Data["PageIsSignUp"] = true
   440  
   441  	// Show Disabled Registration message if DisableRegistration or AllowOnlyExternalRegistration options are true
   442  	ctx.Data["DisableRegistration"] = setting.Service.DisableRegistration || setting.Service.AllowOnlyExternalRegistration
   443  
   444  	redirectTo := ctx.FormString("redirect_to")
   445  	if len(redirectTo) > 0 {
   446  		middleware.SetRedirectToCookie(ctx.Resp, redirectTo)
   447  	}
   448  
   449  	ctx.HTML(http.StatusOK, tplSignUp)
   450  }
   451  
   452  // SignUpPost response for sign up information submission
   453  func SignUpPost(ctx *context.Context) {
   454  	form := web.GetForm(ctx).(*forms.RegisterForm)
   455  	ctx.Data["Title"] = ctx.Tr("sign_up")
   456  
   457  	ctx.Data["SignUpLink"] = setting.AppSubURL + "/user/sign_up"
   458  
   459  	oauth2Providers, err := oauth2.GetOAuth2Providers(ctx, optional.Some(true))
   460  	if err != nil {
   461  		ctx.ServerError("UserSignUp", err)
   462  		return
   463  	}
   464  
   465  	ctx.Data["OAuth2Providers"] = oauth2Providers
   466  	context.SetCaptchaData(ctx)
   467  
   468  	ctx.Data["PageIsSignUp"] = true
   469  
   470  	// Permission denied if DisableRegistration or AllowOnlyExternalRegistration options are true
   471  	if setting.Service.DisableRegistration || setting.Service.AllowOnlyExternalRegistration {
   472  		ctx.Error(http.StatusForbidden)
   473  		return
   474  	}
   475  
   476  	if ctx.HasError() {
   477  		ctx.HTML(http.StatusOK, tplSignUp)
   478  		return
   479  	}
   480  
   481  	context.VerifyCaptcha(ctx, tplSignUp, form)
   482  	if ctx.Written() {
   483  		return
   484  	}
   485  
   486  	if !form.IsEmailDomainAllowed() {
   487  		ctx.RenderWithErr(ctx.Tr("auth.email_domain_blacklisted"), tplSignUp, &form)
   488  		return
   489  	}
   490  
   491  	if form.Password != form.Retype {
   492  		ctx.Data["Err_Password"] = true
   493  		ctx.RenderWithErr(ctx.Tr("form.password_not_match"), tplSignUp, &form)
   494  		return
   495  	}
   496  	if len(form.Password) < setting.MinPasswordLength {
   497  		ctx.Data["Err_Password"] = true
   498  		ctx.RenderWithErr(ctx.Tr("auth.password_too_short", setting.MinPasswordLength), tplSignUp, &form)
   499  		return
   500  	}
   501  	if !password.IsComplexEnough(form.Password) {
   502  		ctx.Data["Err_Password"] = true
   503  		ctx.RenderWithErr(password.BuildComplexityError(ctx.Locale), tplSignUp, &form)
   504  		return
   505  	}
   506  	if err := password.IsPwned(ctx, form.Password); err != nil {
   507  		errMsg := ctx.Tr("auth.password_pwned")
   508  		if password.IsErrIsPwnedRequest(err) {
   509  			log.Error(err.Error())
   510  			errMsg = ctx.Tr("auth.password_pwned_err")
   511  		}
   512  		ctx.Data["Err_Password"] = true
   513  		ctx.RenderWithErr(errMsg, tplSignUp, &form)
   514  		return
   515  	}
   516  
   517  	u := &user_model.User{
   518  		Name:   form.UserName,
   519  		Email:  form.Email,
   520  		Passwd: form.Password,
   521  	}
   522  
   523  	if !createAndHandleCreatedUser(ctx, tplSignUp, form, u, nil, nil, false) {
   524  		// error already handled
   525  		return
   526  	}
   527  
   528  	ctx.Flash.Success(ctx.Tr("auth.sign_up_successful"))
   529  	handleSignIn(ctx, u, false)
   530  }
   531  
   532  // createAndHandleCreatedUser calls createUserInContext and
   533  // then handleUserCreated.
   534  func createAndHandleCreatedUser(ctx *context.Context, tpl base.TplName, form any, u *user_model.User, overwrites *user_model.CreateUserOverwriteOptions, gothUser *goth.User, allowLink bool) bool {
   535  	if !createUserInContext(ctx, tpl, form, u, overwrites, gothUser, allowLink) {
   536  		return false
   537  	}
   538  	return handleUserCreated(ctx, u, gothUser)
   539  }
   540  
   541  // createUserInContext creates a user and handles errors within a given context.
   542  // Optionally a template can be specified.
   543  func createUserInContext(ctx *context.Context, tpl base.TplName, form any, u *user_model.User, overwrites *user_model.CreateUserOverwriteOptions, gothUser *goth.User, allowLink bool) (ok bool) {
   544  	if err := user_model.CreateUser(ctx, u, overwrites); err != nil {
   545  		if allowLink && (user_model.IsErrUserAlreadyExist(err) || user_model.IsErrEmailAlreadyUsed(err)) {
   546  			if setting.OAuth2Client.AccountLinking == setting.OAuth2AccountLinkingAuto {
   547  				var user *user_model.User
   548  				user = &user_model.User{Name: u.Name}
   549  				hasUser, err := user_model.GetUser(ctx, user)
   550  				if !hasUser || err != nil {
   551  					user = &user_model.User{Email: u.Email}
   552  					hasUser, err = user_model.GetUser(ctx, user)
   553  					if !hasUser || err != nil {
   554  						ctx.ServerError("UserLinkAccount", err)
   555  						return false
   556  					}
   557  				}
   558  
   559  				// TODO: probably we should respect 'remember' user's choice...
   560  				linkAccount(ctx, user, *gothUser, true)
   561  				return false // user is already created here, all redirects are handled
   562  			} else if setting.OAuth2Client.AccountLinking == setting.OAuth2AccountLinkingLogin {
   563  				showLinkingLogin(ctx, *gothUser)
   564  				return false // user will be created only after linking login
   565  			}
   566  		}
   567  
   568  		// handle error without template
   569  		if len(tpl) == 0 {
   570  			ctx.ServerError("CreateUser", err)
   571  			return false
   572  		}
   573  
   574  		// handle error with template
   575  		switch {
   576  		case user_model.IsErrUserAlreadyExist(err):
   577  			ctx.Data["Err_UserName"] = true
   578  			ctx.RenderWithErr(ctx.Tr("form.username_been_taken"), tpl, form)
   579  		case user_model.IsErrEmailAlreadyUsed(err):
   580  			ctx.Data["Err_Email"] = true
   581  			ctx.RenderWithErr(ctx.Tr("form.email_been_used"), tpl, form)
   582  		case user_model.IsErrEmailCharIsNotSupported(err):
   583  			ctx.Data["Err_Email"] = true
   584  			ctx.RenderWithErr(ctx.Tr("form.email_invalid"), tpl, form)
   585  		case user_model.IsErrEmailInvalid(err):
   586  			ctx.Data["Err_Email"] = true
   587  			ctx.RenderWithErr(ctx.Tr("form.email_invalid"), tpl, form)
   588  		case db.IsErrNameReserved(err):
   589  			ctx.Data["Err_UserName"] = true
   590  			ctx.RenderWithErr(ctx.Tr("user.form.name_reserved", err.(db.ErrNameReserved).Name), tpl, form)
   591  		case db.IsErrNamePatternNotAllowed(err):
   592  			ctx.Data["Err_UserName"] = true
   593  			ctx.RenderWithErr(ctx.Tr("user.form.name_pattern_not_allowed", err.(db.ErrNamePatternNotAllowed).Pattern), tpl, form)
   594  		case db.IsErrNameCharsNotAllowed(err):
   595  			ctx.Data["Err_UserName"] = true
   596  			ctx.RenderWithErr(ctx.Tr("user.form.name_chars_not_allowed", err.(db.ErrNameCharsNotAllowed).Name), tpl, form)
   597  		default:
   598  			ctx.ServerError("CreateUser", err)
   599  		}
   600  		return false
   601  	}
   602  	log.Trace("Account created: %s", u.Name)
   603  	return true
   604  }
   605  
   606  // handleUserCreated does additional steps after a new user is created.
   607  // It auto-sets admin for the only user, updates the optional external user and
   608  // sends a confirmation email if required.
   609  func handleUserCreated(ctx *context.Context, u *user_model.User, gothUser *goth.User) (ok bool) {
   610  	// Auto-set admin for the only user.
   611  	if user_model.CountUsers(ctx, nil) == 1 {
   612  		opts := &user_service.UpdateOptions{
   613  			IsActive:     optional.Some(true),
   614  			IsAdmin:      optional.Some(true),
   615  			SetLastLogin: true,
   616  		}
   617  		if err := user_service.UpdateUser(ctx, u, opts); err != nil {
   618  			ctx.ServerError("UpdateUser", err)
   619  			return false
   620  		}
   621  	}
   622  
   623  	// update external user information
   624  	if gothUser != nil {
   625  		if err := externalaccount.UpdateExternalUser(ctx, u, *gothUser); err != nil {
   626  			if !errors.Is(err, util.ErrNotExist) {
   627  				log.Error("UpdateExternalUser failed: %v", err)
   628  			}
   629  		}
   630  	}
   631  
   632  	// for active user or the first (admin) user, we don't need to send confirmation email
   633  	if u.IsActive || u.ID == 1 {
   634  		return true
   635  	}
   636  
   637  	if setting.Service.RegisterManualConfirm {
   638  		renderActivationPromptMessage(ctx, ctx.Locale.Tr("auth.manual_activation_only"))
   639  		return false
   640  	}
   641  
   642  	sendActivateEmail(ctx, u)
   643  	return false
   644  }
   645  
   646  func renderActivationPromptMessage(ctx *context.Context, msg template.HTML) {
   647  	ctx.Data["ActivationPromptMessage"] = msg
   648  	ctx.HTML(http.StatusOK, TplActivatePrompt)
   649  }
   650  
   651  func sendActivateEmail(ctx *context.Context, u *user_model.User) {
   652  	if ctx.Cache.IsExist("MailResendLimit_" + u.LowerName) {
   653  		renderActivationPromptMessage(ctx, ctx.Locale.Tr("auth.resent_limit_prompt"))
   654  		return
   655  	}
   656  
   657  	if err := ctx.Cache.Put("MailResendLimit_"+u.LowerName, u.LowerName, 180); err != nil {
   658  		log.Error("Set cache(MailResendLimit) fail: %v", err)
   659  		renderActivationPromptMessage(ctx, ctx.Locale.Tr("auth.resent_limit_prompt"))
   660  		return
   661  	}
   662  
   663  	mailer.SendActivateAccountMail(ctx.Locale, u)
   664  
   665  	activeCodeLives := timeutil.MinutesToFriendly(setting.Service.ActiveCodeLives, ctx.Locale)
   666  	msgHTML := ctx.Locale.Tr("auth.confirmation_mail_sent_prompt_ex", u.Email, activeCodeLives)
   667  	renderActivationPromptMessage(ctx, msgHTML)
   668  }
   669  
   670  func renderActivationVerifyPassword(ctx *context.Context, code string) {
   671  	ctx.Data["ActivationCode"] = code
   672  	ctx.Data["NeedVerifyLocalPassword"] = true
   673  	ctx.HTML(http.StatusOK, TplActivate)
   674  }
   675  
   676  func renderActivationChangeEmail(ctx *context.Context) {
   677  	ctx.HTML(http.StatusOK, TplActivate)
   678  }
   679  
   680  // Activate render activate user page
   681  func Activate(ctx *context.Context) {
   682  	code := ctx.FormString("code")
   683  
   684  	if code == "" {
   685  		if ctx.Doer == nil {
   686  			ctx.Redirect(setting.AppSubURL + "/user/login")
   687  			return
   688  		} else if ctx.Doer.IsActive {
   689  			ctx.Redirect(setting.AppSubURL + "/")
   690  			return
   691  		}
   692  
   693  		if setting.MailService == nil || !setting.Service.RegisterEmailConfirm {
   694  			renderActivationPromptMessage(ctx, ctx.Tr("auth.disable_register_mail"))
   695  			return
   696  		}
   697  
   698  		// Resend confirmation email. FIXME: ideally this should be in a POST request
   699  		sendActivateEmail(ctx, ctx.Doer)
   700  		return
   701  	}
   702  
   703  	// TODO: ctx.Doer/ctx.Data["SignedUser"] could be nil or not the same user as the one being activated
   704  	user := user_model.VerifyUserActiveCode(ctx, code)
   705  	if user == nil { // if code is wrong
   706  		renderActivationPromptMessage(ctx, ctx.Locale.Tr("auth.invalid_code"))
   707  		return
   708  	}
   709  
   710  	// if account is local account, verify password
   711  	if user.LoginSource == 0 {
   712  		renderActivationVerifyPassword(ctx, code)
   713  		return
   714  	}
   715  
   716  	handleAccountActivation(ctx, user)
   717  }
   718  
   719  // ActivatePost handles account activation with password check
   720  func ActivatePost(ctx *context.Context) {
   721  	code := ctx.FormString("code")
   722  	if ctx.Doer != nil && ctx.Doer.IsActive {
   723  		ctx.Redirect(setting.AppSubURL + "/user/activate") // it will redirect again to the correct page
   724  		return
   725  	}
   726  
   727  	if code == "" {
   728  		newEmail := strings.TrimSpace(ctx.FormString("change_email"))
   729  		if ctx.Doer != nil && newEmail != "" && !strings.EqualFold(ctx.Doer.Email, newEmail) {
   730  			if user_model.ValidateEmail(newEmail) != nil {
   731  				ctx.Flash.Error(ctx.Locale.Tr("form.email_invalid"), true)
   732  				renderActivationChangeEmail(ctx)
   733  				return
   734  			}
   735  			err := user_model.ChangeInactivePrimaryEmail(ctx, ctx.Doer.ID, ctx.Doer.Email, newEmail)
   736  			if err != nil {
   737  				ctx.Flash.Error(ctx.Locale.Tr("admin.emails.not_updated", newEmail), true)
   738  				renderActivationChangeEmail(ctx)
   739  				return
   740  			}
   741  			ctx.Doer.Email = newEmail
   742  		}
   743  		// FIXME: at the moment, GET request handles the "send confirmation email" action. But the old code does this redirect and then send a confirmation email.
   744  		ctx.Redirect(setting.AppSubURL + "/user/activate")
   745  		return
   746  	}
   747  
   748  	// TODO: ctx.Doer/ctx.Data["SignedUser"] could be nil or not the same user as the one being activated
   749  	user := user_model.VerifyUserActiveCode(ctx, code)
   750  	if user == nil { // if code is wrong
   751  		renderActivationPromptMessage(ctx, ctx.Locale.Tr("auth.invalid_code"))
   752  		return
   753  	}
   754  
   755  	// if account is local account, verify password
   756  	if user.LoginSource == 0 {
   757  		password := ctx.FormString("password")
   758  		if password == "" {
   759  			renderActivationVerifyPassword(ctx, code)
   760  			return
   761  		}
   762  		if !user.ValidatePassword(password) {
   763  			ctx.Flash.Error(ctx.Locale.Tr("auth.invalid_password"), true)
   764  			renderActivationVerifyPassword(ctx, code)
   765  			return
   766  		}
   767  	}
   768  
   769  	handleAccountActivation(ctx, user)
   770  }
   771  
   772  func handleAccountActivation(ctx *context.Context, user *user_model.User) {
   773  	user.IsActive = true
   774  	var err error
   775  	if user.Rands, err = user_model.GetUserSalt(); err != nil {
   776  		ctx.ServerError("UpdateUser", err)
   777  		return
   778  	}
   779  	if err := user_model.UpdateUserCols(ctx, user, "is_active", "rands"); err != nil {
   780  		if user_model.IsErrUserNotExist(err) {
   781  			ctx.NotFound("UpdateUserCols", err)
   782  		} else {
   783  			ctx.ServerError("UpdateUser", err)
   784  		}
   785  		return
   786  	}
   787  
   788  	if err := user_model.ActivateUserEmail(ctx, user.ID, user.Email, true); err != nil {
   789  		log.Error("Unable to activate email for user: %-v with email: %s: %v", user, user.Email, err)
   790  		ctx.ServerError("ActivateUserEmail", err)
   791  		return
   792  	}
   793  
   794  	log.Trace("User activated: %s", user.Name)
   795  
   796  	if err := updateSession(ctx, nil, map[string]any{
   797  		"uid":   user.ID,
   798  		"uname": user.Name,
   799  	}); err != nil {
   800  		log.Error("Unable to regenerate session for user: %-v with email: %s: %v", user, user.Email, err)
   801  		ctx.ServerError("ActivateUserEmail", err)
   802  		return
   803  	}
   804  
   805  	if err := resetLocale(ctx, user); err != nil {
   806  		ctx.ServerError("resetLocale", err)
   807  		return
   808  	}
   809  
   810  	if err := user_service.UpdateUser(ctx, user, &user_service.UpdateOptions{SetLastLogin: true}); err != nil {
   811  		ctx.ServerError("UpdateUser", err)
   812  		return
   813  	}
   814  
   815  	ctx.Flash.Success(ctx.Tr("auth.account_activated"))
   816  	if redirectTo := ctx.GetSiteCookie("redirect_to"); len(redirectTo) > 0 {
   817  		middleware.DeleteRedirectToCookie(ctx.Resp)
   818  		ctx.RedirectToCurrentSite(redirectTo)
   819  		return
   820  	}
   821  
   822  	ctx.Redirect(setting.AppSubURL + "/")
   823  }
   824  
   825  // ActivateEmail render the activate email page
   826  func ActivateEmail(ctx *context.Context) {
   827  	code := ctx.FormString("code")
   828  	emailStr := ctx.FormString("email")
   829  
   830  	// Verify code.
   831  	if email := user_model.VerifyActiveEmailCode(ctx, code, emailStr); email != nil {
   832  		if err := user_model.ActivateEmail(ctx, email); err != nil {
   833  			ctx.ServerError("ActivateEmail", err)
   834  			return
   835  		}
   836  
   837  		log.Trace("Email activated: %s", email.Email)
   838  		ctx.Flash.Success(ctx.Tr("settings.add_email_success"))
   839  
   840  		if u, err := user_model.GetUserByID(ctx, email.UID); err != nil {
   841  			log.Warn("GetUserByID: %d", email.UID)
   842  		} else {
   843  			// Allow user to validate more emails
   844  			_ = ctx.Cache.Delete("MailResendLimit_" + u.LowerName)
   845  		}
   846  	}
   847  
   848  	// FIXME: e-mail verification does not require the user to be logged in,
   849  	// so this could be redirecting to the login page.
   850  	// Should users be logged in automatically here? (consider 2FA requirements, etc.)
   851  	ctx.Redirect(setting.AppSubURL + "/user/settings/account")
   852  }
   853  
   854  func updateSession(ctx *context.Context, deletes []string, updates map[string]any) error {
   855  	if _, err := session.RegenerateSession(ctx.Resp, ctx.Req); err != nil {
   856  		return fmt.Errorf("regenerate session: %w", err)
   857  	}
   858  	sess := ctx.Session
   859  	sessID := sess.ID()
   860  	for _, k := range deletes {
   861  		if err := sess.Delete(k); err != nil {
   862  			return fmt.Errorf("delete %v in session[%s]: %w", k, sessID, err)
   863  		}
   864  	}
   865  	for k, v := range updates {
   866  		if err := sess.Set(k, v); err != nil {
   867  			return fmt.Errorf("set %v in session[%s]: %w", k, sessID, err)
   868  		}
   869  	}
   870  	if err := sess.Release(); err != nil {
   871  		return fmt.Errorf("store session[%s]: %w", sessID, err)
   872  	}
   873  	return nil
   874  }