// Copyright 2015 - Present, The Gogs Authors. All rights reserved. // Copyright 2018 - Present, Gitote. All rights reserved. // // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree. package user import ( "fmt" "gitote/gitote/models" "gitote/gitote/models/errors" "gitote/gitote/pkg/context" "gitote/gitote/pkg/form" "gitote/gitote/pkg/mailer" "gitote/gitote/pkg/setting" "gitote/gitote/pkg/tool" "net/url" raven "github.com/getsentry/raven-go" "github.com/go-macaron/captcha" log "gopkg.in/clog.v1" ) const ( // LoginTPL page template LoginTPL = "user/auth/login" // TwoFactorTPL page template TwoFactorTPL = "user/auth/two_factor" // TwoFactorRecoveryCodeTPL page template TwoFactorRecoveryCodeTPL = "user/auth/two_factor_recovery_code" // SignupTPL page template SignupTPL = "user/auth/signup" // ActivateTPL page template ActivateTPL = "user/auth/activate" // ForgotPasswordTPL page template ForgotPasswordTPL = "user/auth/forgot_passwd" // ResetPasswordTPL page template ResetPasswordTPL = "user/auth/reset_passwd" ) // AutoLogin reads cookie and try to auto-login. func AutoLogin(c *context.Context) (bool, error) { if !models.HasEngine { return false, nil } uname := c.GetCookie(setting.CookieUserName) if len(uname) == 0 { return false, nil } isSucceed := false defer func() { if !isSucceed { log.Trace("auto-login cookie cleared: %s", uname) c.SetCookie(setting.CookieUserName, "", -1, setting.AppSubURL) c.SetCookie(setting.CookieRememberName, "", -1, setting.AppSubURL) c.SetCookie(setting.LoginStatusCookieName, "", -1, setting.AppSubURL) } }() u, err := models.GetUserByName(uname) if err != nil { if !errors.IsUserNotExist(err) { return false, fmt.Errorf("GetUserByName: %v", err) } return false, nil } if val, ok := c.GetSuperSecureCookie(u.Rands+u.Passwd, setting.CookieRememberName); !ok || val != u.Name { return false, nil } isSucceed = true c.Session.Set("uid", u.ID) c.Session.Set("uname", u.Name) c.SetCookie(setting.CSRFCookieName, "", -1, setting.AppSubURL) if setting.EnableLoginStatusCookie { c.SetCookie(setting.LoginStatusCookieName, "true", 0, setting.AppSubURL) } return true, nil } //Login is the handler for "/login" func Login(c *context.Context) { c.Title("sign_in") // Check auto-login isSucceed, err := AutoLogin(c) if err != nil { c.ServerError("AutoLogin", err) return } redirectTo := c.Query("redirect_to") if len(redirectTo) > 0 { c.SetCookie("redirect_to", redirectTo, 0, setting.AppSubURL) } else { redirectTo, _ = url.QueryUnescape(c.GetCookie("redirect_to")) } if isSucceed { if tool.IsSameSiteURLPath(redirectTo) { c.Redirect(redirectTo) } else { c.SubURLRedirect("/") } c.SetCookie("redirect_to", "", -1, setting.AppSubURL) return } // Display normal login page loginSources, err := models.ActivatedLoginSources() if err != nil { c.ServerError("ActivatedLoginSources", err) return } c.Data["LoginSources"] = loginSources for i := range loginSources { if loginSources[i].IsDefault { c.Data["DefaultSource"] = *loginSources[i] c.Data["login_source"] = loginSources[i].ID newLoginSources := append(loginSources[:i], loginSources[i+1:]...) c.Data["LoginSources"] = newLoginSources break } } c.Success(LoginTPL) } //afterLogin set sessions cookies and redirect to "/" func afterLogin(c *context.Context, u *models.User, remember bool) { if remember { days := 86400 * setting.LoginRememberDays c.SetCookie(setting.CookieUserName, u.Name, days, setting.AppSubURL, "", setting.CookieSecure, true) c.SetSuperSecureCookie(u.Rands+u.Passwd, setting.CookieRememberName, u.Name, days, setting.AppSubURL, "", setting.CookieSecure, true) } c.Session.Set("uid", u.ID) c.Session.Set("uname", u.Name) c.Session.Delete("twoFactorRemember") c.Session.Delete("twoFactorUserID") // Clear whatever CSRF has right now, force to generate a new one c.SetCookie(setting.CSRFCookieName, "", -1, setting.AppSubURL) if setting.EnableLoginStatusCookie { c.SetCookie(setting.LoginStatusCookieName, "true", 0, setting.AppSubURL) } redirectTo, _ := url.QueryUnescape(c.GetCookie("redirect_to")) c.SetCookie("redirect_to", "", -1, setting.AppSubURL) if tool.IsSameSiteURLPath(redirectTo) { c.Redirect(redirectTo) return } c.SubURLRedirect("/") } //LoginPost is the POST handler for "/login" func LoginPost(c *context.Context, f form.SignIn) { c.Title("sign_in") loginSources, err := models.ActivatedLoginSources() if err != nil { c.ServerError("ActivatedLoginSources", err) return } c.Data["LoginSources"] = loginSources if c.HasError() { c.Success(LoginTPL) return } u, err := models.UserLogin(f.UserName, f.Password, f.LoginSource) if err != nil { switch err.(type) { case errors.UserNotExist: c.FormErr("UserName", "Password") c.RenderWithErr(c.Tr("form.username_password_incorrect"), LoginTPL, &f) case errors.LoginSourceMismatch: c.FormErr("LoginSource") c.RenderWithErr(c.Tr("form.auth_source_mismatch"), LoginTPL, &f) default: c.ServerError("UserLogin", err) } for i := range loginSources { if loginSources[i].IsDefault { c.Data["DefaultSource"] = *loginSources[i] c.Data["login_source"] = loginSources[i].ID newLoginSources := append(loginSources[:i], loginSources[i+1:]...) c.Data["LoginSources"] = newLoginSources break } } return } if !u.IsEnabledTwoFactor() { afterLogin(c, u, f.Remember) return } c.Session.Set("twoFactorRemember", f.Remember) c.Session.Set("twoFactorUserID", u.ID) c.SubURLRedirect("/login/two_factor") } //LoginTwoFactor is the handler for "/login" with two factors authentication func LoginTwoFactor(c *context.Context) { _, ok := c.Session.Get("twoFactorUserID").(int64) if !ok { c.NotFound() return } c.Success(TwoFactorTPL) } //LoginTwoFactorPost is the POST handler for "/login" with two factors authentication func LoginTwoFactorPost(c *context.Context) { userID, ok := c.Session.Get("twoFactorUserID").(int64) if !ok { c.NotFound() return } t, err := models.GetTwoFactorByUserID(userID) if err != nil { c.ServerError("GetTwoFactorByUserID", err) return } passcode := c.Query("passcode") valid, err := t.ValidateTOTP(passcode) if err != nil { c.ServerError("ValidateTOTP", err) return } else if !valid { c.Flash.Error(c.Tr("settings.two_factor_invalid_passcode")) c.SubURLRedirect("/login/two_factor") return } u, err := models.GetUserByID(userID) if err != nil { c.ServerError("GetUserByID", err) return } // Prevent same passcode from being reused if c.Cache.IsExist(u.TwoFactorCacheKey(passcode)) { c.Flash.Error(c.Tr("settings.two_factor_reused_passcode")) c.SubURLRedirect("/login/two_factor") return } if err = c.Cache.Put(u.TwoFactorCacheKey(passcode), 1, 60); err != nil { raven.CaptureErrorAndWait(err, nil) log.Error(2, "Failed to put cache 'two factor passcode': %v", err) } afterLogin(c, u, c.Session.Get("twoFactorRemember").(bool)) } //LoginTwoFactorRecoveryCode is the handler to recover Two Factor Code func LoginTwoFactorRecoveryCode(c *context.Context) { _, ok := c.Session.Get("twoFactorUserID").(int64) if !ok { c.NotFound() return } c.Success(TwoFactorRecoveryCodeTPL) } //LoginTwoFactorRecoveryCodePost is the POST handler to recover Two Factor Code func LoginTwoFactorRecoveryCodePost(c *context.Context) { userID, ok := c.Session.Get("twoFactorUserID").(int64) if !ok { c.NotFound() return } if err := models.UseRecoveryCode(userID, c.Query("recovery_code")); err != nil { if errors.IsTwoFactorRecoveryCodeNotFound(err) { c.Flash.Error(c.Tr("auth.login_two_factor_invalid_recovery_code")) c.SubURLRedirect("/login/two_factor_recovery_code") } else { c.ServerError("UseRecoveryCode", err) } return } u, err := models.GetUserByID(userID) if err != nil { c.ServerError("GetUserByID", err) return } afterLogin(c, u, c.Session.Get("twoFactorRemember").(bool)) } //SignOut is the handler for "/user/logout" func SignOut(c *context.Context) { c.Session.Flush() c.Session.Destory(c.Context) c.SetCookie(setting.CookieUserName, "", -1, setting.AppSubURL) c.SetCookie(setting.CookieRememberName, "", -1, setting.AppSubURL) c.SetCookie(setting.CSRFCookieName, "", -1, setting.AppSubURL) c.SubURLRedirect("/") } //SignUp is the handler for "/signup" func SignUp(c *context.Context) { c.Title("sign_up") c.Data["EnableCaptcha"] = setting.Service.EnableCaptcha if setting.Service.DisableRegistration { c.Data["DisableRegistration"] = true c.Success(SignupTPL) return } c.Success(SignupTPL) } //SignUpPost is the POST handler for "/join" func SignUpPost(c *context.Context, cpt *captcha.Captcha, f form.Register) { c.Title("sign_up") c.Data["EnableCaptcha"] = setting.Service.EnableCaptcha if setting.Service.DisableRegistration { c.Status(403) return } if c.HasError() { c.Success(SignupTPL) return } if setting.Service.EnableCaptcha && !cpt.VerifyReq(c.Req) { c.FormErr("Captcha") c.RenderWithErr(c.Tr("form.captcha_incorrect"), SignupTPL, &f) return } if f.Password != f.Retype { c.FormErr("Password") c.RenderWithErr(c.Tr("form.password_not_match"), SignupTPL, &f) return } u := &models.User{ Name: f.UserName, Email: f.Email, Passwd: f.Password, ThemeColor: "#161616", IsActive: !setting.Service.RegisterEmailConfirm, } if err := models.CreateUser(u); err != nil { switch { case models.IsErrUserAlreadyExist(err): c.FormErr("UserName") c.RenderWithErr(c.Tr("form.username_been_taken"), SignupTPL, &f) case models.IsErrEmailAlreadyUsed(err): c.FormErr("Email") c.RenderWithErr(c.Tr("form.email_been_used"), SignupTPL, &f) case models.IsErrNameReserved(err): c.FormErr("UserName") c.RenderWithErr(c.Tr("user.form.name_reserved", err.(models.ErrNameReserved).Name), SignupTPL, &f) case models.IsErrNamePatternNotAllowed(err): c.FormErr("UserName") c.RenderWithErr(c.Tr("user.form.name_pattern_not_allowed", err.(models.ErrNamePatternNotAllowed).Pattern), SignupTPL, &f) default: c.ServerError("CreateUser", err) } return } log.Trace("Account created: %s", u.Name) // Auto-set admin for the only user. if models.CountUsers() == 1 { u.IsAdmin = true u.IsActive = true if err := models.UpdateUser(u); err != nil { c.ServerError("UpdateUser", err) return } } // Auto Follow the first user models.FollowUser(u.ID, 1) // Send confirmation email, no need for social account. if setting.Service.RegisterEmailConfirm && u.ID > 1 { mailer.SendActivateAccountMail(c.Context, models.NewMailerUser(u)) c.Data["IsSendRegisterMail"] = true c.Data["Email"] = u.Email c.Data["Hours"] = setting.Service.ActiveCodeLives / 60 c.Success(ActivateTPL) if err := c.Cache.Put(u.MailResendCacheKey(), 1, 180); err != nil { raven.CaptureErrorAndWait(err, nil) log.Error(2, "Failed to put cache key 'mail resend': %v", err) } return } c.SubURLRedirect("/login") } //Activate activates the code func Activate(c *context.Context) { code := c.Query("code") if len(code) == 0 { c.Data["IsActivatePage"] = true if c.User.IsActive { c.NotFound() return } // Resend confirmation email. if setting.Service.RegisterEmailConfirm { if c.Cache.IsExist(c.User.MailResendCacheKey()) { c.Data["ResendLimited"] = true } else { c.Data["Hours"] = setting.Service.ActiveCodeLives / 60 mailer.SendActivateAccountMail(c.Context, models.NewMailerUser(c.User)) if err := c.Cache.Put(c.User.MailResendCacheKey(), 1, 180); err != nil { raven.CaptureErrorAndWait(err, nil) log.Error(2, "Failed to put cache key 'mail resend': %v", err) } } } else { c.Data["ServiceNotEnabled"] = true } c.Success(ActivateTPL) return } // Verify code. if user := models.VerifyUserActiveCode(code); user != nil { user.IsActive = true var err error if user.Rands, err = models.GetUserSalt(); err != nil { c.ServerError("GetUserSalt", err) return } if err := models.UpdateUser(user); err != nil { c.ServerError("UpdateUser", err) return } log.Trace("User activated: %s", user.Name) c.Session.Set("uid", user.ID) c.Session.Set("uname", user.Name) c.SubURLRedirect("/") return } c.Data["IsActivateFailed"] = true c.Success(ActivateTPL) } //ActivateEmail is the handler to activate the user email func ActivateEmail(c *context.Context) { code := c.Query("code") emailString := c.Query("email") // Verify code. if email := models.VerifyActiveEmailCode(code, emailString); email != nil { if err := email.Activate(); err != nil { c.ServerError("ActivateEmail", err) } log.Trace("Email activated: %s", email.Email) c.Flash.Success(c.Tr("settings.add_email_success")) } c.SubURLRedirect("/user/settings/email") return } //ForgotPasswd is the handler for "/user/forget_password" func ForgotPasswd(c *context.Context) { c.Title("auth.forgot_password") if setting.MailService == nil { c.Data["IsResetDisable"] = true c.Success(ForgotPasswordTPL) return } c.Data["IsResetRequest"] = true c.Success(ForgotPasswordTPL) } //ForgotPasswdPost is the POST handler for "/user/forget_password" func ForgotPasswdPost(c *context.Context) { c.Title("auth.forgot_password") if setting.MailService == nil { c.Status(403) return } c.Data["IsResetRequest"] = true email := c.Query("email") c.Data["Email"] = email u, err := models.GetUserByEmail(email) if err != nil { if errors.IsUserNotExist(err) { c.Data["Hours"] = setting.Service.ActiveCodeLives / 60 c.Data["IsResetSent"] = true c.Success(ForgotPasswordTPL) return } c.ServerError("GetUserByEmail", err) return } if !u.IsLocal() { c.FormErr("Email") c.RenderWithErr(c.Tr("auth.non_local_account"), ForgotPasswordTPL, nil) return } if c.Cache.IsExist(u.MailResendCacheKey()) { c.Data["ResendLimited"] = true c.Success(ForgotPasswordTPL) return } mailer.SendResetPasswordMail(c.Context, models.NewMailerUser(u)) if err = c.Cache.Put(u.MailResendCacheKey(), 1, 180); err != nil { raven.CaptureErrorAndWait(err, nil) log.Error(2, "Failed to put cache key 'mail resend': %v", err) } c.Data["Hours"] = setting.Service.ActiveCodeLives / 60 c.Data["IsResetSent"] = true c.Success(ForgotPasswordTPL) } //ResetPasswd is the handler to reset your password func ResetPasswd(c *context.Context) { c.Title("auth.reset_password") code := c.Query("code") if len(code) == 0 { c.NotFound() return } c.Data["Code"] = code c.Data["IsResetForm"] = true c.Success(ResetPasswordTPL) } //ResetPasswdPost is the POST handler to reset your password func ResetPasswdPost(c *context.Context) { c.Title("auth.reset_password") code := c.Query("code") if len(code) == 0 { c.NotFound() return } c.Data["Code"] = code if u := models.VerifyUserActiveCode(code); u != nil { // Validate password length. passwd := c.Query("password") if len(passwd) < 6 { c.Data["IsResetForm"] = true c.Data["Err_Password"] = true c.RenderWithErr(c.Tr("auth.password_too_short"), ResetPasswordTPL, nil) return } u.Passwd = passwd var err error if u.Rands, err = models.GetUserSalt(); err != nil { c.ServerError("GetUserSalt", err) return } if u.Salt, err = models.GetUserSalt(); err != nil { c.ServerError("GetUserSalt", err) return } u.EncodePasswd() if err := models.UpdateUser(u); err != nil { c.ServerError("UpdateUser", err) return } log.Trace("User password reset: %s", u.Name) c.SubURLRedirect("/login") return } c.Data["IsResetFailed"] = true c.Success(ResetPasswordTPL) }