2022-01-02 16:12:35 +03:00
// Copyright 2014 The Gogs Authors. All rights reserved.
// Copyright 2018 The Gitea Authors. All rights reserved.
2022-11-27 21:20:29 +03:00
// SPDX-License-Identifier: MIT
2022-01-02 16:12:35 +03:00
package auth
import (
2022-10-19 22:07:21 +03:00
"errors"
2022-01-02 16:12:35 +03:00
"fmt"
"net/http"
"strings"
"code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/db"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/eventsource"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/password"
"code.gitea.io/gitea/modules/session"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil"
2022-10-19 22:07:21 +03:00
"code.gitea.io/gitea/modules/util"
2022-01-02 16:12:35 +03:00
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/modules/web/middleware"
"code.gitea.io/gitea/routers/utils"
auth_service "code.gitea.io/gitea/services/auth"
"code.gitea.io/gitea/services/auth/source/oauth2"
"code.gitea.io/gitea/services/externalaccount"
"code.gitea.io/gitea/services/forms"
"code.gitea.io/gitea/services/mailer"
"github.com/markbates/goth"
)
const (
// tplSignIn template for sign in page
tplSignIn base . TplName = "user/auth/signin"
// tplSignUp template path for sign up page
tplSignUp base . TplName = "user/auth/signup"
// TplActivate template path for activate user
TplActivate base . TplName = "user/auth/activate"
)
// AutoSignIn reads cookie and try to auto-login.
func AutoSignIn ( ctx * context . Context ) ( bool , error ) {
if ! db . HasEngine {
return false , nil
}
uname := ctx . GetCookie ( setting . CookieUserName )
if len ( uname ) == 0 {
return false , nil
}
isSucceed := false
defer func ( ) {
if ! isSucceed {
log . Trace ( "auto-login cookie cleared: %s" , uname )
ctx . DeleteCookie ( setting . CookieUserName )
ctx . DeleteCookie ( setting . CookieRememberName )
}
} ( )
2022-05-20 17:08:52 +03:00
u , err := user_model . GetUserByName ( ctx , uname )
2022-01-02 16:12:35 +03:00
if err != nil {
if ! user_model . IsErrUserNotExist ( err ) {
2022-10-24 22:29:17 +03:00
return false , fmt . Errorf ( "GetUserByName: %w" , err )
2022-01-02 16:12:35 +03:00
}
return false , nil
}
if val , ok := ctx . GetSuperSecureCookie (
base . EncodeMD5 ( u . Rands + u . Passwd ) , setting . CookieRememberName ) ; ! ok || val != u . Name {
return false , nil
}
isSucceed = true
2022-11-10 14:43:06 +03:00
if err := updateSession ( ctx , nil , map [ string ] interface { } {
// Set session IDs
"uid" : u . ID ,
"uname" : u . Name ,
} ) ; err != nil {
return false , fmt . Errorf ( "unable to updateSession: %w" , err )
2022-01-02 16:12:35 +03:00
}
if err := resetLocale ( ctx , u ) ; err != nil {
return false , err
}
middleware . DeleteCSRFCookie ( ctx . Resp )
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.
if len ( u . Language ) == 0 {
u . Language = ctx . Locale . Language ( )
2022-03-22 18:22:54 +03:00
if err := user_model . UpdateUserCols ( ctx , u , "language" ) ; err != nil {
2022-01-02 16:12:35 +03: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
}
func checkAutoLogin ( ctx * context . Context ) bool {
// Check auto-login
isSucceed , err := AutoSignIn ( ctx )
if err != nil {
ctx . ServerError ( "AutoSignIn" , err )
return true
}
redirectTo := ctx . FormString ( "redirect_to" )
if len ( redirectTo ) > 0 {
middleware . SetRedirectToCookie ( ctx . Resp , redirectTo )
} else {
redirectTo = ctx . GetCookie ( "redirect_to" )
}
if isSucceed {
middleware . DeleteRedirectToCookie ( ctx . Resp )
ctx . RedirectToFirst ( redirectTo , setting . AppSubURL + string ( setting . LandingPageURL ) )
return true
}
return false
}
// SignIn render sign in page
func SignIn ( ctx * context . Context ) {
ctx . Data [ "Title" ] = ctx . Tr ( "sign_in" )
// Check auto-login
if checkAutoLogin ( ctx ) {
return
}
orderedOAuth2Names , oauth2Providers , err := oauth2 . GetActiveOAuth2Providers ( )
if err != nil {
ctx . ServerError ( "UserSignIn" , err )
return
}
ctx . Data [ "OrderedOAuth2Names" ] = orderedOAuth2Names
ctx . Data [ "OAuth2Providers" ] = oauth2Providers
ctx . Data [ "Title" ] = ctx . Tr ( "sign_in" )
ctx . Data [ "SignInLink" ] = setting . AppSubURL + "/user/login"
ctx . Data [ "PageIsSignIn" ] = true
ctx . Data [ "PageIsLogin" ] = true
ctx . Data [ "EnableSSPI" ] = auth . IsSSPIEnabled ( )
2022-11-23 00:13:18 +03:00
if setting . Service . EnableCaptcha && setting . Service . RequireCaptchaForLogin {
context . SetCaptchaData ( ctx )
}
2022-01-02 16:12:35 +03:00
ctx . HTML ( http . StatusOK , tplSignIn )
}
// SignInPost response for sign in request
func SignInPost ( ctx * context . Context ) {
ctx . Data [ "Title" ] = ctx . Tr ( "sign_in" )
orderedOAuth2Names , oauth2Providers , err := oauth2 . GetActiveOAuth2Providers ( )
if err != nil {
ctx . ServerError ( "UserSignIn" , err )
return
}
ctx . Data [ "OrderedOAuth2Names" ] = orderedOAuth2Names
ctx . Data [ "OAuth2Providers" ] = oauth2Providers
ctx . Data [ "Title" ] = ctx . Tr ( "sign_in" )
ctx . Data [ "SignInLink" ] = setting . AppSubURL + "/user/login"
ctx . Data [ "PageIsSignIn" ] = true
ctx . Data [ "PageIsLogin" ] = true
ctx . Data [ "EnableSSPI" ] = auth . IsSSPIEnabled ( )
if ctx . HasError ( ) {
ctx . HTML ( http . StatusOK , tplSignIn )
return
}
form := web . GetForm ( ctx ) . ( * forms . SignInForm )
2022-11-23 00:13:18 +03:00
if setting . Service . EnableCaptcha && setting . Service . RequireCaptchaForLogin {
context . SetCaptchaData ( ctx )
context . VerifyCaptcha ( ctx , tplSignIn , form )
if ctx . Written ( ) {
return
}
}
2022-01-02 16:12:35 +03:00
u , source , err := auth_service . UserSignIn ( form . UserName , form . Password )
if err != nil {
2022-03-15 12:18:39 +03:00
if user_model . IsErrUserNotExist ( err ) || user_model . IsErrEmailAddressNotExist ( err ) {
2022-01-02 16:12:35 +03:00
ctx . RenderWithErr ( ctx . Tr ( "form.username_password_incorrect" ) , tplSignIn , & form )
log . Info ( "Failed authentication attempt for %s from %s: %v" , form . UserName , ctx . RemoteAddr ( ) , err )
} else if user_model . IsErrEmailAlreadyUsed ( err ) {
ctx . RenderWithErr ( ctx . Tr ( "form.email_been_used" ) , tplSignIn , & form )
log . Info ( "Failed authentication attempt for %s from %s: %v" , form . UserName , ctx . RemoteAddr ( ) , err )
} else if user_model . IsErrUserProhibitLogin ( err ) {
log . Info ( "Failed authentication attempt for %s from %s: %v" , form . UserName , ctx . RemoteAddr ( ) , err )
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 {
log . Info ( "Failed authentication attempt for %s from %s: %v" , form . UserName , ctx . RemoteAddr ( ) , err )
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
if skipper , ok := source . Cfg . ( auth_service . LocalTwoFASkipper ) ; ok && skipper . IsSkipLocalTwoFA ( ) {
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.
hasTOTPtwofa , err := auth . HasTwoFactorByUID ( u . ID )
if err != nil {
ctx . ServerError ( "UserSignIn" , err )
return
}
2022-01-14 18:03:31 +03:00
// Check if the user has webauthn registration
hasWebAuthnTwofa , err := auth . HasWebAuthnRegistrationsByUID ( u . ID )
2022-01-02 16:12:35 +03:00
if err != nil {
ctx . ServerError ( "UserSignIn" , err )
return
}
2022-01-14 18:03:31 +03:00
if ! hasTOTPtwofa && ! hasWebAuthnTwofa {
2022-01-02 16:12:35 +03:00
// No two factor auth configured we can sign in the user
handleSignIn ( ctx , u , form . Remember )
return
}
2022-11-10 14:43:06 +03:00
updates := map [ string ] interface { } {
// User will need to use 2FA TOTP or WebAuthn, save data
"twofaUid" : u . ID ,
"twofaRemember" : form . Remember ,
2022-01-02 16:12:35 +03:00
}
if hasTOTPtwofa {
2022-06-27 05:20:58 +03:00
// User will need to use WebAuthn, save data
2022-11-10 14:43:06 +03:00
updates [ "totpEnrolled" ] = u . ID
2022-01-02 16:12:35 +03:00
}
2022-11-10 14:43:06 +03:00
if err := updateSession ( ctx , nil , updates ) ; err != nil {
ctx . ServerError ( "UserSignIn: Unable to update session" , err )
2022-01-02 16:12:35 +03:00
return
}
2022-06-27 05:20:58 +03:00
// If we have WebAuthn redirect there first
2022-01-14 18:03:31 +03:00
if hasWebAuthnTwofa {
ctx . Redirect ( setting . AppSubURL + "/user/webauthn" )
2022-01-02 16:12:35 +03: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 ) {
redirect := handleSignInFull ( ctx , u , remember , true )
if ctx . Written ( ) {
return
}
ctx . Redirect ( redirect )
}
2022-01-20 20:46:10 +03:00
func handleSignInFull ( ctx * context . Context , u * user_model . User , remember , obeyRedirect bool ) string {
2022-01-02 16:12:35 +03:00
if remember {
days := 86400 * setting . LogInRememberDays
ctx . SetCookie ( setting . CookieUserName , u . Name , days )
ctx . SetSuperSecureCookie ( base . EncodeMD5 ( u . Rands + u . Passwd ) ,
setting . CookieRememberName , u . Name , days )
}
2022-11-10 14:43:06 +03:00
if err := updateSession ( ctx , [ ] string {
// Delete the openid, 2fa and linkaccount data
"openid_verified_uri" ,
"openid_signin_remember" ,
"openid_determined_email" ,
"openid_determined_username" ,
"twofaUid" ,
"twofaRemember" ,
"linkAccount" ,
} , map [ string ] interface { } {
"uid" : u . ID ,
"uname" : u . Name ,
} ) ; err != nil {
2022-01-02 16:12:35 +03:00
ctx . ServerError ( "RegenerateSession" , err )
return setting . AppSubURL + "/"
}
// Language setting of the user overwrites the one previously set
// If the user does not have a locale set, we save the current one.
if len ( u . Language ) == 0 {
u . Language = ctx . Locale . Language ( )
2022-03-22 18:22:54 +03:00
if err := user_model . UpdateUserCols ( ctx , u , "language" ) ; err != nil {
2022-01-02 16:12:35 +03:00
ctx . ServerError ( "UpdateUserCols Language" , fmt . Errorf ( "Error updating user language [user: %d, locale: %s]" , u . ID , u . Language ) )
return setting . AppSubURL + "/"
}
}
middleware . SetLocaleCookie ( ctx . Resp , u . Language , 0 )
if ctx . Locale . Language ( ) != u . Language {
ctx . Locale = middleware . Locale ( ctx . Resp , ctx . Req )
}
2022-04-08 08:21:05 +03:00
// Clear whatever CSRF cookie has right now, force to generate a new one
2022-01-02 16:12:35 +03:00
middleware . DeleteCSRFCookie ( ctx . Resp )
// Register last login
u . SetLastLogin ( )
2022-03-22 18:22:54 +03:00
if err := user_model . UpdateUserCols ( ctx , u , "last_login_unix" ) ; err != nil {
2022-01-02 16:12:35 +03:00
ctx . ServerError ( "UpdateUserCols" , err )
return setting . AppSubURL + "/"
}
if redirectTo := ctx . GetCookie ( "redirect_to" ) ; len ( redirectTo ) > 0 && ! utils . IsExternalURL ( redirectTo ) {
middleware . DeleteRedirectToCookie ( ctx . Resp )
if obeyRedirect {
ctx . RedirectToFirst ( redirectTo )
}
return redirectTo
}
if obeyRedirect {
ctx . Redirect ( setting . AppSubURL + "/" )
}
return setting . AppSubURL + "/"
}
func getUserName ( gothUser * goth . User ) string {
switch setting . OAuth2Client . Username {
case setting . OAuth2UsernameEmail :
return strings . Split ( gothUser . Email , "@" ) [ 0 ]
case setting . OAuth2UsernameNickname :
return gothUser . NickName
default : // OAuth2UsernameUserid
return gothUser . UserID
}
}
// HandleSignOut resets the session and sets the cookies
func HandleSignOut ( ctx * context . Context ) {
_ = ctx . Session . Flush ( )
_ = ctx . Session . Destroy ( ctx . Resp , ctx . Req )
ctx . DeleteCookie ( setting . CookieUserName )
ctx . DeleteCookie ( setting . CookieRememberName )
middleware . DeleteCSRFCookie ( ctx . Resp )
middleware . DeleteLocaleCookie ( ctx . Resp )
middleware . DeleteRedirectToCookie ( ctx . Resp )
}
// SignOut sign out from login status
func SignOut ( ctx * context . Context ) {
2022-03-22 10:03:22 +03:00
if ctx . Doer != nil {
eventsource . GetManager ( ) . SendMessageBlocking ( ctx . Doer . ID , & eventsource . Event {
2022-01-02 16:12:35 +03:00
Name : "logout" ,
Data : ctx . Session . ID ( ) ,
} )
}
HandleSignOut ( ctx )
ctx . Redirect ( setting . AppSubURL + "/" )
}
// SignUp render the register page
func SignUp ( ctx * context . Context ) {
ctx . Data [ "Title" ] = ctx . Tr ( "sign_up" )
ctx . Data [ "SignUpLink" ] = setting . AppSubURL + "/user/sign_up"
2022-11-23 00:13:18 +03:00
context . SetCaptchaData ( ctx )
2022-01-02 16:12:35 +03:00
ctx . Data [ "PageIsSignUp" ] = true
2022-01-20 20:46:10 +03:00
// Show Disabled Registration message if DisableRegistration or AllowOnlyExternalRegistration options are true
2022-01-02 16:12:35 +03:00
ctx . Data [ "DisableRegistration" ] = setting . Service . DisableRegistration || setting . Service . AllowOnlyExternalRegistration
ctx . HTML ( http . StatusOK , tplSignUp )
}
// SignUpPost response for sign up information submission
func SignUpPost ( ctx * context . Context ) {
form := web . GetForm ( ctx ) . ( * forms . RegisterForm )
ctx . Data [ "Title" ] = ctx . Tr ( "sign_up" )
ctx . Data [ "SignUpLink" ] = setting . AppSubURL + "/user/sign_up"
2022-11-23 00:13:18 +03:00
context . SetCaptchaData ( ctx )
2022-01-02 16:12:35 +03:00
ctx . Data [ "PageIsSignUp" ] = true
2022-01-20 20:46:10 +03:00
// Permission denied if DisableRegistration or AllowOnlyExternalRegistration options are true
2022-01-02 16:12:35 +03:00
if setting . Service . DisableRegistration || setting . Service . AllowOnlyExternalRegistration {
ctx . Error ( http . StatusForbidden )
return
}
if ctx . HasError ( ) {
ctx . HTML ( http . StatusOK , tplSignUp )
return
}
2022-11-23 00:13:18 +03:00
context . VerifyCaptcha ( ctx , tplSignUp , form )
if ctx . Written ( ) {
return
2022-01-02 16:12:35 +03:00
}
if ! form . IsEmailDomainAllowed ( ) {
ctx . RenderWithErr ( ctx . Tr ( "auth.email_domain_blacklisted" ) , tplSignUp , & form )
return
}
if form . Password != form . Retype {
ctx . Data [ "Err_Password" ] = true
ctx . RenderWithErr ( ctx . Tr ( "form.password_not_match" ) , tplSignUp , & form )
return
}
if len ( form . Password ) < setting . MinPasswordLength {
ctx . Data [ "Err_Password" ] = true
ctx . RenderWithErr ( ctx . Tr ( "auth.password_too_short" , setting . MinPasswordLength ) , tplSignUp , & form )
return
}
if ! password . IsComplexEnough ( form . Password ) {
ctx . Data [ "Err_Password" ] = true
ctx . RenderWithErr ( password . BuildComplexityError ( ctx ) , tplSignUp , & form )
return
}
pwned , err := password . IsPwned ( ctx , form . Password )
if pwned {
errMsg := ctx . Tr ( "auth.password_pwned" )
if err != nil {
log . Error ( err . Error ( ) )
errMsg = ctx . Tr ( "auth.password_pwned_err" )
}
ctx . Data [ "Err_Password" ] = true
ctx . RenderWithErr ( errMsg , tplSignUp , & form )
return
}
u := & user_model . User {
2022-04-29 22:38:11 +03:00
Name : form . UserName ,
Email : form . Email ,
Passwd : form . Password ,
2022-01-02 16:12:35 +03:00
}
2022-04-29 22:38:11 +03:00
if ! createAndHandleCreatedUser ( ctx , tplSignUp , form , u , nil , nil , false ) {
2022-01-02 16:12:35 +03:00
// error already handled
return
}
ctx . Flash . Success ( ctx . Tr ( "auth.sign_up_successful" ) )
handleSignIn ( ctx , u , false )
}
// createAndHandleCreatedUser calls createUserInContext and
// then handleUserCreated.
2022-04-29 22:38:11 +03:00
func createAndHandleCreatedUser ( ctx * context . Context , tpl base . TplName , form interface { } , u * user_model . User , overwrites * user_model . CreateUserOverwriteOptions , gothUser * goth . User , allowLink bool ) bool {
if ! createUserInContext ( ctx , tpl , form , u , overwrites , gothUser , allowLink ) {
2022-01-02 16:12:35 +03:00
return false
}
return handleUserCreated ( ctx , u , gothUser )
}
// createUserInContext creates a user and handles errors within a given context.
// Optionally a template can be specified.
2022-04-29 22:38:11 +03:00
func createUserInContext ( ctx * context . Context , tpl base . TplName , form interface { } , u * user_model . User , overwrites * user_model . CreateUserOverwriteOptions , gothUser * goth . User , allowLink bool ) ( ok bool ) {
if err := user_model . CreateUser ( u , overwrites ) ; err != nil {
2022-01-02 16:12:35 +03:00
if allowLink && ( user_model . IsErrUserAlreadyExist ( err ) || user_model . IsErrEmailAlreadyUsed ( err ) ) {
if setting . OAuth2Client . AccountLinking == setting . OAuth2AccountLinkingAuto {
var user * user_model . User
user = & user_model . User { Name : u . Name }
hasUser , err := user_model . GetUser ( user )
if ! hasUser || err != nil {
user = & user_model . User { Email : u . Email }
hasUser , err = user_model . GetUser ( user )
if ! hasUser || err != nil {
ctx . ServerError ( "UserLinkAccount" , err )
return
}
}
// TODO: probably we should respect 'remember' user's choice...
linkAccount ( ctx , user , * gothUser , true )
return // user is already created here, all redirects are handled
} else if setting . OAuth2Client . AccountLinking == setting . OAuth2AccountLinkingLogin {
showLinkingLogin ( ctx , * gothUser )
return // user will be created only after linking login
}
}
// handle error without template
if len ( tpl ) == 0 {
ctx . ServerError ( "CreateUser" , err )
return
}
// handle error with template
switch {
case user_model . IsErrUserAlreadyExist ( err ) :
ctx . Data [ "Err_UserName" ] = true
ctx . RenderWithErr ( ctx . Tr ( "form.username_been_taken" ) , tpl , form )
case user_model . IsErrEmailAlreadyUsed ( err ) :
ctx . Data [ "Err_Email" ] = true
ctx . RenderWithErr ( ctx . Tr ( "form.email_been_used" ) , tpl , form )
2022-03-14 20:39:54 +03:00
case user_model . IsErrEmailCharIsNotSupported ( err ) :
ctx . Data [ "Err_Email" ] = true
ctx . RenderWithErr ( ctx . Tr ( "form.email_invalid" ) , tpl , form )
2022-01-02 16:12:35 +03:00
case user_model . IsErrEmailInvalid ( err ) :
ctx . Data [ "Err_Email" ] = true
ctx . RenderWithErr ( ctx . Tr ( "form.email_invalid" ) , tpl , form )
case db . IsErrNameReserved ( err ) :
ctx . Data [ "Err_UserName" ] = true
ctx . RenderWithErr ( ctx . Tr ( "user.form.name_reserved" , err . ( db . ErrNameReserved ) . Name ) , tpl , form )
case db . IsErrNamePatternNotAllowed ( err ) :
ctx . Data [ "Err_UserName" ] = true
ctx . RenderWithErr ( ctx . Tr ( "user.form.name_pattern_not_allowed" , err . ( db . ErrNamePatternNotAllowed ) . Pattern ) , tpl , form )
case db . IsErrNameCharsNotAllowed ( err ) :
ctx . Data [ "Err_UserName" ] = true
ctx . RenderWithErr ( ctx . Tr ( "user.form.name_chars_not_allowed" , err . ( db . ErrNameCharsNotAllowed ) . Name ) , tpl , form )
default :
ctx . ServerError ( "CreateUser" , err )
}
return
}
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.
func handleUserCreated ( ctx * context . Context , u * user_model . User , gothUser * goth . User ) ( ok bool ) {
// Auto-set admin for the only user.
2022-05-02 16:35:45 +03:00
if user_model . CountUsers ( nil ) == 1 {
2022-01-02 16:12:35 +03:00
u . IsAdmin = true
u . IsActive = true
u . SetLastLogin ( )
2022-03-22 18:22:54 +03:00
if err := user_model . UpdateUserCols ( ctx , u , "is_admin" , "is_active" , "last_login_unix" ) ; err != nil {
2022-01-02 16:12:35 +03:00
ctx . ServerError ( "UpdateUser" , err )
return
}
}
// update external user information
if gothUser != nil {
if err := externalaccount . UpdateExternalUser ( u , * gothUser ) ; err != nil {
2022-10-19 22:07:21 +03:00
if ! errors . Is ( err , util . ErrNotExist ) {
log . Error ( "UpdateExternalUser failed: %v" , err )
}
2022-01-02 16:12:35 +03:00
}
}
// Send confirmation email
if ! u . IsActive && u . ID > 1 {
2022-03-18 12:57:07 +03:00
if setting . Service . RegisterManualConfirm {
ctx . Data [ "ManualActivationOnly" ] = true
ctx . HTML ( http . StatusOK , TplActivate )
return
}
2022-01-02 16:12:35 +03:00
mailer . SendActivateAccountMail ( ctx . Locale , u )
ctx . Data [ "IsSendRegisterMail" ] = true
ctx . Data [ "Email" ] = u . Email
2022-06-26 17:19:22 +03:00
ctx . Data [ "ActiveCodeLives" ] = timeutil . MinutesToFriendly ( setting . Service . ActiveCodeLives , ctx . Locale )
2022-01-02 16:12:35 +03:00
ctx . HTML ( http . StatusOK , TplActivate )
2022-05-21 17:29:49 +03:00
if setting . CacheService . Enabled {
if err := ctx . Cache . Put ( "MailResendLimit_" + u . LowerName , u . LowerName , 180 ) ; err != nil {
log . Error ( "Set cache(MailResendLimit) fail: %v" , err )
}
2022-01-02 16:12:35 +03:00
}
return
}
return true
}
// Activate render activate user page
func Activate ( ctx * context . Context ) {
code := ctx . FormString ( "code" )
if len ( code ) == 0 {
ctx . Data [ "IsActivatePage" ] = true
2022-03-22 10:03:22 +03:00
if ctx . Doer == nil || ctx . Doer . IsActive {
2022-01-02 16:12:35 +03:00
ctx . NotFound ( "invalid user" , nil )
return
}
// Resend confirmation email.
if setting . Service . RegisterEmailConfirm {
2022-05-21 17:29:49 +03:00
if setting . CacheService . Enabled && ctx . Cache . IsExist ( "MailResendLimit_" + ctx . Doer . LowerName ) {
2022-01-02 16:12:35 +03:00
ctx . Data [ "ResendLimited" ] = true
} else {
2022-06-26 17:19:22 +03:00
ctx . Data [ "ActiveCodeLives" ] = timeutil . MinutesToFriendly ( setting . Service . ActiveCodeLives , ctx . Locale )
2022-03-22 10:03:22 +03:00
mailer . SendActivateAccountMail ( ctx . Locale , ctx . Doer )
2022-01-02 16:12:35 +03:00
2022-05-21 17:29:49 +03:00
if setting . CacheService . Enabled {
if err := ctx . Cache . Put ( "MailResendLimit_" + ctx . Doer . LowerName , ctx . Doer . LowerName , 180 ) ; err != nil {
log . Error ( "Set cache(MailResendLimit) fail: %v" , err )
}
2022-01-02 16:12:35 +03:00
}
}
} else {
ctx . Data [ "ServiceNotEnabled" ] = true
}
ctx . HTML ( http . StatusOK , TplActivate )
return
}
user := user_model . VerifyUserActiveCode ( code )
// if code is wrong
if user == nil {
ctx . Data [ "IsActivateFailed" ] = true
ctx . HTML ( http . StatusOK , TplActivate )
return
}
// if account is local account, verify password
if user . LoginSource == 0 {
ctx . Data [ "Code" ] = code
ctx . Data [ "NeedsPassword" ] = true
ctx . HTML ( http . StatusOK , TplActivate )
return
}
handleAccountActivation ( ctx , user )
}
// ActivatePost handles account activation with password check
func ActivatePost ( ctx * context . Context ) {
code := ctx . FormString ( "code" )
if len ( code ) == 0 {
ctx . Redirect ( setting . AppSubURL + "/user/activate" )
return
}
user := user_model . VerifyUserActiveCode ( code )
// if code is wrong
if user == nil {
ctx . Data [ "IsActivateFailed" ] = true
ctx . HTML ( http . StatusOK , TplActivate )
return
}
// if account is local account, verify password
if user . LoginSource == 0 {
password := ctx . FormString ( "password" )
if len ( password ) == 0 {
ctx . Data [ "Code" ] = code
ctx . Data [ "NeedsPassword" ] = true
ctx . HTML ( http . StatusOK , TplActivate )
return
}
if ! user . ValidatePassword ( password ) {
ctx . Data [ "IsActivateFailed" ] = true
ctx . HTML ( http . StatusOK , TplActivate )
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 18:22:54 +03:00
if err := user_model . UpdateUserCols ( ctx , user , "is_active" , "rands" ) ; err != nil {
2022-01-02 16:12:35 +03:00
if user_model . IsErrUserNotExist ( err ) {
ctx . NotFound ( "UpdateUserCols" , err )
} else {
ctx . ServerError ( "UpdateUser" , err )
}
return
}
if err := user_model . ActivateUserEmail ( user . ID , user . Email , true ) ; err != nil {
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 )
2022-11-10 14:43:06 +03:00
if err := updateSession ( ctx , nil , map [ string ] interface { } {
"uid" : user . ID ,
"uname" : user . Name ,
} ) ; err != nil {
2022-01-02 16:12:35 +03: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
}
2022-11-09 19:42:06 +03:00
// Register last login
user . SetLastLogin ( )
if err := user_model . UpdateUserCols ( ctx , user , "last_login_unix" ) ; err != nil {
ctx . ServerError ( "UpdateUserCols" , err )
return
}
2022-01-02 16:12:35 +03:00
ctx . Flash . Success ( ctx . Tr ( "auth.account_activated" ) )
ctx . Redirect ( setting . AppSubURL + "/" )
}
// ActivateEmail render the activate email page
func ActivateEmail ( ctx * context . Context ) {
code := ctx . FormString ( "code" )
emailStr := ctx . FormString ( "email" )
// Verify code.
if email := user_model . VerifyActiveEmailCode ( code , emailStr ) ; email != nil {
if err := user_model . ActivateEmail ( email ) ; err != nil {
ctx . ServerError ( "ActivateEmail" , err )
}
log . Trace ( "Email activated: %s" , email . Email )
ctx . Flash . Success ( ctx . Tr ( "settings.add_email_success" ) )
2022-12-03 05:48:26 +03:00
if u , err := user_model . GetUserByID ( ctx , email . UID ) ; err != nil {
2022-01-02 16:12:35 +03:00
log . Warn ( "GetUserByID: %d" , email . UID )
2022-05-21 17:29:49 +03:00
} else if setting . CacheService . Enabled {
2022-01-02 16:12:35 +03: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 14:43:06 +03:00
func updateSession ( ctx * context . Context , deletes [ ] string , updates map [ string ] interface { } ) error {
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
}