2022-01-02 21:12:35 +08:00
// Copyright 2014 The Gogs Authors. All rights reserved.
// Copyright 2018 The Gitea Authors. All rights reserved.
2022-11-27 13:20:29 -05:00
// SPDX-License-Identifier: MIT
2022-01-02 21:12:35 +08:00
package auth
import (
2022-10-19 21:07:21 +02:00
"errors"
2022-01-02 21:12:35 +08:00
"fmt"
2024-02-26 05:55:00 +08:00
"html/template"
2022-01-02 21:12:35 +08:00
"net/http"
2026-01-03 11:43:04 +08:00
"net/url"
2022-01-02 21:12:35 +08:00
"strings"
"code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/db"
user_model "code.gitea.io/gitea/models/user"
2023-02-19 07:35:20 +00:00
"code.gitea.io/gitea/modules/auth/password"
2022-01-02 21:12:35 +08:00
"code.gitea.io/gitea/modules/eventsource"
2024-03-22 04:32:40 +08:00
"code.gitea.io/gitea/modules/httplib"
2022-01-02 21:12:35 +08:00
"code.gitea.io/gitea/modules/log"
2024-02-04 14:29:09 +01:00
"code.gitea.io/gitea/modules/optional"
2022-01-02 21:12:35 +08:00
"code.gitea.io/gitea/modules/session"
"code.gitea.io/gitea/modules/setting"
2024-12-22 23:33:19 +08:00
"code.gitea.io/gitea/modules/templates"
2022-01-02 21:12:35 +08:00
"code.gitea.io/gitea/modules/timeutil"
2022-10-19 21:07:21 +02:00
"code.gitea.io/gitea/modules/util"
2022-01-02 21:12:35 +08:00
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/modules/web/middleware"
auth_service "code.gitea.io/gitea/services/auth"
"code.gitea.io/gitea/services/auth/source/oauth2"
2024-02-27 15:12:22 +08:00
"code.gitea.io/gitea/services/context"
2022-01-02 21:12:35 +08:00
"code.gitea.io/gitea/services/externalaccount"
"code.gitea.io/gitea/services/forms"
"code.gitea.io/gitea/services/mailer"
2024-02-04 14:29:09 +01:00
user_service "code.gitea.io/gitea/services/user"
2022-01-02 21:12:35 +08:00
"github.com/markbates/goth"
)
const (
2024-12-22 23:33:19 +08:00
tplSignIn templates . TplName = "user/auth/signin" // for sign in page
tplSignUp templates . TplName = "user/auth/signup" // for sign up page
TplActivate templates . TplName = "user/auth/activate" // for activate user
TplActivatePrompt templates . TplName = "user/auth/activate_prompt" // for showing a message for user activation
2022-01-02 21:12:35 +08:00
)
2026-03-08 23:57:37 +08:00
type CommonAuthOptions struct {
EnableCaptcha bool
}
func prepareCommonAuthPageData ( ctx * context . Context , opt CommonAuthOptions ) {
ctx . Data [ "EnablePasswordSignInForm" ] = setting . Service . EnablePasswordSignInForm
ctx . Data [ "EnablePasskeyAuth" ] = setting . Service . EnablePasskeyAuth
// for OpenID Connect
ctx . Data [ "EnableOpenIDSignUp" ] = setting . Service . EnableOpenIDSignUp
ctx . Data [ "AllowOnlyInternalRegistration" ] = setting . Service . AllowOnlyInternalRegistration
if opt . EnableCaptcha {
ctx . Data [ "EnableCaptcha" ] = true
ctx . Data [ "RecaptchaAPIScriptURL" ] = strings . TrimSuffix ( setting . Service . RecaptchaURL , "/" ) + "/api.js"
ctx . Data [ "CaptchaType" ] = setting . Service . CaptchaType
ctx . Data [ "RecaptchaSitekey" ] = setting . Service . RecaptchaSitekey
ctx . Data [ "HcaptchaSitekey" ] = setting . Service . HcaptchaSitekey
ctx . Data [ "McaptchaSitekey" ] = setting . Service . McaptchaSitekey
2026-05-02 09:20:52 -07:00
ctx . Data [ "McaptchaURL" ] = strings . TrimSuffix ( setting . Service . McaptchaURL , "/" )
2026-03-08 23:57:37 +08:00
ctx . Data [ "CfTurnstileSitekey" ] = setting . Service . CfTurnstileSitekey
if setting . Service . CaptchaType == setting . ImageCaptcha {
ctx . Data [ "Captcha" ] = context . GetImageCaptcha ( )
}
}
}
2023-10-14 02:56:41 +02:00
// autoSignIn reads cookie and try to auto-login.
func autoSignIn ( ctx * context . Context ) ( bool , error ) {
2022-01-02 21:12:35 +08:00
isSucceed := false
defer func ( ) {
if ! isSucceed {
2023-04-14 03:45:33 +08:00
ctx . DeleteSiteCookie ( setting . CookieRememberName )
2022-01-02 21:12:35 +08:00
}
} ( )
2023-10-14 02:56:41 +02:00
if err := auth . DeleteExpiredAuthTokens ( ctx ) ; err != nil {
log . Error ( "Failed to delete expired auth tokens: %v" , err )
}
t , err := auth_service . CheckAuthToken ( ctx , ctx . GetSiteCookie ( setting . CookieRememberName ) )
2022-01-02 21:12:35 +08:00
if err != nil {
2023-10-14 02:56:41 +02:00
switch err {
case auth_service . ErrAuthTokenInvalidFormat , auth_service . ErrAuthTokenExpired :
return false , nil
2022-01-02 21:12:35 +08:00
}
2023-10-14 02:56:41 +02:00
return false , err
}
if t == nil {
2022-01-02 21:12:35 +08:00
return false , nil
}
2023-10-14 02:56:41 +02:00
u , err := user_model . GetUserByID ( ctx , t . UserID )
if err != nil {
if ! user_model . IsErrUserNotExist ( err ) {
return false , fmt . Errorf ( "GetUserByID: %w" , err )
}
2022-01-02 21:12:35 +08:00
return false , nil
}
2025-04-29 06:31:59 +08:00
userHasTwoFactorAuth , err := auth . HasTwoFactorOrWebAuthn ( ctx , u . ID )
if err != nil {
return false , fmt . Errorf ( "HasTwoFactorOrWebAuthn: %w" , err )
}
2022-01-02 21:12:35 +08:00
isSucceed = true
2023-10-14 02:56:41 +02:00
nt , token , err := auth_service . RegenerateAuthToken ( ctx , t )
if err != nil {
return false , err
}
ctx . SetSiteCookie ( setting . CookieRememberName , nt . ID + ":" + token , setting . LogInRememberDays * timeutil . Day )
2023-07-04 20:36:08 +02:00
if err := updateSession ( ctx , nil , map [ string ] any {
2025-04-29 06:31:59 +08:00
session . KeyUID : u . ID ,
session . KeyUname : u . Name ,
session . KeyUserHasTwoFactorAuth : userHasTwoFactorAuth ,
2022-11-10 19:43:06 +08:00
} ) ; err != nil {
return false , fmt . Errorf ( "unable to updateSession: %w" , err )
2022-01-02 21:12:35 +08:00
}
if err := resetLocale ( ctx , u ) ; err != nil {
return false , err
}
return true , nil
}
func resetLocale ( ctx * context . Context , u * user_model . User ) error {
// Language setting of the user overwrites the one previously set
// If the user does not have a locale set, we save the current one.
2024-02-04 14:29:09 +01:00
if u . Language == "" {
opts := & user_service . UpdateOptions {
Language : optional . Some ( ctx . Locale . Language ( ) ) ,
}
if err := user_service . UpdateUser ( ctx , u , opts ) ; err != nil {
2022-01-02 21:12:35 +08:00
return err
}
}
middleware . SetLocaleCookie ( ctx . Resp , u . Language , 0 )
if ctx . Locale . Language ( ) != u . Language {
ctx . Locale = middleware . Locale ( ctx . Resp , ctx . Req )
}
return nil
}
2026-01-03 11:43:04 +08:00
func rememberAuthRedirectLink ( ctx * context . Context ) {
2024-03-05 10:12:03 +08:00
redirectTo := ctx . FormString ( "redirect_to" )
if redirectTo == "" {
2026-01-03 11:43:04 +08:00
if ref , err := url . Parse ( ctx . Req . Referer ( ) ) ; err == nil && httplib . IsCurrentGiteaSiteURL ( ctx , ctx . Req . Referer ( ) ) {
// the request paths starting with "/user/" are either:
// * auth related pages: don't redirect back to them
// * user settings pages: they have "require sign-in" protection already, no "referer redirect" would happen
skipRefererRedirect := strings . HasPrefix ( ref . Path , setting . AppSubURL + "/user/" )
if ! skipRefererRedirect {
redirectTo = ref . RequestURI ( )
}
}
2024-03-05 10:12:03 +08:00
}
2026-01-03 11:43:04 +08:00
if redirectTo != "" {
middleware . SetRedirectToCookie ( ctx . Resp , redirectTo )
}
}
func consumeAuthRedirectLink ( ctx * context . Context ) string {
redirects := [ ] string { ctx . FormString ( "redirect_to" ) , middleware . GetRedirectToCookie ( ctx . Req ) }
2024-03-05 10:12:03 +08:00
middleware . DeleteRedirectToCookie ( ctx . Resp )
if setting . LandingPageURL == setting . LandingPageLogin {
2026-01-03 11:43:04 +08:00
redirects = append ( redirects , setting . AppSubURL + "/" ) // do not cycle-redirect to the login page
} else {
redirects = append ( redirects , setting . AppSubURL + string ( setting . LandingPageURL ) )
}
for _ , link := range redirects {
if link != "" && httplib . IsCurrentGiteaSiteURL ( ctx , link ) {
return link
}
2024-03-05 10:12:03 +08:00
}
2026-01-03 11:43:04 +08:00
return setting . AppSubURL + "/"
2024-03-05 10:12:03 +08:00
}
2026-01-03 11:43:04 +08:00
func redirectAfterAuth ( ctx * context . Context ) {
2026-02-26 16:16:11 +01:00
if setting . Config ( ) . Instance . MaintenanceMode . Value ( ctx ) . IsActive ( ) {
// in maintenance mode, redirect to admin dashboard, it is the only accessible page
ctx . Redirect ( setting . AppSubURL + "/-/admin" )
return
}
2026-01-03 11:43:04 +08:00
ctx . RedirectToCurrentSite ( consumeAuthRedirectLink ( ctx ) )
}
func performAutoLogin ( ctx * context . Context ) bool {
rememberAuthRedirectLink ( ctx )
2024-03-05 10:12:03 +08:00
isSucceed , err := autoSignIn ( ctx ) // try to auto-login
2022-01-02 21:12:35 +08:00
if err != nil {
2023-10-14 02:56:41 +02:00
if errors . Is ( err , auth_service . ErrAuthTokenInvalidHash ) {
ctx . Flash . Error ( ctx . Tr ( "auth.remember_me.compromised" ) , true )
return false
}
ctx . ServerError ( "autoSignIn" , err )
2022-01-02 21:12:35 +08:00
return true
}
if isSucceed {
2026-01-03 11:43:04 +08:00
redirectAfterAuth ( ctx )
2022-01-02 21:12:35 +08:00
return true
}
return false
}
2026-04-01 18:20:57 +05:30
func performAutoLoginOAuth2 ( ctx * context . Context , data * preparedSignInData ) bool {
// If only 1 OAuth provider is present and other login methods are disabled, redirect to the OAuth provider.
onlySingleOAuth2 := len ( data . oauth2Providers ) == 1 &&
! setting . Service . EnablePasswordSignInForm &&
! setting . Service . EnableOpenIDSignIn &&
! setting . Service . EnablePasskeyAuth &&
! data . enableSSPI
if ! onlySingleOAuth2 {
return false
}
2026-04-22 01:11:19 +08:00
skipToOAuthURL := setting . AppSubURL + "/user/oauth2/" + url . PathEscape ( data . oauth2Providers [ 0 ] . DisplayName ( ) )
2026-04-01 18:20:57 +05:30
if redirectTo := ctx . FormString ( "redirect_to" ) ; redirectTo != "" {
skipToOAuthURL += "?redirect_to=" + url . QueryEscape ( redirectTo )
}
ctx . Redirect ( skipToOAuthURL )
return true
}
type preparedSignInData struct {
oauth2Providers [ ] oauth2 . Provider
enableSSPI bool
}
func prepareSignInPageData ( ctx * context . Context ) ( ret preparedSignInData ) {
var err error
ret . enableSSPI = auth . IsSSPIEnabled ( ctx )
ret . oauth2Providers , err = oauth2 . GetOAuth2Providers ( ctx , optional . Some ( true ) )
if err != nil {
log . Error ( "Failed to get OAuth2 providers: %v" , err )
}
2022-01-02 21:12:35 +08:00
ctx . Data [ "Title" ] = ctx . Tr ( "sign_in" )
2026-04-01 18:20:57 +05:30
ctx . Data [ "OAuth2Providers" ] = ret . oauth2Providers
2022-01-02 21:12:35 +08:00
ctx . Data [ "Title" ] = ctx . Tr ( "sign_in" )
ctx . Data [ "SignInLink" ] = setting . AppSubURL + "/user/login"
ctx . Data [ "PageIsSignIn" ] = true
ctx . Data [ "PageIsLogin" ] = true
2026-04-01 18:20:57 +05:30
ctx . Data [ "EnableSSPI" ] = ret . enableSSPI
2022-01-02 21:12:35 +08:00
2026-03-08 23:57:37 +08:00
prepareCommonAuthPageData ( ctx , CommonAuthOptions {
EnableCaptcha : setting . Service . EnableCaptcha && setting . Service . RequireCaptchaForLogin ,
} )
2026-04-01 18:20:57 +05:30
return ret
2024-12-02 02:03:15 +08:00
}
2022-11-23 05:13:18 +08:00
2024-12-02 02:03:15 +08:00
// SignIn render sign in page
func SignIn ( ctx * context . Context ) {
2026-01-03 11:43:04 +08:00
if performAutoLogin ( ctx ) {
2024-12-02 02:03:15 +08:00
return
}
if ctx . IsSigned {
2026-01-03 11:43:04 +08:00
redirectAfterAuth ( ctx )
2024-12-02 02:03:15 +08:00
return
}
2026-04-01 18:20:57 +05:30
data := prepareSignInPageData ( ctx )
if performAutoLoginOAuth2 ( ctx , & data ) {
return
}
2022-01-02 21:12:35 +08:00
ctx . HTML ( http . StatusOK , tplSignIn )
}
// SignInPost response for sign in request
func SignInPost ( ctx * context . Context ) {
2024-12-02 02:03:15 +08:00
if ! setting . Service . EnablePasswordSignInForm {
2025-02-17 14:13:17 +08:00
ctx . HTTPError ( http . StatusForbidden )
2022-01-02 21:12:35 +08:00
return
}
2024-12-02 02:03:15 +08:00
prepareSignInPageData ( ctx )
2022-01-02 21:12:35 +08:00
if ctx . HasError ( ) {
ctx . HTML ( http . StatusOK , tplSignIn )
return
}
form := web . GetForm ( ctx ) . ( * forms . SignInForm )
2022-11-23 05:13:18 +08:00
if setting . Service . EnableCaptcha && setting . Service . RequireCaptchaForLogin {
context . VerifyCaptcha ( ctx , tplSignIn , form )
if ctx . Written ( ) {
return
}
}
2023-09-14 19:09:32 +02:00
u , source , err := auth_service . UserSignIn ( ctx , form . UserName , form . Password )
2022-01-02 21:12:35 +08:00
if err != nil {
2023-07-04 06:39:38 +08:00
if errors . Is ( err , util . ErrNotExist ) || errors . Is ( err , util . ErrInvalidArgument ) {
2026-02-27 20:38:44 +08:00
ctx . RenderWithErrDeprecated ( ctx . Tr ( "form.username_password_incorrect" ) , tplSignIn , & form )
2024-09-11 19:58:45 +02:00
log . Warn ( "Failed authentication attempt for %s from %s: %v" , form . UserName , ctx . RemoteAddr ( ) , err )
2022-01-02 21:12:35 +08:00
} else if user_model . IsErrEmailAlreadyUsed ( err ) {
2026-02-27 20:38:44 +08:00
ctx . RenderWithErrDeprecated ( ctx . Tr ( "form.email_been_used" ) , tplSignIn , & form )
2024-09-11 19:58:45 +02:00
log . Warn ( "Failed authentication attempt for %s from %s: %v" , form . UserName , ctx . RemoteAddr ( ) , err )
2022-01-02 21:12:35 +08:00
} else if user_model . IsErrUserProhibitLogin ( err ) {
2024-09-11 19:58:45 +02:00
log . Warn ( "Failed authentication attempt for %s from %s: %v" , form . UserName , ctx . RemoteAddr ( ) , err )
2022-01-02 21:12:35 +08:00
ctx . Data [ "Title" ] = ctx . Tr ( "auth.prohibit_login" )
ctx . HTML ( http . StatusOK , "user/auth/prohibit_login" )
} else if user_model . IsErrUserInactive ( err ) {
if setting . Service . RegisterEmailConfirm {
ctx . Data [ "Title" ] = ctx . Tr ( "auth.active_your_account" )
ctx . HTML ( http . StatusOK , TplActivate )
} else {
2024-09-11 19:58:45 +02:00
log . Warn ( "Failed authentication attempt for %s from %s: %v" , form . UserName , ctx . RemoteAddr ( ) , err )
2022-01-02 21:12:35 +08:00
ctx . Data [ "Title" ] = ctx . Tr ( "auth.prohibit_login" )
ctx . HTML ( http . StatusOK , "user/auth/prohibit_login" )
}
} else {
ctx . ServerError ( "UserSignIn" , err )
}
return
}
// Now handle 2FA:
// First of all if the source can skip local two fa we're done
2025-04-29 06:31:59 +08:00
if source . TwoFactorShouldSkip ( ) {
2022-01-02 21:12:35 +08:00
handleSignIn ( ctx , u , form . Remember )
return
}
// If this user is enrolled in 2FA TOTP, we can't sign the user in just yet.
// Instead, redirect them to the 2FA authentication page.
2023-09-15 08:13:19 +02:00
hasTOTPtwofa , err := auth . HasTwoFactorByUID ( ctx , u . ID )
2022-01-02 21:12:35 +08:00
if err != nil {
ctx . ServerError ( "UserSignIn" , err )
return
}
2022-01-14 23:03:31 +08:00
// Check if the user has webauthn registration
2023-09-16 16:39:12 +02:00
hasWebAuthnTwofa , err := auth . HasWebAuthnRegistrationsByUID ( ctx , u . ID )
2022-01-02 21:12:35 +08:00
if err != nil {
ctx . ServerError ( "UserSignIn" , err )
return
}
2022-01-14 23:03:31 +08:00
if ! hasTOTPtwofa && ! hasWebAuthnTwofa {
2025-04-29 06:31:59 +08:00
// No two-factor auth configured we can sign in the user
2022-01-02 21:12:35 +08:00
handleSignIn ( ctx , u , form . Remember )
return
}
2023-07-04 20:36:08 +02:00
updates := map [ string ] any {
2022-11-10 19:43:06 +08:00
// User will need to use 2FA TOTP or WebAuthn, save data
"twofaUid" : u . ID ,
"twofaRemember" : form . Remember ,
2022-01-02 21:12:35 +08:00
}
if hasTOTPtwofa {
2022-06-27 04:20:58 +02:00
// User will need to use WebAuthn, save data
2022-11-10 19:43:06 +08:00
updates [ "totpEnrolled" ] = u . ID
2022-01-02 21:12:35 +08:00
}
2022-11-10 19:43:06 +08:00
if err := updateSession ( ctx , nil , updates ) ; err != nil {
ctx . ServerError ( "UserSignIn: Unable to update session" , err )
2022-01-02 21:12:35 +08:00
return
}
2022-06-27 04:20:58 +02:00
// If we have WebAuthn redirect there first
2022-01-14 23:03:31 +08:00
if hasWebAuthnTwofa {
ctx . Redirect ( setting . AppSubURL + "/user/webauthn" )
2022-01-02 21:12:35 +08:00
return
}
// Fallback to 2FA
ctx . Redirect ( setting . AppSubURL + "/user/two_factor" )
}
// This handles the final part of the sign-in process of the user.
func handleSignIn ( ctx * context . Context , u * user_model . User , remember bool ) {
2026-01-03 11:43:04 +08:00
handleSignInFull ( ctx , u , remember )
2022-01-02 21:12:35 +08:00
if ctx . Written ( ) {
return
}
2026-01-03 11:43:04 +08:00
redirectAfterAuth ( ctx )
2022-01-02 21:12:35 +08:00
}
2026-01-03 11:43:04 +08:00
func handleSignInFull ( ctx * context . Context , u * user_model . User , remember bool ) {
2022-01-02 21:12:35 +08:00
if remember {
2023-10-14 02:56:41 +02:00
nt , token , err := auth_service . CreateAuthTokenForUserID ( ctx , u . ID )
if err != nil {
ctx . ServerError ( "CreateAuthTokenForUserID" , err )
2026-01-03 11:43:04 +08:00
return
2023-10-14 02:56:41 +02:00
}
ctx . SetSiteCookie ( setting . CookieRememberName , nt . ID + ":" + token , setting . LogInRememberDays * timeutil . Day )
2022-01-02 21:12:35 +08:00
}
2025-04-29 06:31:59 +08:00
userHasTwoFactorAuth , err := auth . HasTwoFactorOrWebAuthn ( ctx , u . ID )
if err != nil {
ctx . ServerError ( "HasTwoFactorOrWebAuthn" , err )
2026-01-03 11:43:04 +08:00
return
2025-04-29 06:31:59 +08:00
}
2022-11-10 19:43:06 +08:00
if err := updateSession ( ctx , [ ] string {
2025-04-29 06:31:59 +08:00
// Delete the openid, 2fa and link_account data
2022-11-10 19:43:06 +08:00
"openid_verified_uri" ,
"openid_signin_remember" ,
"openid_determined_email" ,
"openid_determined_username" ,
"twofaUid" ,
"twofaRemember" ,
"linkAccount" ,
2025-07-11 02:35:59 +08:00
"linkAccountData" ,
2023-07-04 20:36:08 +02:00
} , map [ string ] any {
2025-04-29 06:31:59 +08:00
session . KeyUID : u . ID ,
session . KeyUname : u . Name ,
session . KeyUserHasTwoFactorAuth : userHasTwoFactorAuth ,
2022-11-10 19:43:06 +08:00
} ) ; err != nil {
2022-01-02 21:12:35 +08:00
ctx . ServerError ( "RegenerateSession" , err )
2026-01-03 11:43:04 +08:00
return
2022-01-02 21:12:35 +08:00
}
// Language setting of the user overwrites the one previously set
// If the user does not have a locale set, we save the current one.
2024-02-04 14:29:09 +01:00
if u . Language == "" {
opts := & user_service . UpdateOptions {
Language : optional . Some ( ctx . Locale . Language ( ) ) ,
}
if err := user_service . UpdateUser ( ctx , u , opts ) ; err != nil {
ctx . ServerError ( "UpdateUser Language" , fmt . Errorf ( "Error updating user language [user: %d, locale: %s]" , u . ID , ctx . Locale . Language ( ) ) )
2026-01-03 11:43:04 +08:00
return
2022-01-02 21:12:35 +08:00
}
}
middleware . SetLocaleCookie ( ctx . Resp , u . Language , 0 )
if ctx . Locale . Language ( ) != u . Language {
ctx . Locale = middleware . Locale ( ctx . Resp , ctx . Req )
}
// Register last login
2024-02-04 14:29:09 +01:00
if err := user_service . UpdateUser ( ctx , u , & user_service . UpdateOptions { SetLastLogin : true } ) ; err != nil {
ctx . ServerError ( "UpdateUser" , err )
2026-01-03 11:43:04 +08:00
return
2022-01-02 21:12:35 +08:00
}
}
2024-04-25 19:22:32 +08:00
// extractUserNameFromOAuth2 tries to extract a normalized username from the given OAuth2 user.
// It returns ("", nil) if the required field doesn't exist.
func extractUserNameFromOAuth2 ( gothUser * goth . User ) ( string , error ) {
2022-01-02 21:12:35 +08:00
switch setting . OAuth2Client . Username {
case setting . OAuth2UsernameEmail :
2024-04-25 19:22:32 +08:00
return user_model . NormalizeUserName ( gothUser . Email )
2024-04-16 07:41:39 +02:00
case setting . OAuth2UsernamePreferredUsername :
2024-04-25 19:22:32 +08:00
if preferredUsername , ok := gothUser . RawData [ "preferred_username" ] . ( string ) ; ok {
return user_model . NormalizeUserName ( preferredUsername )
2024-04-16 07:41:39 +02:00
}
2024-04-25 19:22:32 +08:00
return "" , nil
2022-01-02 21:12:35 +08:00
case setting . OAuth2UsernameNickname :
2024-01-03 16:48:20 -08:00
return user_model . NormalizeUserName ( gothUser . NickName )
2022-01-02 21:12:35 +08:00
default : // OAuth2UsernameUserid
2024-01-03 16:48:20 -08:00
return gothUser . UserID , nil
2022-01-02 21:12:35 +08:00
}
}
// HandleSignOut resets the session and sets the cookies
func HandleSignOut ( ctx * context . Context ) {
_ = ctx . Session . Flush ( )
_ = ctx . Session . Destroy ( ctx . Resp , ctx . Req )
2023-04-14 03:45:33 +08:00
ctx . DeleteSiteCookie ( setting . CookieRememberName )
2022-01-02 21:12:35 +08:00
middleware . DeleteRedirectToCookie ( ctx . Resp )
}
// SignOut sign out from login status
func SignOut ( ctx * context . Context ) {
2022-03-22 08:03:22 +01:00
if ctx . Doer != nil {
eventsource . GetManager ( ) . SendMessageBlocking ( ctx . Doer . ID , & eventsource . Event {
2022-01-02 21:12:35 +08:00
Name : "logout" ,
Data : ctx . Session . ID ( ) ,
} )
}
2026-03-01 07:28:26 +01:00
// prepare the sign-out URL before destroying the session
redirectTo := buildSignOutRedirectURL ( ctx )
2022-01-02 21:12:35 +08:00
HandleSignOut ( ctx )
2026-03-01 07:28:26 +01:00
ctx . Redirect ( redirectTo )
}
func buildSignOutRedirectURL ( ctx * context . Context ) string {
if ctx . Doer != nil && ctx . Doer . LoginType == auth . OAuth2 {
if s := buildOIDCEndSessionURL ( ctx , ctx . Doer ) ; s != "" {
return s
}
}
2026-04-11 00:49:55 +08:00
// The assumption is: if reverse proxy auth is enabled, then the users should only sign-in via reverse proxy auth.
// TODO: in the future, if we need to distinguish different sign-in methods, we need to save the sign-in method in session and check here
if setting . Service . EnableReverseProxyAuth && setting . ReverseProxyLogoutRedirect != "" {
return setting . ReverseProxyLogoutRedirect
}
2026-03-01 07:28:26 +01:00
return setting . AppSubURL + "/"
2022-01-02 21:12:35 +08:00
}
2026-03-08 23:57:37 +08:00
func prepareSignUpPageData ( ctx * context . Context ) bool {
2022-01-02 21:12:35 +08:00
ctx . Data [ "Title" ] = ctx . Tr ( "sign_up" )
ctx . Data [ "SignUpLink" ] = setting . AppSubURL + "/user/sign_up"
2026-03-08 23:57:37 +08:00
ctx . Data [ "PageIsSignUp" ] = true
2026-04-01 18:20:57 +05:30
ctx . Data [ "EnableSSPI" ] = auth . IsSSPIEnabled ( ctx )
2022-01-02 21:12:35 +08:00
2026-03-08 23:57:37 +08:00
hasUsers , err := user_model . HasUsers ( ctx )
if err != nil {
ctx . ServerError ( "HasUsers" , err )
return false
}
2025-06-22 02:48:06 +08:00
ctx . Data [ "IsFirstTimeRegistration" ] = ! hasUsers . HasAnyUser
2024-03-02 16:42:31 +01:00
oauth2Providers , err := oauth2 . GetOAuth2Providers ( ctx , optional . Some ( true ) )
2023-09-13 08:14:21 +03:00
if err != nil {
2026-03-08 23:57:37 +08:00
ctx . ServerError ( "GetOAuth2Providers" , err )
return false
2023-09-13 08:14:21 +03:00
}
ctx . Data [ "OAuth2Providers" ] = oauth2Providers
2026-03-08 23:57:37 +08:00
prepareCommonAuthPageData ( ctx , CommonAuthOptions {
EnableCaptcha : setting . Service . EnableCaptcha ,
} )
2022-01-02 21:12:35 +08:00
2022-01-20 18:46:10 +01:00
// Show Disabled Registration message if DisableRegistration or AllowOnlyExternalRegistration options are true
2022-01-02 21:12:35 +08:00
ctx . Data [ "DisableRegistration" ] = setting . Service . DisableRegistration || setting . Service . AllowOnlyExternalRegistration
2026-03-08 23:57:37 +08:00
return true
}
2023-08-31 12:26:13 -04:00
2026-03-08 23:57:37 +08:00
// SignUp render the register page
func SignUp ( ctx * context . Context ) {
if ! prepareSignUpPageData ( ctx ) {
return
}
rememberAuthRedirectLink ( ctx )
2022-01-02 21:12:35 +08:00
ctx . HTML ( http . StatusOK , tplSignUp )
}
// SignUpPost response for sign up information submission
func SignUpPost ( ctx * context . Context ) {
2026-03-08 23:57:37 +08:00
if ! prepareSignUpPageData ( ctx ) {
2023-09-13 08:14:21 +03:00
return
}
2026-03-08 23:57:37 +08:00
form := web . GetForm ( ctx ) . ( * forms . RegisterForm )
2022-01-02 21:12:35 +08:00
2022-01-20 18:46:10 +01:00
// Permission denied if DisableRegistration or AllowOnlyExternalRegistration options are true
2022-01-02 21:12:35 +08:00
if setting . Service . DisableRegistration || setting . Service . AllowOnlyExternalRegistration {
2025-02-17 14:13:17 +08:00
ctx . HTTPError ( http . StatusForbidden )
2022-01-02 21:12:35 +08:00
return
}
if ctx . HasError ( ) {
ctx . HTML ( http . StatusOK , tplSignUp )
return
}
2022-11-23 05:13:18 +08:00
context . VerifyCaptcha ( ctx , tplSignUp , form )
if ctx . Written ( ) {
return
2022-01-02 21:12:35 +08:00
}
if ! form . IsEmailDomainAllowed ( ) {
2026-02-27 20:38:44 +08:00
ctx . RenderWithErrDeprecated ( ctx . Tr ( "auth.email_domain_blacklisted" ) , tplSignUp , & form )
2022-01-02 21:12:35 +08:00
return
}
if form . Password != form . Retype {
ctx . Data [ "Err_Password" ] = true
2026-02-27 20:38:44 +08:00
ctx . RenderWithErrDeprecated ( ctx . Tr ( "form.password_not_match" ) , tplSignUp , & form )
2022-01-02 21:12:35 +08:00
return
}
if len ( form . Password ) < setting . MinPasswordLength {
ctx . Data [ "Err_Password" ] = true
2026-02-27 20:38:44 +08:00
ctx . RenderWithErrDeprecated ( ctx . Tr ( "auth.password_too_short" , setting . MinPasswordLength ) , tplSignUp , & form )
2022-01-02 21:12:35 +08:00
return
}
if ! password . IsComplexEnough ( form . Password ) {
ctx . Data [ "Err_Password" ] = true
2026-02-27 20:38:44 +08:00
ctx . RenderWithErrDeprecated ( password . BuildComplexityError ( ctx . Locale ) , tplSignUp , & form )
2022-01-02 21:12:35 +08:00
return
}
2024-02-04 14:29:09 +01:00
if err := password . IsPwned ( ctx , form . Password ) ; err != nil {
2024-09-02 20:36:24 +02:00
errMsg := ctx . Tr ( "auth.password_pwned" , "https://haveibeenpwned.com/Passwords" )
2024-02-04 14:29:09 +01:00
if password . IsErrIsPwnedRequest ( err ) {
2022-01-02 21:12:35 +08:00
log . Error ( err . Error ( ) )
errMsg = ctx . Tr ( "auth.password_pwned_err" )
}
ctx . Data [ "Err_Password" ] = true
2026-02-27 20:38:44 +08:00
ctx . RenderWithErrDeprecated ( errMsg , tplSignUp , & form )
2022-01-02 21:12:35 +08:00
return
}
u := & user_model . User {
2022-04-29 21:38:11 +02:00
Name : form . UserName ,
Email : form . Email ,
Passwd : form . Password ,
2022-01-02 21:12:35 +08:00
}
2025-07-11 02:35:59 +08:00
if ! createAndHandleCreatedUser ( ctx , tplSignUp , form , u , nil , nil ) {
2022-01-02 21:12:35 +08:00
// error already handled
return
}
ctx . Flash . Success ( ctx . Tr ( "auth.sign_up_successful" ) )
handleSignIn ( ctx , u , false )
}
// createAndHandleCreatedUser calls createUserInContext and
// then handleUserCreated.
2025-07-11 02:35:59 +08:00
func createAndHandleCreatedUser ( ctx * context . Context , tpl templates . TplName , form any , u * user_model . User , overwrites * user_model . CreateUserOverwriteOptions , possibleLinkAccountData * LinkAccountData ) bool {
if ! createUserInContext ( ctx , tpl , form , u , overwrites , possibleLinkAccountData ) {
2022-01-02 21:12:35 +08:00
return false
}
2025-07-11 02:35:59 +08:00
return handleUserCreated ( ctx , u , possibleLinkAccountData )
2022-01-02 21:12:35 +08:00
}
// createUserInContext creates a user and handles errors within a given context.
2025-07-11 02:35:59 +08:00
// Optionally, a template can be specified.
func createUserInContext ( ctx * context . Context , tpl templates . TplName , form any , u * user_model . User , overwrites * user_model . CreateUserOverwriteOptions , possibleLinkAccountData * LinkAccountData ) ( ok bool ) {
2024-09-09 17:05:16 -04:00
meta := & user_model . Meta {
InitialIP : ctx . RemoteAddr ( ) ,
InitialUserAgent : ctx . Req . UserAgent ( ) ,
}
if err := user_model . CreateUser ( ctx , u , meta , overwrites ) ; err != nil {
2025-07-11 02:35:59 +08:00
if possibleLinkAccountData != nil && ( user_model . IsErrUserAlreadyExist ( err ) || user_model . IsErrEmailAlreadyUsed ( err ) ) {
2025-03-29 22:32:28 +01:00
switch setting . OAuth2Client . AccountLinking {
case setting . OAuth2AccountLinkingAuto :
2022-01-02 21:12:35 +08:00
var user * user_model . User
user = & user_model . User { Name : u . Name }
2026-04-03 20:19:04 +08:00
hasUser , err := user_model . GetIndividualUser ( ctx , user )
2022-01-02 21:12:35 +08:00
if ! hasUser || err != nil {
user = & user_model . User { Email : u . Email }
2026-04-03 20:19:04 +08:00
hasUser , err = user_model . GetIndividualUser ( ctx , user )
2022-01-02 21:12:35 +08:00
if ! hasUser || err != nil {
ctx . ServerError ( "UserLinkAccount" , err )
2023-07-07 07:31:56 +02:00
return false
2022-01-02 21:12:35 +08:00
}
}
// TODO: probably we should respect 'remember' user's choice...
2025-07-11 02:35:59 +08:00
oauth2LinkAccount ( ctx , user , possibleLinkAccountData , true )
2023-07-07 07:31:56 +02:00
return false // user is already created here, all redirects are handled
2025-03-29 22:32:28 +01:00
case setting . OAuth2AccountLinkingLogin :
2025-07-20 09:49:36 +08:00
showLinkingLogin ( ctx , possibleLinkAccountData . AuthSourceID , possibleLinkAccountData . GothUser )
2023-07-07 07:31:56 +02:00
return false // user will be created only after linking login
2022-01-02 21:12:35 +08:00
}
}
2025-07-11 02:35:59 +08:00
// handle error without a template
2022-01-02 21:12:35 +08:00
if len ( tpl ) == 0 {
ctx . ServerError ( "CreateUser" , err )
2023-07-07 07:31:56 +02:00
return false
2022-01-02 21:12:35 +08:00
}
// handle error with template
switch {
case user_model . IsErrUserAlreadyExist ( err ) :
ctx . Data [ "Err_UserName" ] = true
2026-02-27 20:38:44 +08:00
ctx . RenderWithErrDeprecated ( ctx . Tr ( "form.username_been_taken" ) , tpl , form )
2022-01-02 21:12:35 +08:00
case user_model . IsErrEmailAlreadyUsed ( err ) :
ctx . Data [ "Err_Email" ] = true
2026-02-27 20:38:44 +08:00
ctx . RenderWithErrDeprecated ( ctx . Tr ( "form.email_been_used" ) , tpl , form )
2022-03-15 01:39:54 +08:00
case user_model . IsErrEmailCharIsNotSupported ( err ) :
ctx . Data [ "Err_Email" ] = true
2026-02-27 20:38:44 +08:00
ctx . RenderWithErrDeprecated ( ctx . Tr ( "form.email_invalid" ) , tpl , form )
2022-01-02 21:12:35 +08:00
case user_model . IsErrEmailInvalid ( err ) :
ctx . Data [ "Err_Email" ] = true
2026-02-27 20:38:44 +08:00
ctx . RenderWithErrDeprecated ( ctx . Tr ( "form.email_invalid" ) , tpl , form )
2022-01-02 21:12:35 +08:00
case db . IsErrNameReserved ( err ) :
ctx . Data [ "Err_UserName" ] = true
2026-02-27 20:38:44 +08:00
ctx . RenderWithErrDeprecated ( ctx . Tr ( "user.form.name_reserved" , err . ( db . ErrNameReserved ) . Name ) , tpl , form )
2022-01-02 21:12:35 +08:00
case db . IsErrNamePatternNotAllowed ( err ) :
ctx . Data [ "Err_UserName" ] = true
2026-02-27 20:38:44 +08:00
ctx . RenderWithErrDeprecated ( ctx . Tr ( "user.form.name_pattern_not_allowed" , err . ( db . ErrNamePatternNotAllowed ) . Pattern ) , tpl , form )
2022-01-02 21:12:35 +08:00
case db . IsErrNameCharsNotAllowed ( err ) :
ctx . Data [ "Err_UserName" ] = true
2026-02-27 20:38:44 +08:00
ctx . RenderWithErrDeprecated ( ctx . Tr ( "user.form.name_chars_not_allowed" , err . ( db . ErrNameCharsNotAllowed ) . Name ) , tpl , form )
2022-01-02 21:12:35 +08:00
default :
ctx . ServerError ( "CreateUser" , err )
}
2023-07-07 07:31:56 +02:00
return false
2022-01-02 21:12:35 +08:00
}
log . Trace ( "Account created: %s" , u . Name )
return true
}
// handleUserCreated does additional steps after a new user is created.
// It auto-sets admin for the only user, updates the optional external user and
// sends a confirmation email if required.
2025-07-11 02:35:59 +08:00
func handleUserCreated ( ctx * context . Context , u * user_model . User , possibleLinkAccountData * LinkAccountData ) ( ok bool ) {
2022-01-02 21:12:35 +08:00
// Auto-set admin for the only user.
2025-06-22 02:48:06 +08:00
hasUsers , err := user_model . HasUsers ( ctx )
if err != nil {
ctx . ServerError ( "HasUsers" , err )
return false
}
if hasUsers . HasOnlyOneUser {
// the only user is the one just created, will set it as admin
2024-02-04 14:29:09 +01:00
opts := & user_service . UpdateOptions {
IsActive : optional . Some ( true ) ,
2025-06-10 04:57:45 +08:00
IsAdmin : user_service . UpdateOptionFieldFromValue ( true ) ,
2024-02-04 14:29:09 +01:00
SetLastLogin : true ,
}
if err := user_service . UpdateUser ( ctx , u , opts ) ; err != nil {
2022-01-02 21:12:35 +08:00
ctx . ServerError ( "UpdateUser" , err )
2023-07-07 07:31:56 +02:00
return false
2022-01-02 21:12:35 +08:00
}
}
// update external user information
2025-07-11 02:35:59 +08:00
if possibleLinkAccountData != nil {
2025-07-20 09:49:36 +08:00
if err := externalaccount . EnsureLinkExternalToUser ( ctx , possibleLinkAccountData . AuthSourceID , u , possibleLinkAccountData . GothUser ) ; err != nil {
2024-07-16 13:33:16 -05:00
log . Error ( "EnsureLinkExternalToUser failed: %v" , err )
2022-01-02 21:12:35 +08:00
}
}
2024-02-26 05:55:00 +08:00
// for active user or the first (admin) user, we don't need to send confirmation email
if u . IsActive || u . ID == 1 {
return true
}
2022-03-18 09:57:07 +00:00
2024-02-26 05:55:00 +08:00
if setting . Service . RegisterManualConfirm {
renderActivationPromptMessage ( ctx , ctx . Locale . Tr ( "auth.manual_activation_only" ) )
return false
}
2022-01-02 21:12:35 +08:00
2024-02-26 05:55:00 +08:00
sendActivateEmail ( ctx , u )
return false
}
2022-01-02 21:12:35 +08:00
2024-02-26 05:55:00 +08:00
func renderActivationPromptMessage ( ctx * context . Context , msg template . HTML ) {
ctx . Data [ "ActivationPromptMessage" ] = msg
ctx . HTML ( http . StatusOK , TplActivatePrompt )
}
func sendActivateEmail ( ctx * context . Context , u * user_model . User ) {
if ctx . Cache . IsExist ( "MailResendLimit_" + u . LowerName ) {
renderActivationPromptMessage ( ctx , ctx . Locale . Tr ( "auth.resent_limit_prompt" ) )
return
2022-01-02 21:12:35 +08:00
}
2024-02-26 05:55:00 +08:00
if err := ctx . Cache . Put ( "MailResendLimit_" + u . LowerName , u . LowerName , 180 ) ; err != nil {
log . Error ( "Set cache(MailResendLimit) fail: %v" , err )
renderActivationPromptMessage ( ctx , ctx . Locale . Tr ( "auth.resent_limit_prompt" ) )
return
}
mailer . SendActivateAccountMail ( ctx . Locale , u )
activeCodeLives := timeutil . MinutesToFriendly ( setting . Service . ActiveCodeLives , ctx . Locale )
2024-02-27 18:55:13 +08:00
msgHTML := ctx . Locale . Tr ( "auth.confirmation_mail_sent_prompt_ex" , u . Email , activeCodeLives )
2024-02-26 05:55:00 +08:00
renderActivationPromptMessage ( ctx , msgHTML )
}
func renderActivationVerifyPassword ( ctx * context . Context , code string ) {
ctx . Data [ "ActivationCode" ] = code
ctx . Data [ "NeedVerifyLocalPassword" ] = true
ctx . HTML ( http . StatusOK , TplActivate )
2022-01-02 21:12:35 +08:00
}
2024-02-27 18:55:13 +08:00
func renderActivationChangeEmail ( ctx * context . Context ) {
ctx . HTML ( http . StatusOK , TplActivate )
}
2022-01-02 21:12:35 +08:00
// Activate render activate user page
func Activate ( ctx * context . Context ) {
code := ctx . FormString ( "code" )
2024-02-26 05:55:00 +08:00
if code == "" {
if ctx . Doer == nil {
ctx . Redirect ( setting . AppSubURL + "/user/login" )
return
} else if ctx . Doer . IsActive {
ctx . Redirect ( setting . AppSubURL + "/" )
2022-01-02 21:12:35 +08:00
return
}
2024-02-26 05:55:00 +08:00
if setting . MailService == nil || ! setting . Service . RegisterEmailConfirm {
renderActivationPromptMessage ( ctx , ctx . Tr ( "auth.disable_register_mail" ) )
return
2022-01-02 21:12:35 +08:00
}
2024-02-26 05:55:00 +08:00
2024-02-27 18:55:13 +08:00
// Resend confirmation email. FIXME: ideally this should be in a POST request
2024-02-26 05:55:00 +08:00
sendActivateEmail ( ctx , ctx . Doer )
2022-01-02 21:12:35 +08:00
return
}
2024-02-26 05:55:00 +08:00
// TODO: ctx.Doer/ctx.Data["SignedUser"] could be nil or not the same user as the one being activated
2024-12-27 19:16:23 +08:00
user := user_model . VerifyUserTimeLimitCode ( ctx , & user_model . TimeLimitCodeOptions { Purpose : user_model . TimeLimitCodeActivateAccount } , code )
2024-02-26 05:55:00 +08:00
if user == nil { // if code is wrong
renderActivationPromptMessage ( ctx , ctx . Locale . Tr ( "auth.invalid_code" ) )
2022-01-02 21:12:35 +08:00
return
}
// if account is local account, verify password
if user . LoginSource == 0 {
2024-02-26 05:55:00 +08:00
renderActivationVerifyPassword ( ctx , code )
2022-01-02 21:12:35 +08:00
return
}
handleAccountActivation ( ctx , user )
}
// ActivatePost handles account activation with password check
func ActivatePost ( ctx * context . Context ) {
code := ctx . FormString ( "code" )
2024-02-27 18:55:13 +08:00
if ctx . Doer != nil && ctx . Doer . IsActive {
ctx . Redirect ( setting . AppSubURL + "/user/activate" ) // it will redirect again to the correct page
return
}
if code == "" {
newEmail := strings . TrimSpace ( ctx . FormString ( "change_email" ) )
if ctx . Doer != nil && newEmail != "" && ! strings . EqualFold ( ctx . Doer . Email , newEmail ) {
if user_model . ValidateEmail ( newEmail ) != nil {
ctx . Flash . Error ( ctx . Locale . Tr ( "form.email_invalid" ) , true )
renderActivationChangeEmail ( ctx )
return
}
err := user_model . ChangeInactivePrimaryEmail ( ctx , ctx . Doer . ID , ctx . Doer . Email , newEmail )
if err != nil {
ctx . Flash . Error ( ctx . Locale . Tr ( "admin.emails.not_updated" , newEmail ) , true )
renderActivationChangeEmail ( ctx )
return
}
ctx . Doer . Email = newEmail
}
// 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.
2022-01-02 21:12:35 +08:00
ctx . Redirect ( setting . AppSubURL + "/user/activate" )
return
}
2024-02-26 05:55:00 +08:00
// TODO: ctx.Doer/ctx.Data["SignedUser"] could be nil or not the same user as the one being activated
2024-12-27 19:16:23 +08:00
user := user_model . VerifyUserTimeLimitCode ( ctx , & user_model . TimeLimitCodeOptions { Purpose : user_model . TimeLimitCodeActivateAccount } , code )
2024-02-26 05:55:00 +08:00
if user == nil { // if code is wrong
renderActivationPromptMessage ( ctx , ctx . Locale . Tr ( "auth.invalid_code" ) )
2022-01-02 21:12:35 +08:00
return
}
// if account is local account, verify password
if user . LoginSource == 0 {
password := ctx . FormString ( "password" )
2024-02-26 05:55:00 +08:00
if password == "" {
renderActivationVerifyPassword ( ctx , code )
2022-01-02 21:12:35 +08:00
return
}
if ! user . ValidatePassword ( password ) {
2024-02-26 05:55:00 +08:00
ctx . Flash . Error ( ctx . Locale . Tr ( "auth.invalid_password" ) , true )
renderActivationVerifyPassword ( ctx , code )
2022-01-02 21:12:35 +08:00
return
}
}
handleAccountActivation ( ctx , user )
}
func handleAccountActivation ( ctx * context . Context , user * user_model . User ) {
user . IsActive = true
var err error
if user . Rands , err = user_model . GetUserSalt ( ) ; err != nil {
ctx . ServerError ( "UpdateUser" , err )
return
}
2022-03-22 23:22:54 +08:00
if err := user_model . UpdateUserCols ( ctx , user , "is_active" , "rands" ) ; err != nil {
2022-01-02 21:12:35 +08:00
if user_model . IsErrUserNotExist ( err ) {
2025-02-17 14:13:17 +08:00
ctx . NotFound ( err )
2022-01-02 21:12:35 +08:00
} else {
ctx . ServerError ( "UpdateUser" , err )
}
return
}
2023-09-14 19:09:32 +02:00
if err := user_model . ActivateUserEmail ( ctx , user . ID , user . Email , true ) ; err != nil {
2022-01-02 21:12:35 +08:00
log . Error ( "Unable to activate email for user: %-v with email: %s: %v" , user , user . Email , err )
ctx . ServerError ( "ActivateUserEmail" , err )
return
}
log . Trace ( "User activated: %s" , user . Name )
2023-07-04 20:36:08 +02:00
if err := updateSession ( ctx , nil , map [ string ] any {
2022-11-10 19:43:06 +08:00
"uid" : user . ID ,
"uname" : user . Name ,
} ) ; err != nil {
2022-01-02 21:12:35 +08:00
log . Error ( "Unable to regenerate session for user: %-v with email: %s: %v" , user , user . Email , err )
ctx . ServerError ( "ActivateUserEmail" , err )
return
}
if err := resetLocale ( ctx , user ) ; err != nil {
ctx . ServerError ( "resetLocale" , err )
return
}
2024-02-04 14:29:09 +01:00
if err := user_service . UpdateUser ( ctx , user , & user_service . UpdateOptions { SetLastLogin : true } ) ; err != nil {
ctx . ServerError ( "UpdateUser" , err )
2022-11-10 00:42:06 +08:00
return
}
2022-01-02 21:12:35 +08:00
ctx . Flash . Success ( ctx . Tr ( "auth.account_activated" ) )
2026-01-03 11:43:04 +08:00
redirectAfterAuth ( ctx )
2022-01-02 21:12:35 +08:00
}
// ActivateEmail render the activate email page
func ActivateEmail ( ctx * context . Context ) {
code := ctx . FormString ( "code" )
emailStr := ctx . FormString ( "email" )
// Verify code.
2023-09-14 19:09:32 +02:00
if email := user_model . VerifyActiveEmailCode ( ctx , code , emailStr ) ; email != nil {
if err := user_model . ActivateEmail ( ctx , email ) ; err != nil {
2022-01-02 21:12:35 +08:00
ctx . ServerError ( "ActivateEmail" , err )
2024-05-28 17:31:59 +08:00
return
2022-01-02 21:12:35 +08:00
}
log . Trace ( "Email activated: %s" , email . Email )
ctx . Flash . Success ( ctx . Tr ( "settings.add_email_success" ) )
2022-12-03 10:48:26 +08:00
if u , err := user_model . GetUserByID ( ctx , email . UID ) ; err != nil {
2022-01-02 21:12:35 +08:00
log . Warn ( "GetUserByID: %d" , email . UID )
2023-12-19 17:29:05 +08:00
} else {
2022-01-02 21:12:35 +08:00
// Allow user to validate more emails
_ = ctx . Cache . Delete ( "MailResendLimit_" + u . LowerName )
}
}
// FIXME: e-mail verification does not require the user to be logged in,
// so this could be redirecting to the login page.
// Should users be logged in automatically here? (consider 2FA requirements, etc.)
ctx . Redirect ( setting . AppSubURL + "/user/settings/account" )
}
2022-11-10 19:43:06 +08:00
2023-07-04 20:36:08 +02:00
func updateSession ( ctx * context . Context , deletes [ ] string , updates map [ string ] any ) error {
2022-11-10 19:43:06 +08:00
if _ , err := session . RegenerateSession ( ctx . Resp , ctx . Req ) ; err != nil {
return fmt . Errorf ( "regenerate session: %w" , err )
}
sess := ctx . Session
sessID := sess . ID ( )
for _ , k := range deletes {
if err := sess . Delete ( k ) ; err != nil {
return fmt . Errorf ( "delete %v in session[%s]: %w" , k , sessID , err )
}
}
for k , v := range updates {
if err := sess . Set ( k , v ) ; err != nil {
return fmt . Errorf ( "set %v in session[%s]: %w" , k , sessID , err )
}
}
if err := sess . Release ( ) ; err != nil {
return fmt . Errorf ( "store session[%s]: %w" , sessID , err )
}
return nil
}