2014-07-26 12:24:27 +08:00
// Copyright 2014 The Gogs Authors. All rights reserved.
2018-07-20 01:58:33 +08:00
// Copyright 2018 The Gitea Authors. All rights reserved.
2014-07-26 12:24:27 +08:00
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package user
import (
2017-01-16 10:14:29 +08:00
"errors"
2016-03-12 00:56:52 +08:00
"fmt"
2021-04-14 20:02:12 +08:00
"io"
"io/ioutil"
2017-02-25 22:57:06 +08:00
"net/http"
"strings"
2014-08-01 05:25:34 +08:00
2016-11-11 00:24:48 +08:00
"code.gitea.io/gitea/models"
2017-02-25 22:57:06 +08:00
"code.gitea.io/gitea/modules/auth/oauth2"
2016-11-11 00:24:48 +08:00
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
2020-05-08 05:49:00 +08:00
"code.gitea.io/gitea/modules/eventsource"
2020-10-03 11:37:53 +08:00
"code.gitea.io/gitea/modules/hcaptcha"
2016-11-11 00:24:48 +08:00
"code.gitea.io/gitea/modules/log"
2019-10-14 23:24:26 +08:00
"code.gitea.io/gitea/modules/password"
2018-07-05 12:13:05 +08:00
"code.gitea.io/gitea/modules/recaptcha"
2016-11-11 00:24:48 +08:00
"code.gitea.io/gitea/modules/setting"
2019-08-15 22:46:21 +08:00
"code.gitea.io/gitea/modules/timeutil"
2021-01-26 23:36:53 +08:00
"code.gitea.io/gitea/modules/web"
2021-03-07 16:12:43 +08:00
"code.gitea.io/gitea/modules/web/middleware"
2020-08-12 04:05:34 +08:00
"code.gitea.io/gitea/routers/utils"
2019-10-14 14:10:42 +08:00
"code.gitea.io/gitea/services/externalaccount"
2021-04-07 03:44:05 +08:00
"code.gitea.io/gitea/services/forms"
2019-09-24 13:02:49 +08:00
"code.gitea.io/gitea/services/mailer"
2017-02-25 22:57:06 +08:00
2017-02-22 15:14:37 +08:00
"github.com/markbates/goth"
2018-05-19 22:12:37 +08:00
"github.com/tstranex/u2f"
2014-07-26 12:24:27 +08:00
)
const (
2018-09-13 20:04:25 +08:00
// tplMustChangePassword template for updating a user's password
tplMustChangePassword = "user/auth/change_passwd"
2016-11-18 11:03:03 +08:00
// 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"
tplForgotPassword base . TplName = "user/auth/forgot_passwd"
tplResetPassword base . TplName = "user/auth/reset_passwd"
2017-01-16 10:14:29 +08:00
tplTwofa base . TplName = "user/auth/twofa"
tplTwofaScratch base . TplName = "user/auth/twofa_scratch"
2017-02-22 15:14:37 +08:00
tplLinkAccount base . TplName = "user/auth/link_account"
2018-05-19 22:12:37 +08:00
tplU2F base . TplName = "user/auth/u2f"
2014-07-26 12:24:27 +08:00
)
2016-03-12 00:56:52 +08:00
// AutoSignIn reads cookie and try to auto-login.
func AutoSignIn ( ctx * context . Context ) ( bool , error ) {
if ! models . 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 )
2021-03-07 16:12:43 +08:00
ctx . DeleteCookie ( setting . CookieUserName )
ctx . DeleteCookie ( setting . CookieRememberName )
2016-03-12 00:56:52 +08:00
}
} ( )
u , err := models . GetUserByName ( uname )
if err != nil {
if ! models . IsErrUserNotExist ( err ) {
return false , fmt . Errorf ( "GetUserByName: %v" , err )
}
return false , nil
}
2019-07-06 23:47:09 +08:00
if val , ok := ctx . GetSuperSecureCookie (
base . EncodeMD5 ( u . Rands + u . Passwd ) , setting . CookieRememberName ) ; ! ok || val != u . Name {
2016-03-12 00:56:52 +08:00
return false , nil
}
isSucceed = true
2020-05-17 20:43:29 +08:00
// Set session IDs
if err := ctx . Session . Set ( "uid" , u . ID ) ; err != nil {
2019-06-13 03:41:28 +08:00
return false , err
}
2020-05-17 20:43:29 +08:00
if err := ctx . Session . Set ( "uname" , u . Name ) ; err != nil {
return false , err
}
if err := ctx . Session . Release ( ) ; err != nil {
2019-06-13 03:41:28 +08:00
return false , err
}
2020-05-17 20:43:29 +08:00
2021-03-07 16:12:43 +08:00
middleware . DeleteCSRFCookie ( ctx . Resp )
2016-03-12 00:56:52 +08:00
return true , nil
}
2017-01-16 10:14:29 +08:00
func checkAutoLogin ( ctx * context . Context ) bool {
2014-07-26 12:24:27 +08:00
// Check auto-login.
2016-03-12 00:56:52 +08:00
isSucceed , err := AutoSignIn ( ctx )
2014-07-26 12:24:27 +08:00
if err != nil {
2018-01-11 05:34:17 +08:00
ctx . ServerError ( "AutoSignIn" , err )
2017-01-16 10:14:29 +08:00
return true
2014-07-26 12:24:27 +08:00
}
2016-08-28 06:07:02 +08:00
redirectTo := ctx . Query ( "redirect_to" )
if len ( redirectTo ) > 0 {
2021-03-07 16:12:43 +08:00
middleware . SetRedirectToCookie ( ctx . Resp , redirectTo )
2016-08-28 06:07:02 +08:00
} else {
2019-03-21 10:06:16 +08:00
redirectTo = ctx . GetCookie ( "redirect_to" )
2016-08-28 06:07:02 +08:00
}
2015-08-14 02:43:40 +08:00
if isSucceed {
2021-03-07 16:12:43 +08:00
middleware . DeleteRedirectToCookie ( ctx . Resp )
2018-03-16 05:13:34 +08:00
ctx . RedirectToFirst ( redirectTo , setting . AppSubURL + string ( setting . LandingPageURL ) )
2017-01-16 10:14:29 +08:00
return true
}
return false
}
// SignIn render sign in page
func SignIn ( ctx * context . Context ) {
2017-05-01 21:26:53 +08:00
ctx . Data [ "Title" ] = ctx . Tr ( "sign_in" )
2017-01-16 10:14:29 +08:00
// Check auto-login.
if checkAutoLogin ( ctx ) {
2014-07-26 12:24:27 +08:00
return
}
2017-05-01 21:26:53 +08:00
orderedOAuth2Names , oauth2Providers , err := models . GetActiveOAuth2Providers ( )
2017-02-22 15:14:37 +08:00
if err != nil {
2018-01-11 05:34:17 +08:00
ctx . ServerError ( "UserSignIn" , err )
2017-02-22 15:14:37 +08:00
return
}
2017-05-01 21:26:53 +08:00
ctx . Data [ "OrderedOAuth2Names" ] = orderedOAuth2Names
2017-02-22 15:14:37 +08:00
ctx . Data [ "OAuth2Providers" ] = oauth2Providers
2017-03-17 22:16:08 +08:00
ctx . Data [ "Title" ] = ctx . Tr ( "sign_in" )
2017-03-07 18:47:56 +08:00
ctx . Data [ "SignInLink" ] = setting . AppSubURL + "/user/login"
2017-03-17 22:16:08 +08:00
ctx . Data [ "PageIsSignIn" ] = true
ctx . Data [ "PageIsLogin" ] = true
2019-11-23 07:33:31 +08:00
ctx . Data [ "EnableSSPI" ] = models . IsSSPIEnabled ( )
2017-02-22 15:14:37 +08:00
2021-04-05 23:30:52 +08:00
ctx . HTML ( http . StatusOK , tplSignIn )
2014-07-26 12:24:27 +08:00
}
2016-11-18 11:03:03 +08:00
// SignInPost response for sign in request
2021-01-26 23:36:53 +08:00
func SignInPost ( ctx * context . Context ) {
2017-05-01 21:26:53 +08:00
ctx . Data [ "Title" ] = ctx . Tr ( "sign_in" )
orderedOAuth2Names , oauth2Providers , err := models . GetActiveOAuth2Providers ( )
2017-02-22 15:14:37 +08:00
if err != nil {
2018-01-11 05:34:17 +08:00
ctx . ServerError ( "UserSignIn" , err )
2017-02-22 15:14:37 +08:00
return
}
2017-05-01 21:26:53 +08:00
ctx . Data [ "OrderedOAuth2Names" ] = orderedOAuth2Names
2017-02-22 15:14:37 +08:00
ctx . Data [ "OAuth2Providers" ] = oauth2Providers
2017-03-07 18:47:56 +08:00
ctx . Data [ "Title" ] = ctx . Tr ( "sign_in" )
ctx . Data [ "SignInLink" ] = setting . AppSubURL + "/user/login"
ctx . Data [ "PageIsSignIn" ] = true
ctx . Data [ "PageIsLogin" ] = true
2019-11-23 07:33:31 +08:00
ctx . Data [ "EnableSSPI" ] = models . IsSSPIEnabled ( )
2017-02-22 15:14:37 +08:00
2014-07-26 12:24:27 +08:00
if ctx . HasError ( ) {
2021-04-05 23:30:52 +08:00
ctx . HTML ( http . StatusOK , tplSignIn )
2014-07-26 12:24:27 +08:00
return
}
2021-04-07 03:44:05 +08:00
form := web . GetForm ( ctx ) . ( * forms . SignInForm )
2014-07-26 12:24:27 +08:00
u , err := models . UserSignIn ( form . UserName , form . Password )
if err != nil {
2015-08-05 11:14:17 +08:00
if models . IsErrUserNotExist ( err ) {
2016-11-18 11:03:03 +08:00
ctx . RenderWithErr ( ctx . Tr ( "form.username_password_incorrect" ) , tplSignIn , & form )
2020-12-09 04:37:47 +08:00
log . Info ( "Failed authentication attempt for %s from %s: %v" , form . UserName , ctx . RemoteAddr ( ) , err )
2017-02-25 22:57:06 +08:00
} else if models . IsErrEmailAlreadyUsed ( err ) {
ctx . RenderWithErr ( ctx . Tr ( "form.email_been_used" ) , tplSignIn , & form )
2020-12-09 04:37:47 +08:00
log . Info ( "Failed authentication attempt for %s from %s: %v" , form . UserName , ctx . RemoteAddr ( ) , err )
2019-02-19 15:19:28 +08:00
} else if models . IsErrUserProhibitLogin ( err ) {
2020-12-09 04:37:47 +08:00
log . Info ( "Failed authentication attempt for %s from %s: %v" , form . UserName , ctx . RemoteAddr ( ) , err )
2019-02-19 15:19:28 +08:00
ctx . Data [ "Title" ] = ctx . Tr ( "auth.prohibit_login" )
2021-04-05 23:30:52 +08:00
ctx . HTML ( http . StatusOK , "user/auth/prohibit_login" )
2019-02-19 15:19:28 +08:00
} else if models . IsErrUserInactive ( err ) {
if setting . Service . RegisterEmailConfirm {
ctx . Data [ "Title" ] = ctx . Tr ( "auth.active_your_account" )
2021-04-05 23:30:52 +08:00
ctx . HTML ( http . StatusOK , TplActivate )
2019-02-19 15:19:28 +08:00
} else {
2020-12-09 04:37:47 +08:00
log . Info ( "Failed authentication attempt for %s from %s: %v" , form . UserName , ctx . RemoteAddr ( ) , err )
2019-02-19 15:19:28 +08:00
ctx . Data [ "Title" ] = ctx . Tr ( "auth.prohibit_login" )
2021-04-05 23:30:52 +08:00
ctx . HTML ( http . StatusOK , "user/auth/prohibit_login" )
2019-02-19 15:19:28 +08:00
}
2014-08-10 12:02:00 +08:00
} else {
2018-01-11 05:34:17 +08:00
ctx . ServerError ( "UserSignIn" , err )
2014-07-26 12:24:27 +08:00
}
return
}
2017-01-16 10:14:29 +08:00
// If this user is enrolled in 2FA, we can't sign the user in just yet.
// Instead, redirect them to the 2FA authentication page.
_ , err = models . GetTwoFactorByUID ( u . ID )
if err != nil {
if models . IsErrTwoFactorNotEnrolled ( err ) {
handleSignIn ( ctx , u , form . Remember )
} else {
2018-01-11 05:34:17 +08:00
ctx . ServerError ( "UserSignIn" , err )
2017-01-16 10:14:29 +08:00
}
return
}
// User needs to use 2FA, save data and redirect to 2FA page.
2020-05-17 20:43:29 +08:00
if err := ctx . Session . Set ( "twofaUid" , u . ID ) ; err != nil {
ctx . ServerError ( "UserSignIn: Unable to set twofaUid in session" , err )
2019-06-13 03:41:28 +08:00
return
}
2020-05-17 20:43:29 +08:00
if err := ctx . Session . Set ( "twofaRemember" , form . Remember ) ; err != nil {
ctx . ServerError ( "UserSignIn: Unable to set twofaRemember in session" , err )
return
}
if err := ctx . Session . Release ( ) ; err != nil {
ctx . ServerError ( "UserSignIn: Unable to save session" , err )
2019-06-13 03:41:28 +08:00
return
}
2018-05-19 22:12:37 +08:00
regs , err := models . GetU2FRegistrationsByUID ( u . ID )
if err == nil && len ( regs ) > 0 {
ctx . Redirect ( setting . AppSubURL + "/user/u2f" )
return
}
2017-01-16 10:14:29 +08:00
ctx . Redirect ( setting . AppSubURL + "/user/two_factor" )
}
// TwoFactor shows the user a two-factor authentication page.
func TwoFactor ( ctx * context . Context ) {
ctx . Data [ "Title" ] = ctx . Tr ( "twofa" )
// Check auto-login.
if checkAutoLogin ( ctx ) {
return
}
// Ensure user is in a 2FA session.
if ctx . Session . Get ( "twofaUid" ) == nil {
2018-01-11 05:34:17 +08:00
ctx . ServerError ( "UserSignIn" , errors . New ( "not in 2FA session" ) )
2017-01-16 10:14:29 +08:00
return
}
2021-04-05 23:30:52 +08:00
ctx . HTML ( http . StatusOK , tplTwofa )
2017-01-16 10:14:29 +08:00
}
// TwoFactorPost validates a user's two-factor authentication token.
2021-01-26 23:36:53 +08:00
func TwoFactorPost ( ctx * context . Context ) {
2021-04-07 03:44:05 +08:00
form := web . GetForm ( ctx ) . ( * forms . TwoFactorAuthForm )
2017-01-16 10:14:29 +08:00
ctx . Data [ "Title" ] = ctx . Tr ( "twofa" )
// Ensure user is in a 2FA session.
idSess := ctx . Session . Get ( "twofaUid" )
if idSess == nil {
2018-01-11 05:34:17 +08:00
ctx . ServerError ( "UserSignIn" , errors . New ( "not in 2FA session" ) )
2017-01-16 10:14:29 +08:00
return
}
id := idSess . ( int64 )
twofa , err := models . GetTwoFactorByUID ( id )
if err != nil {
2018-01-11 05:34:17 +08:00
ctx . ServerError ( "UserSignIn" , err )
2017-01-16 10:14:29 +08:00
return
}
// Validate the passcode with the stored TOTP secret.
ok , err := twofa . ValidateTOTP ( form . Passcode )
if err != nil {
2018-01-11 05:34:17 +08:00
ctx . ServerError ( "UserSignIn" , err )
2017-01-16 10:14:29 +08:00
return
}
2018-05-02 23:02:02 +08:00
if ok && twofa . LastUsedPasscode != form . Passcode {
2017-01-16 10:14:29 +08:00
remember := ctx . Session . Get ( "twofaRemember" ) . ( bool )
u , err := models . GetUserByID ( id )
if err != nil {
2018-01-11 05:34:17 +08:00
ctx . ServerError ( "UserSignIn" , err )
2017-01-16 10:14:29 +08:00
return
}
2017-02-27 18:10:26 +08:00
if ctx . Session . Get ( "linkAccount" ) != nil {
gothUser := ctx . Session . Get ( "linkAccountGothUser" )
if gothUser == nil {
2018-01-11 05:34:17 +08:00
ctx . ServerError ( "UserSignIn" , errors . New ( "not in LinkAccount session" ) )
2017-02-27 18:10:26 +08:00
return
}
2019-10-14 14:10:42 +08:00
err = externalaccount . LinkAccountToUser ( u , gothUser . ( goth . User ) )
2017-02-27 18:10:26 +08:00
if err != nil {
2018-01-11 05:34:17 +08:00
ctx . ServerError ( "UserSignIn" , err )
2017-02-27 18:10:26 +08:00
return
}
}
2018-05-02 23:02:02 +08:00
twofa . LastUsedPasscode = form . Passcode
if err = models . UpdateTwoFactor ( twofa ) ; err != nil {
ctx . ServerError ( "UserSignIn" , err )
return
}
2017-01-16 10:14:29 +08:00
handleSignIn ( ctx , u , remember )
return
}
2021-04-07 03:44:05 +08:00
ctx . RenderWithErr ( ctx . Tr ( "auth.twofa_passcode_incorrect" ) , tplTwofa , forms . TwoFactorAuthForm { } )
2017-01-16 10:14:29 +08:00
}
// TwoFactorScratch shows the scratch code form for two-factor authentication.
func TwoFactorScratch ( ctx * context . Context ) {
ctx . Data [ "Title" ] = ctx . Tr ( "twofa_scratch" )
// Check auto-login.
if checkAutoLogin ( ctx ) {
return
}
// Ensure user is in a 2FA session.
if ctx . Session . Get ( "twofaUid" ) == nil {
2018-01-11 05:34:17 +08:00
ctx . ServerError ( "UserSignIn" , errors . New ( "not in 2FA session" ) )
2017-01-16 10:14:29 +08:00
return
}
2021-04-05 23:30:52 +08:00
ctx . HTML ( http . StatusOK , tplTwofaScratch )
2017-01-16 10:14:29 +08:00
}
// TwoFactorScratchPost validates and invalidates a user's two-factor scratch token.
2021-01-26 23:36:53 +08:00
func TwoFactorScratchPost ( ctx * context . Context ) {
2021-04-07 03:44:05 +08:00
form := web . GetForm ( ctx ) . ( * forms . TwoFactorScratchAuthForm )
2017-01-16 10:14:29 +08:00
ctx . Data [ "Title" ] = ctx . Tr ( "twofa_scratch" )
// Ensure user is in a 2FA session.
idSess := ctx . Session . Get ( "twofaUid" )
if idSess == nil {
2018-01-11 05:34:17 +08:00
ctx . ServerError ( "UserSignIn" , errors . New ( "not in 2FA session" ) )
2017-01-16 10:14:29 +08:00
return
}
id := idSess . ( int64 )
twofa , err := models . GetTwoFactorByUID ( id )
if err != nil {
2018-01-11 05:34:17 +08:00
ctx . ServerError ( "UserSignIn" , err )
2017-01-16 10:14:29 +08:00
return
}
// Validate the passcode with the stored TOTP secret.
if twofa . VerifyScratchToken ( form . Token ) {
// Invalidate the scratch token.
2018-07-27 20:54:50 +08:00
_ , err = twofa . GenerateScratchToken ( )
if err != nil {
ctx . ServerError ( "UserSignIn" , err )
return
}
2017-01-16 10:14:29 +08:00
if err = models . UpdateTwoFactor ( twofa ) ; err != nil {
2018-01-11 05:34:17 +08:00
ctx . ServerError ( "UserSignIn" , err )
2017-01-16 10:14:29 +08:00
return
}
remember := ctx . Session . Get ( "twofaRemember" ) . ( bool )
u , err := models . GetUserByID ( id )
if err != nil {
2018-01-11 05:34:17 +08:00
ctx . ServerError ( "UserSignIn" , err )
2017-01-16 10:14:29 +08:00
return
}
handleSignInFull ( ctx , u , remember , false )
ctx . Flash . Info ( ctx . Tr ( "auth.twofa_scratch_used" ) )
2018-07-20 01:58:33 +08:00
ctx . Redirect ( setting . AppSubURL + "/user/settings/security" )
2017-01-16 10:14:29 +08:00
return
}
2021-04-07 03:44:05 +08:00
ctx . RenderWithErr ( ctx . Tr ( "auth.twofa_scratch_token_incorrect" ) , tplTwofaScratch , forms . TwoFactorScratchAuthForm { } )
2017-01-16 10:14:29 +08:00
}
2018-05-19 22:12:37 +08:00
// U2F shows the U2F login page
func U2F ( ctx * context . Context ) {
ctx . Data [ "Title" ] = ctx . Tr ( "twofa" )
ctx . Data [ "RequireU2F" ] = true
// Check auto-login.
if checkAutoLogin ( ctx ) {
return
}
// Ensure user is in a 2FA session.
if ctx . Session . Get ( "twofaUid" ) == nil {
ctx . ServerError ( "UserSignIn" , errors . New ( "not in U2F session" ) )
return
}
2021-04-05 23:30:52 +08:00
ctx . HTML ( http . StatusOK , tplU2F )
2018-05-19 22:12:37 +08:00
}
// U2FChallenge submits a sign challenge to the browser
func U2FChallenge ( ctx * context . Context ) {
// Ensure user is in a U2F session.
idSess := ctx . Session . Get ( "twofaUid" )
if idSess == nil {
ctx . ServerError ( "UserSignIn" , errors . New ( "not in U2F session" ) )
return
}
id := idSess . ( int64 )
regs , err := models . GetU2FRegistrationsByUID ( id )
if err != nil {
ctx . ServerError ( "UserSignIn" , err )
return
}
if len ( regs ) == 0 {
ctx . ServerError ( "UserSignIn" , errors . New ( "no device registered" ) )
return
}
challenge , err := u2f . NewChallenge ( setting . U2F . AppID , setting . U2F . TrustedFacets )
2019-06-13 03:41:28 +08:00
if err != nil {
ctx . ServerError ( "u2f.NewChallenge" , err )
return
}
2020-05-17 20:43:29 +08:00
if err := ctx . Session . Set ( "u2fChallenge" , challenge ) ; err != nil {
ctx . ServerError ( "UserSignIn: unable to set u2fChallenge in session" , err )
2018-05-19 22:12:37 +08:00
return
}
2020-05-17 20:43:29 +08:00
if err := ctx . Session . Release ( ) ; err != nil {
ctx . ServerError ( "UserSignIn: unable to store session" , err )
}
2021-04-05 23:30:52 +08:00
ctx . JSON ( http . StatusOK , challenge . SignRequest ( regs . ToRegistrations ( ) ) )
2018-05-19 22:12:37 +08:00
}
// U2FSign authenticates the user by signResp
2021-01-26 23:36:53 +08:00
func U2FSign ( ctx * context . Context ) {
signResp := web . GetForm ( ctx ) . ( * u2f . SignResponse )
2018-05-19 22:12:37 +08:00
challSess := ctx . Session . Get ( "u2fChallenge" )
idSess := ctx . Session . Get ( "twofaUid" )
if challSess == nil || idSess == nil {
ctx . ServerError ( "UserSignIn" , errors . New ( "not in U2F session" ) )
return
}
challenge := challSess . ( * u2f . Challenge )
id := idSess . ( int64 )
regs , err := models . GetU2FRegistrationsByUID ( id )
if err != nil {
ctx . ServerError ( "UserSignIn" , err )
return
}
for _ , reg := range regs {
r , err := reg . Parse ( )
if err != nil {
2019-04-02 15:48:31 +08:00
log . Fatal ( "parsing u2f registration: %v" , err )
2018-05-19 22:12:37 +08:00
continue
}
2021-01-26 23:36:53 +08:00
newCounter , authErr := r . Authenticate ( * signResp , * challenge , reg . Counter )
2018-05-19 22:12:37 +08:00
if authErr == nil {
reg . Counter = newCounter
user , err := models . GetUserByID ( id )
if err != nil {
ctx . ServerError ( "UserSignIn" , err )
return
}
remember := ctx . Session . Get ( "twofaRemember" ) . ( bool )
if err := reg . UpdateCounter ( ) ; err != nil {
ctx . ServerError ( "UserSignIn" , err )
return
}
if ctx . Session . Get ( "linkAccount" ) != nil {
gothUser := ctx . Session . Get ( "linkAccountGothUser" )
if gothUser == nil {
ctx . ServerError ( "UserSignIn" , errors . New ( "not in LinkAccount session" ) )
return
}
2019-10-14 14:10:42 +08:00
err = externalaccount . LinkAccountToUser ( user , gothUser . ( goth . User ) )
2018-05-19 22:12:37 +08:00
if err != nil {
ctx . ServerError ( "UserSignIn" , err )
return
}
}
redirect := handleSignInFull ( ctx , user , remember , false )
if redirect == "" {
redirect = setting . AppSubURL + "/"
}
ctx . PlainText ( 200 , [ ] byte ( redirect ) )
return
}
}
2021-04-05 23:30:52 +08:00
ctx . Error ( http . StatusUnauthorized )
2018-05-19 22:12:37 +08:00
}
2017-01-16 10:14:29 +08:00
// This handles the final part of the sign-in process of the user.
func handleSignIn ( ctx * context . Context , u * models . User , remember bool ) {
handleSignInFull ( ctx , u , remember , true )
}
2018-05-19 22:12:37 +08:00
func handleSignInFull ( ctx * context . Context , u * models . User , remember bool , obeyRedirect bool ) string {
2017-01-16 10:14:29 +08:00
if remember {
2014-07-26 12:24:27 +08:00
days := 86400 * setting . LogInRememberDays
2021-03-07 16:12:43 +08:00
ctx . SetCookie ( setting . CookieUserName , u . Name , days )
2017-02-25 22:57:06 +08:00
ctx . SetSuperSecureCookie ( base . EncodeMD5 ( u . Rands + u . Passwd ) ,
2021-03-07 16:12:43 +08:00
setting . CookieRememberName , u . Name , days )
2014-07-26 12:24:27 +08:00
}
2019-06-13 03:41:28 +08:00
_ = ctx . Session . Delete ( "openid_verified_uri" )
_ = ctx . Session . Delete ( "openid_signin_remember" )
_ = ctx . Session . Delete ( "openid_determined_email" )
_ = ctx . Session . Delete ( "openid_determined_username" )
_ = ctx . Session . Delete ( "twofaUid" )
_ = ctx . Session . Delete ( "twofaRemember" )
_ = ctx . Session . Delete ( "u2fChallenge" )
_ = ctx . Session . Delete ( "linkAccount" )
2020-05-17 20:43:29 +08:00
if err := ctx . Session . Set ( "uid" , u . ID ) ; err != nil {
log . Error ( "Error setting uid %d in session: %v" , u . ID , err )
2019-06-13 03:41:28 +08:00
}
2020-05-17 20:43:29 +08:00
if err := ctx . Session . Set ( "uname" , u . Name ) ; err != nil {
log . Error ( "Error setting uname %s session: %v" , u . Name , err )
}
if err := ctx . Session . Release ( ) ; err != nil {
log . Error ( "Unable to store session: %v" , err )
2019-06-13 03:41:28 +08:00
}
2016-03-13 09:56:03 +08:00
2018-05-05 08:28:30 +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.
if len ( u . Language ) == 0 {
u . Language = ctx . Locale . Language ( )
if err := models . UpdateUserCols ( u , "language" ) ; err != nil {
2019-04-02 15:48:31 +08:00
log . Error ( fmt . Sprintf ( "Error updating user language [user: %d, locale: %s]" , u . ID , u . Language ) )
2018-05-19 22:12:37 +08:00
return setting . AppSubURL + "/"
2018-05-05 08:28:30 +08:00
}
}
2021-03-07 16:12:43 +08:00
middleware . SetLocaleCookie ( ctx . Resp , u . Language , 0 )
2018-05-05 08:28:30 +08:00
2016-03-13 09:56:03 +08:00
// Clear whatever CSRF has right now, force to generate a new one
2021-03-07 16:12:43 +08:00
middleware . DeleteCSRFCookie ( ctx . Resp )
2016-03-13 09:56:03 +08:00
2016-11-09 18:53:45 +08:00
// Register last login
u . SetLastLogin ( )
2017-08-12 22:18:44 +08:00
if err := models . UpdateUserCols ( u , "last_login_unix" ) ; err != nil {
2018-01-11 05:34:17 +08:00
ctx . ServerError ( "UpdateUserCols" , err )
2018-05-19 22:12:37 +08:00
return setting . AppSubURL + "/"
2016-11-09 18:53:45 +08:00
}
2020-08-12 04:05:34 +08:00
if redirectTo := ctx . GetCookie ( "redirect_to" ) ; len ( redirectTo ) > 0 && ! utils . IsExternalURL ( redirectTo ) {
2021-03-07 16:12:43 +08:00
middleware . DeleteRedirectToCookie ( ctx . Resp )
2017-01-16 10:14:29 +08:00
if obeyRedirect {
2018-03-16 05:13:34 +08:00
ctx . RedirectToFirst ( redirectTo )
2017-01-16 10:14:29 +08:00
}
2018-05-19 22:12:37 +08:00
return redirectTo
2014-07-26 12:24:27 +08:00
}
2017-01-16 10:14:29 +08:00
if obeyRedirect {
ctx . Redirect ( setting . AppSubURL + "/" )
}
2018-05-19 22:12:37 +08:00
return setting . AppSubURL + "/"
2014-07-26 12:24:27 +08:00
}
2017-02-22 15:14:37 +08:00
// SignInOAuth handles the OAuth2 login buttons
func SignInOAuth ( ctx * context . Context ) {
provider := ctx . Params ( ":provider" )
loginSource , err := models . GetActiveOAuth2LoginSourceByName ( provider )
if err != nil {
2018-01-11 05:34:17 +08:00
ctx . ServerError ( "SignIn" , err )
2017-02-22 15:14:37 +08:00
return
}
// try to do a direct callback flow, so we don't authenticate the user again but use the valid accesstoken to get the user
2021-01-26 23:36:53 +08:00
user , gothUser , err := oAuth2UserLoginCallback ( loginSource , ctx . Req , ctx . Resp )
2017-02-22 15:14:37 +08:00
if err == nil && user != nil {
// we got the user without going through the whole OAuth2 authentication flow again
2021-04-14 20:02:12 +08:00
handleOAuth2SignIn ( ctx , user , gothUser )
2017-02-22 15:14:37 +08:00
return
}
2021-01-26 23:36:53 +08:00
if err = oauth2 . Auth ( loginSource . Name , ctx . Req , ctx . Resp ) ; err != nil {
2020-12-25 03:47:17 +08:00
if strings . Contains ( err . Error ( ) , "no provider for " ) {
if err = models . ResetOAuth2 ( ) ; err != nil {
ctx . ServerError ( "SignIn" , err )
return
}
2021-01-26 23:36:53 +08:00
if err = oauth2 . Auth ( loginSource . Name , ctx . Req , ctx . Resp ) ; err != nil {
2020-12-25 03:47:17 +08:00
ctx . ServerError ( "SignIn" , err )
}
return
}
2018-01-11 05:34:17 +08:00
ctx . ServerError ( "SignIn" , err )
2017-02-22 15:14:37 +08:00
}
// redirect is done in oauth2.Auth
}
// SignInOAuthCallback handles the callback from the given provider
func SignInOAuthCallback ( ctx * context . Context ) {
provider := ctx . Params ( ":provider" )
// first look if the provider is still active
loginSource , err := models . GetActiveOAuth2LoginSourceByName ( provider )
if err != nil {
2018-01-11 05:34:17 +08:00
ctx . ServerError ( "SignIn" , err )
2017-02-22 15:14:37 +08:00
return
}
if loginSource == nil {
2018-01-11 05:34:17 +08:00
ctx . ServerError ( "SignIn" , errors . New ( "No valid provider found, check configured callback url in provider" ) )
2017-02-22 15:14:37 +08:00
return
}
2021-01-26 23:36:53 +08:00
u , gothUser , err := oAuth2UserLoginCallback ( loginSource , ctx . Req , ctx . Resp )
2017-02-22 15:14:37 +08:00
if err != nil {
2018-01-11 05:34:17 +08:00
ctx . ServerError ( "UserSignIn" , err )
2017-02-22 15:14:37 +08:00
return
}
if u == nil {
2021-05-09 22:13:35 +08:00
if ! ( setting . Service . DisableRegistration || setting . Service . AllowOnlyInternalRegistration ) && setting . OAuth2Client . EnableAutoRegistration {
2021-04-14 20:02:12 +08:00
// create new user with details from oauth2 provider
var missingFields [ ] string
if gothUser . UserID == "" {
missingFields = append ( missingFields , "sub" )
}
if gothUser . Email == "" {
missingFields = append ( missingFields , "email" )
}
if setting . OAuth2Client . Username == setting . OAuth2UsernameNickname && gothUser . NickName == "" {
missingFields = append ( missingFields , "nickname" )
}
if len ( missingFields ) > 0 {
log . Error ( "OAuth2 Provider %s returned empty or missing fields: %s" , loginSource . Name , missingFields )
if loginSource . IsOAuth2 ( ) && loginSource . OAuth2 ( ) . Provider == "openidConnect" {
log . Error ( "You may need to change the 'OPENID_CONNECT_SCOPES' setting to request all required fields" )
}
err = fmt . Errorf ( "OAuth2 Provider %s returned empty or missing fields: %s" , loginSource . Name , missingFields )
ctx . ServerError ( "CreateUser" , err )
return
}
u = & models . User {
Name : getUserName ( & gothUser ) ,
FullName : gothUser . Name ,
Email : gothUser . Email ,
IsActive : ! setting . OAuth2Client . RegisterEmailConfirm ,
LoginType : models . LoginOAuth2 ,
LoginSource : loginSource . ID ,
LoginName : gothUser . UserID ,
}
if ! createAndHandleCreatedUser ( ctx , base . TplName ( "" ) , nil , u , & gothUser , setting . OAuth2Client . AccountLinking != setting . OAuth2AccountLinkingDisabled ) {
// error already handled
return
}
} else {
// no existing user is found, request attach or new account
showLinkingLogin ( ctx , gothUser )
return
2020-05-17 20:43:29 +08:00
}
2021-04-14 20:02:12 +08:00
}
handleOAuth2SignIn ( ctx , u , gothUser )
}
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
}
}
func showLinkingLogin ( ctx * context . Context , gothUser goth . User ) {
if err := ctx . Session . Set ( "linkAccountGothUser" , gothUser ) ; err != nil {
log . Error ( "Error setting linkAccountGothUser in session: %v" , err )
}
if err := ctx . Session . Release ( ) ; err != nil {
log . Error ( "Error storing session: %v" , err )
}
ctx . Redirect ( setting . AppSubURL + "/user/link_account" )
}
func updateAvatarIfNeed ( url string , u * models . User ) {
if setting . OAuth2Client . UpdateAvatar && len ( url ) > 0 {
resp , err := http . Get ( url )
if err == nil {
defer func ( ) {
_ = resp . Body . Close ( )
} ( )
}
// ignore any error
if err == nil && resp . StatusCode == http . StatusOK {
data , err := ioutil . ReadAll ( io . LimitReader ( resp . Body , setting . Avatar . MaxFileSize + 1 ) )
if err == nil && int64 ( len ( data ) ) <= setting . Avatar . MaxFileSize {
_ = u . UploadAvatar ( data )
}
2019-06-13 03:41:28 +08:00
}
2017-02-22 15:14:37 +08:00
}
2021-04-14 20:02:12 +08:00
}
func handleOAuth2SignIn ( ctx * context . Context , u * models . User , gothUser goth . User ) {
updateAvatarIfNeed ( gothUser . AvatarURL , u )
2017-02-22 15:14:37 +08:00
// If this user is enrolled in 2FA, we can't sign the user in just yet.
// Instead, redirect them to the 2FA authentication page.
2021-04-14 20:02:12 +08:00
_ , err := models . GetTwoFactorByUID ( u . ID )
2017-02-22 15:14:37 +08:00
if err != nil {
2019-10-14 14:10:42 +08:00
if ! models . IsErrTwoFactorNotEnrolled ( err ) {
ctx . ServerError ( "UserSignIn" , err )
return
}
2017-02-22 15:14:37 +08:00
2020-05-17 20:43:29 +08:00
if err := ctx . Session . Set ( "uid" , u . ID ) ; err != nil {
log . Error ( "Error setting uid in session: %v" , err )
2019-10-14 14:10:42 +08:00
}
2020-05-17 20:43:29 +08:00
if err := ctx . Session . Set ( "uname" , u . Name ) ; err != nil {
log . Error ( "Error setting uname in session: %v" , err )
}
if err := ctx . Session . Release ( ) ; err != nil {
log . Error ( "Error storing session: %v" , err )
2019-10-14 14:10:42 +08:00
}
2017-02-22 15:14:37 +08:00
2019-10-14 14:10:42 +08:00
// Clear whatever CSRF has right now, force to generate a new one
2021-03-07 16:12:43 +08:00
middleware . DeleteCSRFCookie ( ctx . Resp )
2017-02-22 15:14:37 +08:00
2019-10-14 14:10:42 +08:00
// Register last login
u . SetLastLogin ( )
if err := models . UpdateUserCols ( u , "last_login_unix" ) ; err != nil {
ctx . ServerError ( "UpdateUserCols" , err )
return
}
2017-02-22 15:14:37 +08:00
2019-10-14 14:10:42 +08:00
// update external user information
if err := models . UpdateExternalUser ( u , gothUser ) ; err != nil {
log . Error ( "UpdateExternalUser failed: %v" , err )
}
if redirectTo := ctx . GetCookie ( "redirect_to" ) ; len ( redirectTo ) > 0 {
2021-03-07 16:12:43 +08:00
middleware . DeleteRedirectToCookie ( ctx . Resp )
2019-10-14 14:10:42 +08:00
ctx . RedirectToFirst ( redirectTo )
return
2017-02-22 15:14:37 +08:00
}
2019-10-14 14:10:42 +08:00
ctx . Redirect ( setting . AppSubURL + "/" )
2017-02-22 15:14:37 +08:00
return
}
// User needs to use 2FA, save data and redirect to 2FA page.
2020-05-17 20:43:29 +08:00
if err := ctx . Session . Set ( "twofaUid" , u . ID ) ; err != nil {
log . Error ( "Error setting twofaUid in session: %v" , err )
2019-06-13 03:41:28 +08:00
}
2020-05-17 20:43:29 +08:00
if err := ctx . Session . Set ( "twofaRemember" , false ) ; err != nil {
log . Error ( "Error setting twofaRemember in session: %v" , err )
}
if err := ctx . Session . Release ( ) ; err != nil {
log . Error ( "Error storing session: %v" , err )
2019-06-13 03:41:28 +08:00
}
2018-05-19 22:12:37 +08:00
// If U2F is enrolled -> Redirect to U2F instead
regs , err := models . GetU2FRegistrationsByUID ( u . ID )
if err == nil && len ( regs ) > 0 {
ctx . Redirect ( setting . AppSubURL + "/user/u2f" )
return
}
2017-02-22 15:14:37 +08:00
ctx . Redirect ( setting . AppSubURL + "/user/two_factor" )
}
// OAuth2UserLoginCallback attempts to handle the callback from the OAuth2 provider and if successful
// login the user
func oAuth2UserLoginCallback ( loginSource * models . LoginSource , request * http . Request , response http . ResponseWriter ) ( * models . User , goth . User , error ) {
gothUser , err := oauth2 . ProviderCallback ( loginSource . Name , request , response )
if err != nil {
2020-04-23 06:47:23 +08:00
if err . Error ( ) == "securecookie: the value is too long" {
log . Error ( "OAuth2 Provider %s returned too long a token. Current max: %d. Either increase the [OAuth2] MAX_TOKEN_LENGTH or reduce the information returned from the OAuth2 provider" , loginSource . Name , setting . OAuth2 . MaxTokenLength )
err = fmt . Errorf ( "OAuth2 Provider %s returned too long a token. Current max: %d. Either increase the [OAuth2] MAX_TOKEN_LENGTH or reduce the information returned from the OAuth2 provider" , loginSource . Name , setting . OAuth2 . MaxTokenLength )
}
2017-02-22 15:14:37 +08:00
return nil , goth . User { } , err
}
user := & models . User {
LoginName : gothUser . UserID ,
LoginType : models . LoginOAuth2 ,
LoginSource : loginSource . ID ,
}
hasUser , err := models . GetUser ( user )
if err != nil {
return nil , goth . User { } , err
}
if hasUser {
2019-10-14 14:10:42 +08:00
return user , gothUser , nil
2017-02-22 15:14:37 +08:00
}
// search in external linked users
externalLoginUser := & models . ExternalLoginUser {
ExternalID : gothUser . UserID ,
LoginSourceID : loginSource . ID ,
}
hasUser , err = models . GetExternalLogin ( externalLoginUser )
if err != nil {
return nil , goth . User { } , err
}
if hasUser {
user , err = models . GetUserByID ( externalLoginUser . UserID )
2019-10-14 14:10:42 +08:00
return user , gothUser , err
2017-02-22 15:14:37 +08:00
}
// no user found to login
return nil , gothUser , nil
}
// LinkAccount shows the page where the user can decide to login or create a new account
func LinkAccount ( ctx * context . Context ) {
2019-11-09 17:42:34 +08:00
ctx . Data [ "DisablePassword" ] = ! setting . Service . RequireExternalRegistrationPassword || setting . Service . AllowOnlyExternalRegistration
2017-02-22 15:14:37 +08:00
ctx . Data [ "Title" ] = ctx . Tr ( "link_account" )
ctx . Data [ "LinkAccountMode" ] = true
2019-07-07 03:48:02 +08:00
ctx . Data [ "EnableCaptcha" ] = setting . Service . EnableCaptcha && setting . Service . RequireExternalRegistrationCaptcha
2021-01-27 22:56:54 +08:00
ctx . Data [ "Captcha" ] = context . GetImageCaptcha ( )
2018-07-05 12:13:05 +08:00
ctx . Data [ "CaptchaType" ] = setting . Service . CaptchaType
2019-05-02 21:09:39 +08:00
ctx . Data [ "RecaptchaURL" ] = setting . Service . RecaptchaURL
2018-07-05 12:13:05 +08:00
ctx . Data [ "RecaptchaSitekey" ] = setting . Service . RecaptchaSitekey
2021-03-01 06:04:15 +08:00
ctx . Data [ "HcaptchaSitekey" ] = setting . Service . HcaptchaSitekey
2017-02-22 15:14:37 +08:00
ctx . Data [ "DisableRegistration" ] = setting . Service . DisableRegistration
2021-05-09 22:13:35 +08:00
ctx . Data [ "AllowOnlyInternalRegistration" ] = setting . Service . AllowOnlyInternalRegistration
2017-02-22 15:14:37 +08:00
ctx . Data [ "ShowRegistrationButton" ] = false
// use this to set the right link into the signIn and signUp templates in the link_account template
ctx . Data [ "SignInLink" ] = setting . AppSubURL + "/user/link_account_signin"
ctx . Data [ "SignUpLink" ] = setting . AppSubURL + "/user/link_account_signup"
gothUser := ctx . Session . Get ( "linkAccountGothUser" )
if gothUser == nil {
2018-01-11 05:34:17 +08:00
ctx . ServerError ( "UserSignIn" , errors . New ( "not in LinkAccount session" ) )
2017-02-22 15:14:37 +08:00
return
}
2021-04-14 20:02:12 +08:00
gu , _ := gothUser . ( goth . User )
uname := getUserName ( & gu )
email := gu . Email
2018-10-29 06:46:16 +08:00
ctx . Data [ "user_name" ] = uname
ctx . Data [ "email" ] = email
if len ( email ) != 0 {
u , err := models . GetUserByEmail ( email )
if err != nil && ! models . IsErrUserNotExist ( err ) {
ctx . ServerError ( "UserSignIn" , err )
return
}
if u != nil {
ctx . Data [ "user_exists" ] = true
}
} else if len ( uname ) != 0 {
u , err := models . GetUserByName ( uname )
if err != nil && ! models . IsErrUserNotExist ( err ) {
ctx . ServerError ( "UserSignIn" , err )
return
}
if u != nil {
ctx . Data [ "user_exists" ] = true
}
}
2017-02-22 15:14:37 +08:00
2021-04-05 23:30:52 +08:00
ctx . HTML ( http . StatusOK , tplLinkAccount )
2017-02-22 15:14:37 +08:00
}
// LinkAccountPostSignIn handle the coupling of external account with another account using signIn
2021-01-26 23:36:53 +08:00
func LinkAccountPostSignIn ( ctx * context . Context ) {
2021-04-07 03:44:05 +08:00
signInForm := web . GetForm ( ctx ) . ( * forms . SignInForm )
2019-11-09 17:42:34 +08:00
ctx . Data [ "DisablePassword" ] = ! setting . Service . RequireExternalRegistrationPassword || setting . Service . AllowOnlyExternalRegistration
2017-02-22 15:14:37 +08:00
ctx . Data [ "Title" ] = ctx . Tr ( "link_account" )
ctx . Data [ "LinkAccountMode" ] = true
ctx . Data [ "LinkAccountModeSignIn" ] = true
2019-07-07 03:48:02 +08:00
ctx . Data [ "EnableCaptcha" ] = setting . Service . EnableCaptcha && setting . Service . RequireExternalRegistrationCaptcha
2019-05-02 21:09:39 +08:00
ctx . Data [ "RecaptchaURL" ] = setting . Service . RecaptchaURL
2021-01-27 22:56:54 +08:00
ctx . Data [ "Captcha" ] = context . GetImageCaptcha ( )
2018-07-05 12:13:05 +08:00
ctx . Data [ "CaptchaType" ] = setting . Service . CaptchaType
ctx . Data [ "RecaptchaSitekey" ] = setting . Service . RecaptchaSitekey
2021-03-01 06:04:15 +08:00
ctx . Data [ "HcaptchaSitekey" ] = setting . Service . HcaptchaSitekey
2017-02-22 15:14:37 +08:00
ctx . Data [ "DisableRegistration" ] = setting . Service . DisableRegistration
ctx . Data [ "ShowRegistrationButton" ] = false
// use this to set the right link into the signIn and signUp templates in the link_account template
ctx . Data [ "SignInLink" ] = setting . AppSubURL + "/user/link_account_signin"
ctx . Data [ "SignUpLink" ] = setting . AppSubURL + "/user/link_account_signup"
gothUser := ctx . Session . Get ( "linkAccountGothUser" )
if gothUser == nil {
2018-01-11 05:34:17 +08:00
ctx . ServerError ( "UserSignIn" , errors . New ( "not in LinkAccount session" ) )
2017-02-22 15:14:37 +08:00
return
}
if ctx . HasError ( ) {
2021-04-05 23:30:52 +08:00
ctx . HTML ( http . StatusOK , tplLinkAccount )
2017-02-22 15:14:37 +08:00
return
}
u , err := models . UserSignIn ( signInForm . UserName , signInForm . Password )
if err != nil {
if models . IsErrUserNotExist ( err ) {
2019-11-25 04:17:53 +08:00
ctx . Data [ "user_exists" ] = true
2017-02-22 15:14:37 +08:00
ctx . RenderWithErr ( ctx . Tr ( "form.username_password_incorrect" ) , tplLinkAccount , & signInForm )
} else {
2018-01-11 05:34:17 +08:00
ctx . ServerError ( "UserLinkAccount" , err )
2017-02-22 15:14:37 +08:00
}
return
}
2021-04-14 20:02:12 +08:00
linkAccount ( ctx , u , gothUser . ( goth . User ) , signInForm . Remember )
}
func linkAccount ( ctx * context . Context , u * models . User , gothUser goth . User , remember bool ) {
updateAvatarIfNeed ( gothUser . AvatarURL , u )
2017-02-22 15:14:37 +08:00
// If this user is enrolled in 2FA, we can't sign the user in just yet.
// Instead, redirect them to the 2FA authentication page.
2021-04-14 20:02:12 +08:00
_ , err := models . GetTwoFactorByUID ( u . ID )
2017-02-22 15:14:37 +08:00
if err != nil {
2019-10-14 14:10:42 +08:00
if ! models . IsErrTwoFactorNotEnrolled ( err ) {
ctx . ServerError ( "UserLinkAccount" , err )
return
}
2021-04-14 20:02:12 +08:00
err = externalaccount . LinkAccountToUser ( u , gothUser )
2019-10-14 14:10:42 +08:00
if err != nil {
2018-01-11 05:34:17 +08:00
ctx . ServerError ( "UserLinkAccount" , err )
2019-10-14 14:10:42 +08:00
return
2017-02-22 15:14:37 +08:00
}
2019-10-14 14:10:42 +08:00
2021-04-14 20:02:12 +08:00
handleSignIn ( ctx , u , remember )
2017-02-22 15:14:37 +08:00
return
}
// User needs to use 2FA, save data and redirect to 2FA page.
2020-05-17 20:43:29 +08:00
if err := ctx . Session . Set ( "twofaUid" , u . ID ) ; err != nil {
log . Error ( "Error setting twofaUid in session: %v" , err )
2019-06-13 03:41:28 +08:00
}
2021-04-14 20:02:12 +08:00
if err := ctx . Session . Set ( "twofaRemember" , remember ) ; err != nil {
2020-05-17 20:43:29 +08:00
log . Error ( "Error setting twofaRemember in session: %v" , err )
2019-06-13 03:41:28 +08:00
}
2020-05-17 20:43:29 +08:00
if err := ctx . Session . Set ( "linkAccount" , true ) ; err != nil {
log . Error ( "Error setting linkAccount in session: %v" , err )
}
if err := ctx . Session . Release ( ) ; err != nil {
log . Error ( "Error storing session: %v" , err )
2019-06-13 03:41:28 +08:00
}
2017-02-22 15:14:37 +08:00
2018-05-19 22:12:37 +08:00
// If U2F is enrolled -> Redirect to U2F instead
regs , err := models . GetU2FRegistrationsByUID ( u . ID )
if err == nil && len ( regs ) > 0 {
ctx . Redirect ( setting . AppSubURL + "/user/u2f" )
return
}
2017-02-22 15:14:37 +08:00
ctx . Redirect ( setting . AppSubURL + "/user/two_factor" )
}
// LinkAccountPostRegister handle the creation of a new account for an external account using signUp
2021-01-26 23:36:53 +08:00
func LinkAccountPostRegister ( ctx * context . Context ) {
2021-04-07 03:44:05 +08:00
form := web . GetForm ( ctx ) . ( * forms . RegisterForm )
2019-07-07 03:48:02 +08:00
// TODO Make insecure passwords optional for local accounts also,
// once email-based Second-Factor Auth is available
2019-11-09 17:42:34 +08:00
ctx . Data [ "DisablePassword" ] = ! setting . Service . RequireExternalRegistrationPassword || setting . Service . AllowOnlyExternalRegistration
2017-02-22 15:14:37 +08:00
ctx . Data [ "Title" ] = ctx . Tr ( "link_account" )
ctx . Data [ "LinkAccountMode" ] = true
ctx . Data [ "LinkAccountModeRegister" ] = true
2019-07-07 03:48:02 +08:00
ctx . Data [ "EnableCaptcha" ] = setting . Service . EnableCaptcha && setting . Service . RequireExternalRegistrationCaptcha
2019-05-02 21:09:39 +08:00
ctx . Data [ "RecaptchaURL" ] = setting . Service . RecaptchaURL
2021-01-27 22:56:54 +08:00
ctx . Data [ "Captcha" ] = context . GetImageCaptcha ( )
2018-07-05 12:13:05 +08:00
ctx . Data [ "CaptchaType" ] = setting . Service . CaptchaType
ctx . Data [ "RecaptchaSitekey" ] = setting . Service . RecaptchaSitekey
2021-03-01 06:04:15 +08:00
ctx . Data [ "HcaptchaSitekey" ] = setting . Service . HcaptchaSitekey
2017-02-22 15:14:37 +08:00
ctx . Data [ "DisableRegistration" ] = setting . Service . DisableRegistration
ctx . Data [ "ShowRegistrationButton" ] = false
// use this to set the right link into the signIn and signUp templates in the link_account template
ctx . Data [ "SignInLink" ] = setting . AppSubURL + "/user/link_account_signin"
ctx . Data [ "SignUpLink" ] = setting . AppSubURL + "/user/link_account_signup"
gothUser := ctx . Session . Get ( "linkAccountGothUser" )
if gothUser == nil {
2018-01-11 05:34:17 +08:00
ctx . ServerError ( "UserSignUp" , errors . New ( "not in LinkAccount session" ) )
2017-02-22 15:14:37 +08:00
return
}
if ctx . HasError ( ) {
2021-04-05 23:30:52 +08:00
ctx . HTML ( http . StatusOK , tplLinkAccount )
2017-02-22 15:14:37 +08:00
return
}
2021-05-09 22:13:35 +08:00
if setting . Service . DisableRegistration || setting . Service . AllowOnlyInternalRegistration {
2021-04-05 23:30:52 +08:00
ctx . Error ( http . StatusForbidden )
2017-02-22 15:14:37 +08:00
return
}
2019-07-07 03:48:02 +08:00
if setting . Service . EnableCaptcha && setting . Service . RequireExternalRegistrationCaptcha {
var valid bool
2020-10-03 11:37:53 +08:00
var err error
2019-07-07 03:48:02 +08:00
switch setting . Service . CaptchaType {
case setting . ImageCaptcha :
2021-01-26 23:36:53 +08:00
valid = context . GetImageCaptcha ( ) . VerifyReq ( ctx . Req )
2019-07-07 03:48:02 +08:00
case setting . ReCaptcha :
2020-10-03 11:37:53 +08:00
valid , err = recaptcha . Verify ( ctx . Req . Context ( ) , form . GRecaptchaResponse )
case setting . HCaptcha :
valid , err = hcaptcha . Verify ( ctx . Req . Context ( ) , form . HcaptchaResponse )
2019-07-07 03:48:02 +08:00
default :
ctx . ServerError ( "Unknown Captcha Type" , fmt . Errorf ( "Unknown Captcha Type: %s" , setting . Service . CaptchaType ) )
return
}
2020-10-03 11:37:53 +08:00
if err != nil {
log . Debug ( "%s" , err . Error ( ) )
}
2017-02-22 15:14:37 +08:00
2018-07-05 12:13:05 +08:00
if ! valid {
ctx . Data [ "Err_Captcha" ] = true
ctx . RenderWithErr ( ctx . Tr ( "form.captcha_incorrect" ) , tplLinkAccount , & form )
return
}
}
2021-03-17 11:54:02 +08:00
if ! form . IsEmailDomainAllowed ( ) {
ctx . RenderWithErr ( ctx . Tr ( "auth.email_domain_blacklisted" ) , tplLinkAccount , & form )
return
}
2019-07-07 03:48:02 +08:00
if setting . Service . AllowOnlyExternalRegistration || ! setting . Service . RequireExternalRegistrationPassword {
// In models.User an empty password is classed as not set, so we set form.Password to empty.
// Eventually the database should be changed to indicate "Second Factor"-enabled accounts
// (accounts that do not introduce the security vulnerabilities of a password).
// If a user decides to circumvent second-factor security, and purposefully create a password,
// they can still do so using the "Recover Account" option.
form . Password = ""
} else {
if ( len ( strings . TrimSpace ( form . Password ) ) > 0 || len ( strings . TrimSpace ( form . Retype ) ) > 0 ) && form . Password != form . Retype {
ctx . Data [ "Err_Password" ] = true
ctx . RenderWithErr ( ctx . Tr ( "form.password_not_match" ) , tplLinkAccount , & form )
return
}
if len ( strings . TrimSpace ( form . Password ) ) > 0 && len ( form . Password ) < setting . MinPasswordLength {
ctx . Data [ "Err_Password" ] = true
ctx . RenderWithErr ( ctx . Tr ( "auth.password_too_short" , setting . MinPasswordLength ) , tplLinkAccount , & form )
return
}
2017-02-22 15:14:37 +08:00
}
loginSource , err := models . GetActiveOAuth2LoginSourceByName ( gothUser . ( goth . User ) . Provider )
if err != nil {
2018-01-11 05:34:17 +08:00
ctx . ServerError ( "CreateUser" , err )
2017-02-22 15:14:37 +08:00
}
u := & models . User {
Name : form . UserName ,
Email : form . Email ,
Passwd : form . Password ,
2020-12-20 09:31:06 +08:00
IsActive : ! ( setting . Service . RegisterEmailConfirm || setting . Service . RegisterManualConfirm ) ,
2017-02-22 15:14:37 +08:00
LoginType : models . LoginOAuth2 ,
LoginSource : loginSource . ID ,
LoginName : gothUser . ( goth . User ) . UserID ,
}
2021-04-14 20:02:12 +08:00
if ! createAndHandleCreatedUser ( ctx , tplLinkAccount , form , u , gothUser . ( * goth . User ) , false ) {
// error already handled
2017-02-22 15:14:37 +08:00
return
}
ctx . Redirect ( setting . AppSubURL + "/user/login" )
}
2020-05-08 05:49:00 +08:00
// HandleSignOut resets the session and sets the cookies
func HandleSignOut ( ctx * context . Context ) {
2020-05-17 14:05:11 +08:00
_ = ctx . Session . Flush ( )
2021-01-26 23:36:53 +08:00
_ = ctx . Session . Destroy ( ctx . Resp , ctx . Req )
2021-03-07 16:12:43 +08:00
ctx . DeleteCookie ( setting . CookieUserName )
ctx . DeleteCookie ( setting . CookieRememberName )
middleware . DeleteCSRFCookie ( ctx . Resp )
middleware . DeleteLocaleCookie ( ctx . Resp )
middleware . DeleteRedirectToCookie ( ctx . Resp )
2019-04-18 15:23:59 +08:00
}
// SignOut sign out from login status
func SignOut ( ctx * context . Context ) {
2020-05-08 05:49:00 +08:00
if ctx . User != nil {
eventsource . GetManager ( ) . SendMessageBlocking ( ctx . User . ID , & eventsource . Event {
Name : "logout" ,
Data : ctx . Session . ID ( ) ,
} )
}
HandleSignOut ( ctx )
2016-11-27 18:14:25 +08:00
ctx . Redirect ( setting . AppSubURL + "/" )
2014-07-26 12:24:27 +08:00
}
2016-11-18 11:03:03 +08:00
// SignUp render the register page
2016-03-12 00:56:52 +08:00
func SignUp ( ctx * context . Context ) {
2014-07-26 12:24:27 +08:00
ctx . Data [ "Title" ] = ctx . Tr ( "sign_up" )
2017-03-07 18:47:56 +08:00
ctx . Data [ "SignUpLink" ] = setting . AppSubURL + "/user/sign_up"
2015-09-13 23:07:21 +08:00
ctx . Data [ "EnableCaptcha" ] = setting . Service . EnableCaptcha
2019-05-02 21:09:39 +08:00
ctx . Data [ "RecaptchaURL" ] = setting . Service . RecaptchaURL
2021-01-27 22:56:54 +08:00
ctx . Data [ "Captcha" ] = context . GetImageCaptcha ( )
2018-07-05 12:13:05 +08:00
ctx . Data [ "CaptchaType" ] = setting . Service . CaptchaType
ctx . Data [ "RecaptchaSitekey" ] = setting . Service . RecaptchaSitekey
2020-10-03 11:37:53 +08:00
ctx . Data [ "HcaptchaSitekey" ] = setting . Service . HcaptchaSitekey
2020-05-13 02:01:15 +08:00
ctx . Data [ "PageIsSignUp" ] = true
2018-07-05 12:13:05 +08:00
2020-04-30 05:46:43 +08:00
//Show Disabled Registration message if DisableRegistration or AllowOnlyExternalRegistration options are true
ctx . Data [ "DisableRegistration" ] = setting . Service . DisableRegistration || setting . Service . AllowOnlyExternalRegistration
2014-07-26 12:24:27 +08:00
2021-04-05 23:30:52 +08:00
ctx . HTML ( http . StatusOK , tplSignUp )
2014-07-26 12:24:27 +08:00
}
2016-11-18 11:03:03 +08:00
// SignUpPost response for sign up information submission
2021-01-26 23:36:53 +08:00
func SignUpPost ( ctx * context . Context ) {
2021-04-07 03:44:05 +08:00
form := web . GetForm ( ctx ) . ( * forms . RegisterForm )
2014-07-26 12:24:27 +08:00
ctx . Data [ "Title" ] = ctx . Tr ( "sign_up" )
2017-03-07 18:47:56 +08:00
ctx . Data [ "SignUpLink" ] = setting . AppSubURL + "/user/sign_up"
2015-09-13 23:07:21 +08:00
ctx . Data [ "EnableCaptcha" ] = setting . Service . EnableCaptcha
2019-05-02 21:09:39 +08:00
ctx . Data [ "RecaptchaURL" ] = setting . Service . RecaptchaURL
2021-01-27 22:56:54 +08:00
ctx . Data [ "Captcha" ] = context . GetImageCaptcha ( )
2018-07-05 12:13:05 +08:00
ctx . Data [ "CaptchaType" ] = setting . Service . CaptchaType
ctx . Data [ "RecaptchaSitekey" ] = setting . Service . RecaptchaSitekey
2020-10-03 11:37:53 +08:00
ctx . Data [ "HcaptchaSitekey" ] = setting . Service . HcaptchaSitekey
2020-05-13 02:01:15 +08:00
ctx . Data [ "PageIsSignUp" ] = true
2018-07-05 12:13:05 +08:00
2018-05-13 15:51:16 +08:00
//Permission denied if DisableRegistration or AllowOnlyExternalRegistration options are true
2020-04-30 05:46:43 +08:00
if setting . Service . DisableRegistration || setting . Service . AllowOnlyExternalRegistration {
2021-04-05 23:30:52 +08:00
ctx . Error ( http . StatusForbidden )
2014-07-26 12:24:27 +08:00
return
}
if ctx . HasError ( ) {
2021-04-05 23:30:52 +08:00
ctx . HTML ( http . StatusOK , tplSignUp )
2014-07-26 12:24:27 +08:00
return
}
2019-07-07 03:48:02 +08:00
if setting . Service . EnableCaptcha {
var valid bool
2020-10-03 11:37:53 +08:00
var err error
2019-07-07 03:48:02 +08:00
switch setting . Service . CaptchaType {
case setting . ImageCaptcha :
2021-01-26 23:36:53 +08:00
valid = context . GetImageCaptcha ( ) . VerifyReq ( ctx . Req )
2019-07-07 03:48:02 +08:00
case setting . ReCaptcha :
2020-10-03 11:37:53 +08:00
valid , err = recaptcha . Verify ( ctx . Req . Context ( ) , form . GRecaptchaResponse )
case setting . HCaptcha :
valid , err = hcaptcha . Verify ( ctx . Req . Context ( ) , form . HcaptchaResponse )
2019-07-07 03:48:02 +08:00
default :
ctx . ServerError ( "Unknown Captcha Type" , fmt . Errorf ( "Unknown Captcha Type: %s" , setting . Service . CaptchaType ) )
return
}
2020-10-03 11:37:53 +08:00
if err != nil {
log . Debug ( "%s" , err . Error ( ) )
}
2015-09-13 21:51:51 +08:00
2018-07-05 12:13:05 +08:00
if ! valid {
ctx . Data [ "Err_Captcha" ] = true
ctx . RenderWithErr ( ctx . Tr ( "form.captcha_incorrect" ) , tplSignUp , & form )
return
}
}
2021-02-15 07:31:29 +08:00
if ! form . IsEmailDomainAllowed ( ) {
2018-11-15 09:00:04 +08:00
ctx . RenderWithErr ( ctx . Tr ( "auth.email_domain_blacklisted" ) , tplSignUp , & form )
return
}
2015-09-13 21:51:51 +08:00
if form . Password != form . Retype {
2014-07-26 12:24:27 +08:00
ctx . Data [ "Err_Password" ] = true
2016-11-18 11:03:03 +08:00
ctx . RenderWithErr ( ctx . Tr ( "form.password_not_match" ) , tplSignUp , & form )
2014-07-26 12:24:27 +08:00
return
}
2016-12-24 21:40:44 +08:00
if len ( form . Password ) < setting . MinPasswordLength {
ctx . Data [ "Err_Password" ] = true
ctx . RenderWithErr ( ctx . Tr ( "auth.password_too_short" , setting . MinPasswordLength ) , tplSignUp , & form )
return
}
2019-11-09 11:40:37 +08:00
if ! password . IsComplexEnough ( form . Password ) {
ctx . Data [ "Err_Password" ] = true
2019-11-20 06:44:58 +08:00
ctx . RenderWithErr ( password . BuildComplexityError ( ctx ) , tplSignUp , & form )
2019-11-09 11:40:37 +08:00
return
}
2020-09-09 06:06:39 +08:00
pwned , err := password . IsPwned ( ctx . Req . Context ( ) , 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
}
2014-07-26 12:24:27 +08:00
u := & models . User {
Name : form . UserName ,
Email : form . Email ,
Passwd : form . Password ,
2020-12-20 09:31:06 +08:00
IsActive : ! ( setting . Service . RegisterEmailConfirm || setting . Service . RegisterManualConfirm ) ,
2014-07-26 12:24:27 +08:00
}
2021-04-14 20:02:12 +08:00
if ! createAndHandleCreatedUser ( ctx , tplSignUp , form , u , nil , false ) {
// error already handled
return
}
ctx . Flash . Success ( ctx . Tr ( "auth.sign_up_successful" ) )
handleSignInFull ( ctx , u , false , true )
}
// createAndHandleCreatedUser calls createUserInContext and
// then handleUserCreated.
func createAndHandleCreatedUser ( ctx * context . Context , tpl base . TplName , form interface { } , u * models . User , gothUser * goth . User , allowLink bool ) bool {
if ! createUserInContext ( ctx , tpl , form , u , gothUser , allowLink ) {
return false
}
return handleUserCreated ( ctx , u , gothUser )
}
// createUserInContext creates a user and handles errors within a given context.
// Optionally a template can be specified.
func createUserInContext ( ctx * context . Context , tpl base . TplName , form interface { } , u * models . User , gothUser * goth . User , allowLink bool ) ( ok bool ) {
2014-07-26 12:24:27 +08:00
if err := models . CreateUser ( u ) ; err != nil {
2021-04-14 20:02:12 +08:00
if allowLink && ( models . IsErrUserAlreadyExist ( err ) || models . IsErrEmailAlreadyUsed ( err ) ) {
if setting . OAuth2Client . AccountLinking == setting . OAuth2AccountLinkingAuto {
var user * models . User
user = & models . User { Name : u . Name }
hasUser , err := models . GetUser ( user )
if ! hasUser || err != nil {
user = & models . User { Email : u . Email }
hasUser , err = models . GetUser ( user )
if ! hasUser || err != nil {
ctx . ServerError ( "UserLinkAccount" , err )
return
}
}
2021-05-01 04:21:33 +08:00
// TODO: probably we should respect 'remember' user's choice...
2021-04-14 20:02:12 +08:00
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
2015-03-27 05:11:47 +08:00
switch {
case models . IsErrUserAlreadyExist ( err ) :
2014-07-26 12:24:27 +08:00
ctx . Data [ "Err_UserName" ] = true
2021-04-14 20:02:12 +08:00
ctx . RenderWithErr ( ctx . Tr ( "form.username_been_taken" ) , tpl , form )
2015-03-27 05:11:47 +08:00
case models . IsErrEmailAlreadyUsed ( err ) :
2014-07-26 12:24:27 +08:00
ctx . Data [ "Err_Email" ] = true
2021-04-14 20:02:12 +08:00
ctx . RenderWithErr ( ctx . Tr ( "form.email_been_used" ) , tpl , form )
2020-11-15 00:53:43 +08:00
case models . IsErrEmailInvalid ( err ) :
ctx . Data [ "Err_Email" ] = true
2021-04-14 20:02:12 +08:00
ctx . RenderWithErr ( ctx . Tr ( "form.email_invalid" ) , tpl , form )
2015-03-27 05:11:47 +08:00
case models . IsErrNameReserved ( err ) :
2014-07-26 12:24:27 +08:00
ctx . Data [ "Err_UserName" ] = true
2021-04-14 20:02:12 +08:00
ctx . RenderWithErr ( ctx . Tr ( "user.form.name_reserved" , err . ( models . ErrNameReserved ) . Name ) , tpl , form )
2015-03-27 05:11:47 +08:00
case models . IsErrNamePatternNotAllowed ( err ) :
ctx . Data [ "Err_UserName" ] = true
2021-04-14 20:02:12 +08:00
ctx . RenderWithErr ( ctx . Tr ( "user.form.name_pattern_not_allowed" , err . ( models . ErrNamePatternNotAllowed ) . Pattern ) , tpl , form )
case models . IsErrNameCharsNotAllowed ( err ) :
ctx . Data [ "Err_UserName" ] = true
ctx . RenderWithErr ( ctx . Tr ( "user.form.name_chars_not_allowed" , err . ( models . ErrNameCharsNotAllowed ) . Name ) , tpl , form )
2014-07-26 12:24:27 +08:00
default :
2018-01-11 05:34:17 +08:00
ctx . ServerError ( "CreateUser" , err )
2014-07-26 12:24:27 +08:00
}
return
}
log . Trace ( "Account created: %s" , u . Name )
2021-04-14 20:02:12 +08:00
return true
}
2014-07-26 12:24:27 +08:00
2021-04-14 20:02:12 +08:00
// 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 * models . User , gothUser * goth . User ) ( ok bool ) {
2015-08-19 04:58:45 +08:00
// Auto-set admin for the only user.
if models . CountUsers ( ) == 1 {
u . IsAdmin = true
u . IsActive = true
2017-08-12 22:18:44 +08:00
u . SetLastLogin ( )
if err := models . UpdateUserCols ( u , "is_admin" , "is_active" , "last_login_unix" ) ; err != nil {
2018-01-11 05:34:17 +08:00
ctx . ServerError ( "UpdateUser" , err )
2015-08-19 04:58:45 +08:00
return
}
}
2021-04-14 20:02:12 +08:00
// update external user information
if gothUser != nil {
if err := models . UpdateExternalUser ( u , * gothUser ) ; err != nil {
log . Error ( "UpdateExternalUser failed: %v" , err )
}
}
// Send confirmation email
if ! u . IsActive && u . ID > 1 {
2019-09-24 13:02:49 +08:00
mailer . SendActivateAccountMail ( ctx . Locale , u )
2014-08-10 08:25:02 +08:00
ctx . Data [ "IsSendRegisterMail" ] = true
ctx . Data [ "Email" ] = u . Email
2019-08-15 22:46:21 +08:00
ctx . Data [ "ActiveCodeLives" ] = timeutil . MinutesToFriendly ( setting . Service . ActiveCodeLives , ctx . Locale . Language ( ) )
2021-04-05 23:30:52 +08:00
ctx . HTML ( http . StatusOK , TplActivate )
2014-08-10 08:25:02 +08:00
if err := ctx . Cache . Put ( "MailResendLimit_" + u . LowerName , u . LowerName , 180 ) ; err != nil {
2019-04-02 15:48:31 +08:00
log . Error ( "Set cache(MailResendLimit) fail: %v" , err )
2014-08-10 08:25:02 +08:00
}
return
}
2014-07-26 12:24:27 +08:00
2021-04-14 20:02:12 +08:00
return true
2014-07-26 12:24:27 +08:00
}
2016-11-18 11:03:03 +08:00
// Activate render activate user page
2016-03-12 00:56:52 +08:00
func Activate ( ctx * context . Context ) {
2014-08-10 12:02:00 +08:00
code := ctx . Query ( "code" )
2020-11-29 06:41:06 +08:00
2014-08-10 12:02:00 +08:00
if len ( code ) == 0 {
ctx . Data [ "IsActivatePage" ] = true
2021-05-01 04:21:33 +08:00
if ctx . User == nil || ctx . User . IsActive {
ctx . NotFound ( "invalid user" , nil )
2014-08-10 12:02:00 +08:00
return
}
2016-07-16 00:36:39 +08:00
// Resend confirmation email.
2014-08-10 12:02:00 +08:00
if setting . Service . RegisterEmailConfirm {
if ctx . Cache . IsExist ( "MailResendLimit_" + ctx . User . LowerName ) {
ctx . Data [ "ResendLimited" ] = true
} else {
2019-08-15 22:46:21 +08:00
ctx . Data [ "ActiveCodeLives" ] = timeutil . MinutesToFriendly ( setting . Service . ActiveCodeLives , ctx . Locale . Language ( ) )
2019-09-24 13:02:49 +08:00
mailer . SendActivateAccountMail ( ctx . Locale , ctx . User )
2014-08-10 12:02:00 +08:00
if err := ctx . Cache . Put ( "MailResendLimit_" + ctx . User . LowerName , ctx . User . LowerName , 180 ) ; err != nil {
2019-04-02 15:48:31 +08:00
log . Error ( "Set cache(MailResendLimit) fail: %v" , err )
2014-08-10 12:02:00 +08:00
}
}
} else {
ctx . Data [ "ServiceNotEnabled" ] = true
}
2021-04-05 23:30:52 +08:00
ctx . HTML ( http . StatusOK , TplActivate )
2014-08-10 12:02:00 +08:00
return
}
2020-11-29 06:41:06 +08:00
user := models . VerifyUserActiveCode ( code )
// if code is wrong
if user == nil {
ctx . Data [ "IsActivateFailed" ] = true
2021-04-05 23:30:52 +08:00
ctx . HTML ( http . StatusOK , TplActivate )
2020-11-29 06:41:06 +08:00
return
}
// if account is local account, verify password
if user . LoginSource == 0 {
2021-05-01 04:21:33 +08:00
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 . Query ( "code" )
if len ( code ) == 0 {
ctx . Redirect ( setting . AppSubURL + "/user/activate" )
return
}
user := models . 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 . Query ( "password" )
2020-11-29 06:41:06 +08:00
if len ( password ) == 0 {
ctx . Data [ "Code" ] = code
ctx . Data [ "NeedsPassword" ] = true
2021-04-05 23:30:52 +08:00
ctx . HTML ( http . StatusOK , TplActivate )
2016-12-20 20:32:02 +08:00
return
}
2020-11-29 06:41:06 +08:00
if ! user . ValidatePassword ( password ) {
ctx . Data [ "IsActivateFailed" ] = true
2021-04-05 23:30:52 +08:00
ctx . HTML ( http . StatusOK , TplActivate )
2014-08-10 12:02:00 +08:00
return
}
2020-11-29 06:41:06 +08:00
}
2014-08-10 12:02:00 +08:00
2021-05-01 04:21:33 +08:00
handleAccountActivation ( ctx , user )
}
func handleAccountActivation ( ctx * context . Context , user * models . User ) {
2020-11-29 06:41:06 +08:00
user . IsActive = true
var err error
if user . Rands , err = models . GetUserSalt ( ) ; err != nil {
ctx . ServerError ( "UpdateUser" , err )
return
}
if err := models . UpdateUserCols ( user , "is_active" , "rands" ) ; err != nil {
if models . IsErrUserNotExist ( err ) {
2021-05-01 04:21:33 +08:00
ctx . NotFound ( "UpdateUserCols" , err )
2020-11-29 06:41:06 +08:00
} else {
ctx . ServerError ( "UpdateUser" , err )
2020-05-17 20:43:29 +08:00
}
2014-08-10 12:02:00 +08:00
return
}
2020-11-29 06:41:06 +08:00
log . Trace ( "User activated: %s" , user . Name )
if err := ctx . Session . Set ( "uid" , user . ID ) ; err != nil {
log . Error ( fmt . Sprintf ( "Error setting uid in session: %v" , err ) )
}
if err := ctx . Session . Set ( "uname" , user . Name ) ; err != nil {
log . Error ( fmt . Sprintf ( "Error setting uname in session: %v" , err ) )
}
if err := ctx . Session . Release ( ) ; err != nil {
log . Error ( "Error storing session: %v" , err )
}
ctx . Flash . Success ( ctx . Tr ( "auth.account_activated" ) )
ctx . Redirect ( setting . AppSubURL + "/" )
2014-07-26 12:24:27 +08:00
}
2016-11-18 11:03:03 +08:00
// ActivateEmail render the activate email page
2016-03-12 00:56:52 +08:00
func ActivateEmail ( ctx * context . Context ) {
2014-12-17 23:41:49 +08:00
code := ctx . Query ( "code" )
2016-11-18 11:03:03 +08:00
emailStr := ctx . Query ( "email" )
2014-12-17 23:41:49 +08:00
// Verify code.
2016-11-18 11:03:03 +08:00
if email := models . VerifyActiveEmailCode ( code , emailStr ) ; email != nil {
2015-02-22 11:13:47 +08:00
if err := email . Activate ( ) ; err != nil {
2018-01-11 05:34:17 +08:00
ctx . ServerError ( "ActivateEmail" , err )
2014-12-17 23:41:49 +08:00
}
log . Trace ( "Email activated: %s" , email . Email )
2015-11-20 00:52:39 +08:00
ctx . Flash . Success ( ctx . Tr ( "settings.add_email_success" ) )
2020-03-03 02:25:36 +08:00
if u , err := models . GetUserByID ( email . UID ) ; err != nil {
log . Warn ( "GetUserByID: %d" , email . UID )
} else {
// Allow user to validate more emails
_ = ctx . Cache . Delete ( "MailResendLimit_" + u . LowerName )
}
2014-12-17 23:41:49 +08:00
}
2020-03-03 02:25:36 +08:00
// 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.)
2020-03-01 07:46:20 +08:00
ctx . Redirect ( setting . AppSubURL + "/user/settings/account" )
2014-12-17 23:41:49 +08:00
}
2016-11-18 11:03:03 +08:00
// ForgotPasswd render the forget pasword page
2016-03-12 00:56:52 +08:00
func ForgotPasswd ( ctx * context . Context ) {
2017-02-21 02:27:20 +08:00
ctx . Data [ "Title" ] = ctx . Tr ( "auth.forgot_password_title" )
2014-07-26 12:24:27 +08:00
if setting . MailService == nil {
ctx . Data [ "IsResetDisable" ] = true
2021-04-05 23:30:52 +08:00
ctx . HTML ( http . StatusOK , tplForgotPassword )
2014-07-26 12:24:27 +08:00
return
}
2017-03-11 17:11:54 +08:00
email := ctx . Query ( "email" )
ctx . Data [ "Email" ] = email
2014-07-26 12:24:27 +08:00
ctx . Data [ "IsResetRequest" ] = true
2021-04-05 23:30:52 +08:00
ctx . HTML ( http . StatusOK , tplForgotPassword )
2014-07-26 12:24:27 +08:00
}
2016-11-18 11:03:03 +08:00
// ForgotPasswdPost response for forget password request
2016-03-12 00:56:52 +08:00
func ForgotPasswdPost ( ctx * context . Context ) {
2017-02-21 02:27:20 +08:00
ctx . Data [ "Title" ] = ctx . Tr ( "auth.forgot_password_title" )
2014-08-10 12:02:00 +08:00
if setting . MailService == nil {
2018-01-11 05:34:17 +08:00
ctx . NotFound ( "ForgotPasswdPost" , nil )
2014-08-10 12:02:00 +08:00
return
}
ctx . Data [ "IsResetRequest" ] = true
email := ctx . Query ( "email" )
2015-09-18 02:57:24 +08:00
ctx . Data [ "Email" ] = email
2014-08-10 12:02:00 +08:00
u , err := models . GetUserByEmail ( email )
if err != nil {
2015-08-05 11:14:17 +08:00
if models . IsErrUserNotExist ( err ) {
2019-08-15 22:46:21 +08:00
ctx . Data [ "ResetPwdCodeLives" ] = timeutil . MinutesToFriendly ( setting . Service . ResetPwdCodeLives , ctx . Locale . Language ( ) )
2016-10-17 10:08:40 +08:00
ctx . Data [ "IsResetSent" ] = true
2021-04-05 23:30:52 +08:00
ctx . HTML ( http . StatusOK , tplForgotPassword )
2016-10-17 10:08:40 +08:00
return
2014-08-10 12:02:00 +08:00
}
2016-11-18 11:03:03 +08:00
2018-01-11 05:34:17 +08:00
ctx . ServerError ( "user.ResetPasswd(check existence)" , err )
2014-08-10 12:02:00 +08:00
return
}
2017-02-22 15:14:37 +08:00
if ! u . IsLocal ( ) && ! u . IsOAuth2 ( ) {
2016-03-14 22:40:16 +08:00
ctx . Data [ "Err_Email" ] = true
2016-11-18 11:03:03 +08:00
ctx . RenderWithErr ( ctx . Tr ( "auth.non_local_account" ) , tplForgotPassword , nil )
2016-03-14 22:40:16 +08:00
return
}
2014-08-10 12:02:00 +08:00
if ctx . Cache . IsExist ( "MailResendLimit_" + u . LowerName ) {
ctx . Data [ "ResendLimited" ] = true
2021-04-05 23:30:52 +08:00
ctx . HTML ( http . StatusOK , tplForgotPassword )
2014-08-10 12:02:00 +08:00
return
}
2021-04-02 18:25:13 +08:00
mailer . SendResetPasswordMail ( u )
2019-09-24 13:02:49 +08:00
2014-08-10 12:02:00 +08:00
if err = ctx . Cache . Put ( "MailResendLimit_" + u . LowerName , u . LowerName , 180 ) ; err != nil {
2019-04-02 15:48:31 +08:00
log . Error ( "Set cache(MailResendLimit) fail: %v" , err )
2014-08-10 12:02:00 +08:00
}
2019-08-15 22:46:21 +08:00
ctx . Data [ "ResetPwdCodeLives" ] = timeutil . MinutesToFriendly ( setting . Service . ResetPwdCodeLives , ctx . Locale . Language ( ) )
2014-08-10 12:02:00 +08:00
ctx . Data [ "IsResetSent" ] = true
2021-04-05 23:30:52 +08:00
ctx . HTML ( http . StatusOK , tplForgotPassword )
2014-07-26 12:24:27 +08:00
}
2020-01-20 04:17:12 +08:00
func commonResetPassword ( ctx * context . Context ) ( * models . User , * models . TwoFactor ) {
2019-04-18 15:23:59 +08:00
code := ctx . Query ( "code" )
2014-08-10 12:02:00 +08:00
ctx . Data [ "Title" ] = ctx . Tr ( "auth.reset_password" )
2019-04-18 15:23:59 +08:00
ctx . Data [ "Code" ] = code
if nil != ctx . User {
ctx . Data [ "user_signed_in" ] = true
}
2014-07-26 12:24:27 +08:00
if len ( code ) == 0 {
2019-04-18 15:23:59 +08:00
ctx . Flash . Error ( ctx . Tr ( "auth.invalid_code" ) )
2020-01-20 04:17:12 +08:00
return nil , nil
2014-07-26 12:24:27 +08:00
}
2019-02-20 07:09:47 +08:00
2019-04-18 15:23:59 +08:00
// Fail early, don't frustrate the user
u := models . VerifyUserActiveCode ( code )
if u == nil {
ctx . Flash . Error ( ctx . Tr ( "auth.invalid_code" ) )
2020-01-20 04:17:12 +08:00
return nil , nil
}
twofa , err := models . GetTwoFactorByUID ( u . ID )
if err != nil {
if ! models . IsErrTwoFactorNotEnrolled ( err ) {
ctx . Error ( http . StatusInternalServerError , "CommonResetPassword" , err . Error ( ) )
return nil , nil
}
} else {
ctx . Data [ "has_two_factor" ] = true
ctx . Data [ "scratch_code" ] = ctx . QueryBool ( "scratch_code" )
2019-02-20 07:09:47 +08:00
}
2019-04-18 15:23:59 +08:00
// Show the user that they are affecting the account that they intended to
ctx . Data [ "user_email" ] = u . Email
if nil != ctx . User && u . ID != ctx . User . ID {
ctx . Flash . Error ( ctx . Tr ( "auth.reset_password_wrong_user" , ctx . User . Email , u . Email ) )
2020-01-20 04:17:12 +08:00
return nil , nil
2019-04-18 15:23:59 +08:00
}
2020-01-20 04:17:12 +08:00
return u , twofa
2019-04-18 15:23:59 +08:00
}
// ResetPasswd render the account recovery page
func ResetPasswd ( ctx * context . Context ) {
ctx . Data [ "IsResetForm" ] = true
commonResetPassword ( ctx )
2020-01-20 04:17:12 +08:00
if ctx . Written ( ) {
return
}
2019-04-18 15:23:59 +08:00
2021-04-05 23:30:52 +08:00
ctx . HTML ( http . StatusOK , tplResetPassword )
2014-07-26 12:24:27 +08:00
}
2019-04-18 15:23:59 +08:00
// ResetPasswdPost response from account recovery request
2016-03-12 00:56:52 +08:00
func ResetPasswdPost ( ctx * context . Context ) {
2020-01-20 04:17:12 +08:00
u , twofa := commonResetPassword ( ctx )
if ctx . Written ( ) {
return
}
2014-08-10 12:02:00 +08:00
2019-04-18 15:23:59 +08:00
if u == nil {
// Flash error has been set
2021-04-05 23:30:52 +08:00
ctx . HTML ( http . StatusOK , tplResetPassword )
2014-08-10 12:02:00 +08:00
return
}
2019-04-18 15:23:59 +08:00
// Validate password length.
passwd := ctx . Query ( "password" )
if len ( passwd ) < setting . MinPasswordLength {
ctx . Data [ "IsResetForm" ] = true
ctx . Data [ "Err_Password" ] = true
ctx . RenderWithErr ( ctx . Tr ( "auth.password_too_short" , setting . MinPasswordLength ) , tplResetPassword , nil )
return
2019-10-14 23:24:26 +08:00
} else if ! password . IsComplexEnough ( passwd ) {
ctx . Data [ "IsResetForm" ] = true
ctx . Data [ "Err_Password" ] = true
2019-11-20 06:44:58 +08:00
ctx . RenderWithErr ( password . BuildComplexityError ( ctx ) , tplResetPassword , nil )
2019-10-14 23:24:26 +08:00
return
2020-09-09 06:06:39 +08:00
} else if pwned , err := password . IsPwned ( ctx . Req . Context ( ) , passwd ) ; pwned || err != nil {
errMsg := ctx . Tr ( "auth.password_pwned" )
if err != nil {
log . Error ( err . Error ( ) )
errMsg = ctx . Tr ( "auth.password_pwned_err" )
}
ctx . Data [ "IsResetForm" ] = true
ctx . Data [ "Err_Password" ] = true
ctx . RenderWithErr ( errMsg , tplResetPassword , nil )
return
2019-04-18 15:23:59 +08:00
}
2014-08-10 12:02:00 +08:00
2020-01-20 04:17:12 +08:00
// Handle two-factor
regenerateScratchToken := false
if twofa != nil {
if ctx . QueryBool ( "scratch_code" ) {
if ! twofa . VerifyScratchToken ( ctx . Query ( "token" ) ) {
ctx . Data [ "IsResetForm" ] = true
ctx . Data [ "Err_Token" ] = true
ctx . RenderWithErr ( ctx . Tr ( "auth.twofa_scratch_token_incorrect" ) , tplResetPassword , nil )
return
}
regenerateScratchToken = true
} else {
passcode := ctx . Query ( "passcode" )
ok , err := twofa . ValidateTOTP ( passcode )
if err != nil {
ctx . Error ( http . StatusInternalServerError , "ValidateTOTP" , err . Error ( ) )
return
}
if ! ok || twofa . LastUsedPasscode == passcode {
ctx . Data [ "IsResetForm" ] = true
ctx . Data [ "Err_Passcode" ] = true
ctx . RenderWithErr ( ctx . Tr ( "auth.twofa_passcode_incorrect" ) , tplResetPassword , nil )
return
}
twofa . LastUsedPasscode = passcode
if err = models . UpdateTwoFactor ( twofa ) ; err != nil {
ctx . ServerError ( "ResetPasswdPost: UpdateTwoFactor" , err )
return
}
}
}
2019-04-18 15:23:59 +08:00
var err error
if u . Rands , err = models . GetUserSalt ( ) ; err != nil {
ctx . ServerError ( "UpdateUser" , err )
return
}
2021-01-11 02:05:18 +08:00
if err = u . SetPassword ( passwd ) ; err != nil {
2019-04-18 15:23:59 +08:00
ctx . ServerError ( "UpdateUser" , err )
return
}
u . MustChangePassword = false
2020-12-13 02:59:49 +08:00
if err := models . UpdateUserCols ( u , "must_change_password" , "passwd" , "passwd_hash_algo" , "rands" , "salt" ) ; err != nil {
2019-04-18 15:23:59 +08:00
ctx . ServerError ( "UpdateUser" , err )
2014-08-10 12:02:00 +08:00
return
}
2019-04-18 15:23:59 +08:00
log . Trace ( "User password reset: %s" , u . Name )
2014-08-10 12:02:00 +08:00
ctx . Data [ "IsResetFailed" ] = true
2019-04-18 15:23:59 +08:00
remember := len ( ctx . Query ( "remember" ) ) != 0
2020-01-20 04:17:12 +08:00
if regenerateScratchToken {
// Invalidate the scratch token.
_ , err = twofa . GenerateScratchToken ( )
if err != nil {
ctx . ServerError ( "UserSignIn" , err )
return
}
if err = models . UpdateTwoFactor ( twofa ) ; err != nil {
ctx . ServerError ( "UserSignIn" , err )
return
}
handleSignInFull ( ctx , u , remember , false )
ctx . Flash . Info ( ctx . Tr ( "auth.twofa_scratch_used" ) )
ctx . Redirect ( setting . AppSubURL + "/user/settings/security" )
return
}
2019-04-18 15:23:59 +08:00
handleSignInFull ( ctx , u , remember , true )
2014-07-26 12:24:27 +08:00
}
2018-09-13 20:04:25 +08:00
// MustChangePassword renders the page to change a user's password
func MustChangePassword ( ctx * context . Context ) {
ctx . Data [ "Title" ] = ctx . Tr ( "auth.must_change_password" )
ctx . Data [ "ChangePasscodeLink" ] = setting . AppSubURL + "/user/settings/change_password"
2020-05-27 06:39:39 +08:00
ctx . Data [ "MustChangePassword" ] = true
2021-04-05 23:30:52 +08:00
ctx . HTML ( http . StatusOK , tplMustChangePassword )
2018-09-13 20:04:25 +08:00
}
// MustChangePasswordPost response for updating a user's password after his/her
// account was created by an admin
2021-01-26 23:36:53 +08:00
func MustChangePasswordPost ( ctx * context . Context ) {
2021-04-07 03:44:05 +08:00
form := web . GetForm ( ctx ) . ( * forms . MustChangePasswordForm )
2018-09-13 20:04:25 +08:00
ctx . Data [ "Title" ] = ctx . Tr ( "auth.must_change_password" )
ctx . Data [ "ChangePasscodeLink" ] = setting . AppSubURL + "/user/settings/change_password"
if ctx . HasError ( ) {
2021-04-05 23:30:52 +08:00
ctx . HTML ( http . StatusOK , tplMustChangePassword )
2018-09-13 20:04:25 +08:00
return
}
u := ctx . User
// Make sure only requests for users who are eligible to change their password via
// this method passes through
if ! u . MustChangePassword {
ctx . ServerError ( "MustUpdatePassword" , errors . New ( "cannot update password.. Please visit the settings page" ) )
return
}
if form . Password != form . Retype {
ctx . Data [ "Err_Password" ] = true
ctx . RenderWithErr ( ctx . Tr ( "form.password_not_match" ) , tplMustChangePassword , & form )
return
}
if len ( form . Password ) < setting . MinPasswordLength {
ctx . Data [ "Err_Password" ] = true
ctx . RenderWithErr ( ctx . Tr ( "auth.password_too_short" , setting . MinPasswordLength ) , tplMustChangePassword , & form )
return
}
var err error
2021-01-11 02:05:18 +08:00
if err = u . SetPassword ( form . Password ) ; err != nil {
2018-09-13 20:04:25 +08:00
ctx . ServerError ( "UpdateUser" , err )
return
}
u . MustChangePassword = false
2020-12-13 02:59:49 +08:00
if err := models . UpdateUserCols ( u , "must_change_password" , "passwd" , "passwd_hash_algo" , "salt" ) ; err != nil {
2018-09-13 20:04:25 +08:00
ctx . ServerError ( "UpdateUser" , err )
return
}
ctx . Flash . Success ( ctx . Tr ( "settings.change_password_success" ) )
log . Trace ( "User updated password: %s" , u . Name )
2020-08-12 04:05:34 +08:00
if redirectTo := ctx . GetCookie ( "redirect_to" ) ; len ( redirectTo ) > 0 && ! utils . IsExternalURL ( redirectTo ) {
2021-03-07 16:12:43 +08:00
middleware . DeleteRedirectToCookie ( ctx . Resp )
2018-09-13 20:04:25 +08:00
ctx . RedirectToFirst ( redirectTo )
return
}
ctx . Redirect ( setting . AppSubURL + "/" )
}