diff options
Diffstat (limited to 'internal/route/user')
-rw-r--r-- | internal/route/user/auth.go | 573 | ||||
-rw-r--r-- | internal/route/user/home.go | 424 | ||||
-rw-r--r-- | internal/route/user/profile.go | 126 | ||||
-rw-r--r-- | internal/route/user/setting.go | 669 |
4 files changed, 1792 insertions, 0 deletions
diff --git a/internal/route/user/auth.go b/internal/route/user/auth.go new file mode 100644 index 00000000..3613297b --- /dev/null +++ b/internal/route/user/auth.go @@ -0,0 +1,573 @@ +// Copyright 2014 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package user + +import ( + "fmt" + "net/url" + + "github.com/go-macaron/captcha" + log "gopkg.in/clog.v1" + + "gogs.io/gogs/internal/context" + "gogs.io/gogs/internal/db" + "gogs.io/gogs/internal/db/errors" + "gogs.io/gogs/internal/form" + "gogs.io/gogs/internal/mailer" + "gogs.io/gogs/internal/setting" + "gogs.io/gogs/internal/tool" +) + +const ( + LOGIN = "user/auth/login" + TWO_FACTOR = "user/auth/two_factor" + TWO_FACTOR_RECOVERY_CODE = "user/auth/two_factor_recovery_code" + SIGNUP = "user/auth/signup" + ACTIVATE = "user/auth/activate" + FORGOT_PASSWORD = "user/auth/forgot_passwd" + RESET_PASSWORD = "user/auth/reset_passwd" +) + +// AutoLogin reads cookie and try to auto-login. +func AutoLogin(c *context.Context) (bool, error) { + if !db.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 := db.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 +} + +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 := db.ActivatedLoginSources() + if err != nil { + c.ServerError("ActivatedLoginSources", err) + return + } + c.Data["LoginSources"] = loginSources + for i := range loginSources { + if loginSources[i].IsDefault { + c.Data["DefaultLoginSource"] = loginSources[i] + c.Data["login_source"] = loginSources[i].ID + break + } + } + c.Success(LOGIN) +} + +func afterLogin(c *context.Context, u *db.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("/") +} + +func LoginPost(c *context.Context, f form.SignIn) { + c.Title("sign_in") + + loginSources, err := db.ActivatedLoginSources() + if err != nil { + c.ServerError("ActivatedLoginSources", err) + return + } + c.Data["LoginSources"] = loginSources + + if c.HasError() { + c.Success(LOGIN) + return + } + + u, err := db.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"), LOGIN, &f) + case errors.LoginSourceMismatch: + c.FormErr("LoginSource") + c.RenderWithErr(c.Tr("form.auth_source_mismatch"), LOGIN, &f) + + default: + c.ServerError("UserLogin", err) + } + for i := range loginSources { + if loginSources[i].IsDefault { + c.Data["DefaultLoginSource"] = loginSources[i] + 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("/user/login/two_factor") +} + +func LoginTwoFactor(c *context.Context) { + _, ok := c.Session.Get("twoFactorUserID").(int64) + if !ok { + c.NotFound() + return + } + + c.Success(TWO_FACTOR) +} + +func LoginTwoFactorPost(c *context.Context) { + userID, ok := c.Session.Get("twoFactorUserID").(int64) + if !ok { + c.NotFound() + return + } + + t, err := db.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("/user/login/two_factor") + return + } + + u, err := db.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("/user/login/two_factor") + return + } + if err = c.Cache.Put(u.TwoFactorCacheKey(passcode), 1, 60); err != nil { + log.Error(2, "Failed to put cache 'two factor passcode': %v", err) + } + + afterLogin(c, u, c.Session.Get("twoFactorRemember").(bool)) +} + +func LoginTwoFactorRecoveryCode(c *context.Context) { + _, ok := c.Session.Get("twoFactorUserID").(int64) + if !ok { + c.NotFound() + return + } + + c.Success(TWO_FACTOR_RECOVERY_CODE) +} + +func LoginTwoFactorRecoveryCodePost(c *context.Context) { + userID, ok := c.Session.Get("twoFactorUserID").(int64) + if !ok { + c.NotFound() + return + } + + if err := db.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("/user/login/two_factor_recovery_code") + } else { + c.ServerError("UseRecoveryCode", err) + } + return + } + + u, err := db.GetUserByID(userID) + if err != nil { + c.ServerError("GetUserByID", err) + return + } + afterLogin(c, u, c.Session.Get("twoFactorRemember").(bool)) +} + +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("/") +} + +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(SIGNUP) + return + } + + c.Success(SIGNUP) +} + +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(SIGNUP) + return + } + + if setting.Service.EnableCaptcha && !cpt.VerifyReq(c.Req) { + c.FormErr("Captcha") + c.RenderWithErr(c.Tr("form.captcha_incorrect"), SIGNUP, &f) + return + } + + if f.Password != f.Retype { + c.FormErr("Password") + c.RenderWithErr(c.Tr("form.password_not_match"), SIGNUP, &f) + return + } + + u := &db.User{ + Name: f.UserName, + Email: f.Email, + Passwd: f.Password, + IsActive: !setting.Service.RegisterEmailConfirm, + } + if err := db.CreateUser(u); err != nil { + switch { + case db.IsErrUserAlreadyExist(err): + c.FormErr("UserName") + c.RenderWithErr(c.Tr("form.username_been_taken"), SIGNUP, &f) + case db.IsErrEmailAlreadyUsed(err): + c.FormErr("Email") + c.RenderWithErr(c.Tr("form.email_been_used"), SIGNUP, &f) + case db.IsErrNameReserved(err): + c.FormErr("UserName") + c.RenderWithErr(c.Tr("user.form.name_reserved", err.(db.ErrNameReserved).Name), SIGNUP, &f) + case db.IsErrNamePatternNotAllowed(err): + c.FormErr("UserName") + c.RenderWithErr(c.Tr("user.form.name_pattern_not_allowed", err.(db.ErrNamePatternNotAllowed).Pattern), SIGNUP, &f) + default: + c.ServerError("CreateUser", err) + } + return + } + log.Trace("Account created: %s", u.Name) + + // Auto-set admin for the only user. + if db.CountUsers() == 1 { + u.IsAdmin = true + u.IsActive = true + if err := db.UpdateUser(u); err != nil { + c.ServerError("UpdateUser", err) + return + } + } + + // Send confirmation email, no need for social account. + if setting.Service.RegisterEmailConfirm && u.ID > 1 { + mailer.SendActivateAccountMail(c.Context, db.NewMailerUser(u)) + c.Data["IsSendRegisterMail"] = true + c.Data["Email"] = u.Email + c.Data["Hours"] = setting.Service.ActiveCodeLives / 60 + c.Success(ACTIVATE) + + if err := c.Cache.Put(u.MailResendCacheKey(), 1, 180); err != nil { + log.Error(2, "Failed to put cache key 'mail resend': %v", err) + } + return + } + + c.SubURLRedirect("/user/login") +} + +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, db.NewMailerUser(c.User)) + + if err := c.Cache.Put(c.User.MailResendCacheKey(), 1, 180); err != nil { + log.Error(2, "Failed to put cache key 'mail resend': %v", err) + } + } + } else { + c.Data["ServiceNotEnabled"] = true + } + c.Success(ACTIVATE) + return + } + + // Verify code. + if user := db.VerifyUserActiveCode(code); user != nil { + user.IsActive = true + var err error + if user.Rands, err = db.GetUserSalt(); err != nil { + c.ServerError("GetUserSalt", err) + return + } + if err := db.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(ACTIVATE) +} + +func ActivateEmail(c *context.Context) { + code := c.Query("code") + email_string := c.Query("email") + + // Verify code. + if email := db.VerifyActiveEmailCode(code, email_string); 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 +} + +func ForgotPasswd(c *context.Context) { + c.Title("auth.forgot_password") + + if setting.MailService == nil { + c.Data["IsResetDisable"] = true + c.Success(FORGOT_PASSWORD) + return + } + + c.Data["IsResetRequest"] = true + c.Success(FORGOT_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 := db.GetUserByEmail(email) + if err != nil { + if errors.IsUserNotExist(err) { + c.Data["Hours"] = setting.Service.ActiveCodeLives / 60 + c.Data["IsResetSent"] = true + c.Success(FORGOT_PASSWORD) + return + } else { + c.ServerError("GetUserByEmail", err) + } + return + } + + if !u.IsLocal() { + c.FormErr("Email") + c.RenderWithErr(c.Tr("auth.non_local_account"), FORGOT_PASSWORD, nil) + return + } + + if c.Cache.IsExist(u.MailResendCacheKey()) { + c.Data["ResendLimited"] = true + c.Success(FORGOT_PASSWORD) + return + } + + mailer.SendResetPasswordMail(c.Context, db.NewMailerUser(u)) + if err = c.Cache.Put(u.MailResendCacheKey(), 1, 180); 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(FORGOT_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(RESET_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 := db.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"), RESET_PASSWORD, nil) + return + } + + u.Passwd = passwd + var err error + if u.Rands, err = db.GetUserSalt(); err != nil { + c.ServerError("GetUserSalt", err) + return + } + if u.Salt, err = db.GetUserSalt(); err != nil { + c.ServerError("GetUserSalt", err) + return + } + u.EncodePasswd() + if err := db.UpdateUser(u); err != nil { + c.ServerError("UpdateUser", err) + return + } + + log.Trace("User password reset: %s", u.Name) + c.SubURLRedirect("/user/login") + return + } + + c.Data["IsResetFailed"] = true + c.Success(RESET_PASSWORD) +} diff --git a/internal/route/user/home.go b/internal/route/user/home.go new file mode 100644 index 00000000..c411fae0 --- /dev/null +++ b/internal/route/user/home.go @@ -0,0 +1,424 @@ +// Copyright 2014 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package user + +import ( + "bytes" + "fmt" + + "github.com/unknwon/com" + "github.com/unknwon/paginater" + + "gogs.io/gogs/internal/context" + "gogs.io/gogs/internal/db" + "gogs.io/gogs/internal/db/errors" + "gogs.io/gogs/internal/setting" +) + +const ( + DASHBOARD = "user/dashboard/dashboard" + NEWS_FEED = "user/dashboard/feeds" + ISSUES = "user/dashboard/issues" + PROFILE = "user/profile" + ORG_HOME = "org/home" +) + +// getDashboardContextUser finds out dashboard is viewing as which context user. +func getDashboardContextUser(c *context.Context) *db.User { + ctxUser := c.User + orgName := c.Params(":org") + if len(orgName) > 0 { + // Organization. + org, err := db.GetUserByName(orgName) + if err != nil { + c.NotFoundOrServerError("GetUserByName", errors.IsUserNotExist, err) + return nil + } + ctxUser = org + } + c.Data["ContextUser"] = ctxUser + + if err := c.User.GetOrganizations(true); err != nil { + c.Handle(500, "GetOrganizations", err) + return nil + } + c.Data["Orgs"] = c.User.Orgs + + return ctxUser +} + +// retrieveFeeds loads feeds from database by given context user. +// The user could be organization so it is not always the logged in user, +// which is why we have to explicitly pass the context user ID. +func retrieveFeeds(c *context.Context, ctxUser *db.User, userID int64, isProfile bool) { + actions, err := db.GetFeeds(ctxUser, userID, c.QueryInt64("after_id"), isProfile) + if err != nil { + c.Handle(500, "GetFeeds", err) + return + } + + // Check access of private repositories. + feeds := make([]*db.Action, 0, len(actions)) + unameAvatars := make(map[string]string) + for _, act := range actions { + // Cache results to reduce queries. + _, ok := unameAvatars[act.ActUserName] + if !ok { + u, err := db.GetUserByName(act.ActUserName) + if err != nil { + if errors.IsUserNotExist(err) { + continue + } + c.Handle(500, "GetUserByName", err) + return + } + unameAvatars[act.ActUserName] = u.RelAvatarLink() + } + + act.ActAvatar = unameAvatars[act.ActUserName] + feeds = append(feeds, act) + } + c.Data["Feeds"] = feeds + if len(feeds) > 0 { + afterID := feeds[len(feeds)-1].ID + c.Data["AfterID"] = afterID + c.Header().Set("X-AJAX-URL", fmt.Sprintf("%s?after_id=%d", c.Data["Link"], afterID)) + } +} + +func Dashboard(c *context.Context) { + ctxUser := getDashboardContextUser(c) + if c.Written() { + return + } + + retrieveFeeds(c, ctxUser, c.User.ID, false) + if c.Written() { + return + } + + if c.Req.Header.Get("X-AJAX") == "true" { + c.HTML(200, NEWS_FEED) + return + } + + c.Data["Title"] = ctxUser.DisplayName() + " - " + c.Tr("dashboard") + c.Data["PageIsDashboard"] = true + c.Data["PageIsNews"] = true + + // Only user can have collaborative repositories. + if !ctxUser.IsOrganization() { + collaborateRepos, err := c.User.GetAccessibleRepositories(setting.UI.User.RepoPagingNum) + if err != nil { + c.Handle(500, "GetAccessibleRepositories", err) + return + } else if err = db.RepositoryList(collaborateRepos).LoadAttributes(); err != nil { + c.Handle(500, "RepositoryList.LoadAttributes", err) + return + } + c.Data["CollaborativeRepos"] = collaborateRepos + } + + var err error + var repos, mirrors []*db.Repository + var repoCount int64 + if ctxUser.IsOrganization() { + repos, repoCount, err = ctxUser.GetUserRepositories(c.User.ID, 1, setting.UI.User.RepoPagingNum) + if err != nil { + c.Handle(500, "GetUserRepositories", err) + return + } + + mirrors, err = ctxUser.GetUserMirrorRepositories(c.User.ID) + if err != nil { + c.Handle(500, "GetUserMirrorRepositories", err) + return + } + } else { + if err = ctxUser.GetRepositories(1, setting.UI.User.RepoPagingNum); err != nil { + c.Handle(500, "GetRepositories", err) + return + } + repos = ctxUser.Repos + repoCount = int64(ctxUser.NumRepos) + + mirrors, err = ctxUser.GetMirrorRepositories() + if err != nil { + c.Handle(500, "GetMirrorRepositories", err) + return + } + } + c.Data["Repos"] = repos + c.Data["RepoCount"] = repoCount + c.Data["MaxShowRepoNum"] = setting.UI.User.RepoPagingNum + + if err := db.MirrorRepositoryList(mirrors).LoadAttributes(); err != nil { + c.Handle(500, "MirrorRepositoryList.LoadAttributes", err) + return + } + c.Data["MirrorCount"] = len(mirrors) + c.Data["Mirrors"] = mirrors + + c.HTML(200, DASHBOARD) +} + +func Issues(c *context.Context) { + isPullList := c.Params(":type") == "pulls" + if isPullList { + c.Data["Title"] = c.Tr("pull_requests") + c.Data["PageIsPulls"] = true + } else { + c.Data["Title"] = c.Tr("issues") + c.Data["PageIsIssues"] = true + } + + ctxUser := getDashboardContextUser(c) + if c.Written() { + return + } + + var ( + sortType = c.Query("sort") + filterMode = db.FILTER_MODE_YOUR_REPOS + ) + + // Note: Organization does not have view type and filter mode. + if !ctxUser.IsOrganization() { + viewType := c.Query("type") + types := []string{ + string(db.FILTER_MODE_YOUR_REPOS), + string(db.FILTER_MODE_ASSIGN), + string(db.FILTER_MODE_CREATE), + } + if !com.IsSliceContainsStr(types, viewType) { + viewType = string(db.FILTER_MODE_YOUR_REPOS) + } + filterMode = db.FilterMode(viewType) + } + + page := c.QueryInt("page") + if page <= 1 { + page = 1 + } + + repoID := c.QueryInt64("repo") + isShowClosed := c.Query("state") == "closed" + + // Get repositories. + var ( + err error + repos []*db.Repository + userRepoIDs []int64 + showRepos = make([]*db.Repository, 0, 10) + ) + if ctxUser.IsOrganization() { + repos, _, err = ctxUser.GetUserRepositories(c.User.ID, 1, ctxUser.NumRepos) + if err != nil { + c.Handle(500, "GetRepositories", err) + return + } + } else { + if err := ctxUser.GetRepositories(1, c.User.NumRepos); err != nil { + c.Handle(500, "GetRepositories", err) + return + } + repos = ctxUser.Repos + } + + userRepoIDs = make([]int64, 0, len(repos)) + for _, repo := range repos { + userRepoIDs = append(userRepoIDs, repo.ID) + + if filterMode != db.FILTER_MODE_YOUR_REPOS { + continue + } + + if isPullList { + if isShowClosed && repo.NumClosedPulls == 0 || + !isShowClosed && repo.NumOpenPulls == 0 { + continue + } + } else { + if !repo.EnableIssues || repo.EnableExternalTracker || + isShowClosed && repo.NumClosedIssues == 0 || + !isShowClosed && repo.NumOpenIssues == 0 { + continue + } + } + + showRepos = append(showRepos, repo) + } + + // Filter repositories if the page shows issues. + if !isPullList { + userRepoIDs, err = db.FilterRepositoryWithIssues(userRepoIDs) + if err != nil { + c.Handle(500, "FilterRepositoryWithIssues", err) + return + } + } + + issueOptions := &db.IssuesOptions{ + RepoID: repoID, + Page: page, + IsClosed: isShowClosed, + IsPull: isPullList, + SortType: sortType, + } + switch filterMode { + case db.FILTER_MODE_YOUR_REPOS: + // Get all issues from repositories from this user. + if userRepoIDs == nil { + issueOptions.RepoIDs = []int64{-1} + } else { + issueOptions.RepoIDs = userRepoIDs + } + + case db.FILTER_MODE_ASSIGN: + // Get all issues assigned to this user. + issueOptions.AssigneeID = ctxUser.ID + + case db.FILTER_MODE_CREATE: + // Get all issues created by this user. + issueOptions.PosterID = ctxUser.ID + } + + issues, err := db.Issues(issueOptions) + if err != nil { + c.Handle(500, "Issues", err) + return + } + + if repoID > 0 { + repo, err := db.GetRepositoryByID(repoID) + if err != nil { + c.Handle(500, "GetRepositoryByID", fmt.Errorf("[#%d] %v", repoID, err)) + return + } + + if err = repo.GetOwner(); err != nil { + c.Handle(500, "GetOwner", fmt.Errorf("[#%d] %v", repoID, err)) + return + } + + // Check if user has access to given repository. + if !repo.IsOwnedBy(ctxUser.ID) && !repo.HasAccess(ctxUser.ID) { + c.Handle(404, "Issues", fmt.Errorf("#%d", repoID)) + return + } + } + + for _, issue := range issues { + if err = issue.Repo.GetOwner(); err != nil { + c.Handle(500, "GetOwner", fmt.Errorf("[#%d] %v", issue.RepoID, err)) + return + } + } + + issueStats := db.GetUserIssueStats(repoID, ctxUser.ID, userRepoIDs, filterMode, isPullList) + + var total int + if !isShowClosed { + total = int(issueStats.OpenCount) + } else { + total = int(issueStats.ClosedCount) + } + + c.Data["Issues"] = issues + c.Data["Repos"] = showRepos + c.Data["Page"] = paginater.New(total, setting.UI.IssuePagingNum, page, 5) + c.Data["IssueStats"] = issueStats + c.Data["ViewType"] = string(filterMode) + c.Data["SortType"] = sortType + c.Data["RepoID"] = repoID + c.Data["IsShowClosed"] = isShowClosed + + if isShowClosed { + c.Data["State"] = "closed" + } else { + c.Data["State"] = "open" + } + + c.HTML(200, ISSUES) +} + +func ShowSSHKeys(c *context.Context, uid int64) { + keys, err := db.ListPublicKeys(uid) + if err != nil { + c.Handle(500, "ListPublicKeys", err) + return + } + + var buf bytes.Buffer + for i := range keys { + buf.WriteString(keys[i].OmitEmail()) + buf.WriteString("\n") + } + c.PlainText(200, buf.Bytes()) +} + +func showOrgProfile(c *context.Context) { + c.SetParams(":org", c.Params(":username")) + context.HandleOrgAssignment(c) + if c.Written() { + return + } + + org := c.Org.Organization + c.Data["Title"] = org.FullName + + page := c.QueryInt("page") + if page <= 0 { + page = 1 + } + + var ( + repos []*db.Repository + count int64 + err error + ) + if c.IsLogged && !c.User.IsAdmin { + repos, count, err = org.GetUserRepositories(c.User.ID, page, setting.UI.User.RepoPagingNum) + if err != nil { + c.Handle(500, "GetUserRepositories", err) + return + } + c.Data["Repos"] = repos + } else { + showPrivate := c.IsLogged && c.User.IsAdmin + repos, err = db.GetUserRepositories(&db.UserRepoOptions{ + UserID: org.ID, + Private: showPrivate, + Page: page, + PageSize: setting.UI.User.RepoPagingNum, + }) + if err != nil { + c.Handle(500, "GetRepositories", err) + return + } + c.Data["Repos"] = repos + count = db.CountUserRepositories(org.ID, showPrivate) + } + c.Data["Page"] = paginater.New(int(count), setting.UI.User.RepoPagingNum, page, 5) + + if err := org.GetMembers(); err != nil { + c.Handle(500, "GetMembers", err) + return + } + c.Data["Members"] = org.Members + + c.Data["Teams"] = org.Teams + + c.HTML(200, ORG_HOME) +} + +func Email2User(c *context.Context) { + u, err := db.GetUserByEmail(c.Query("email")) + if err != nil { + c.NotFoundOrServerError("GetUserByEmail", errors.IsUserNotExist, err) + return + } + c.Redirect(setting.AppSubURL + "/user/" + u.Name) +} diff --git a/internal/route/user/profile.go b/internal/route/user/profile.go new file mode 100644 index 00000000..39d36ad0 --- /dev/null +++ b/internal/route/user/profile.go @@ -0,0 +1,126 @@ +// Copyright 2015 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package user + +import ( + "fmt" + repo2 "gogs.io/gogs/internal/route/repo" + "strings" + + "github.com/unknwon/paginater" + + "gogs.io/gogs/internal/context" + "gogs.io/gogs/internal/db" + "gogs.io/gogs/internal/setting" + "gogs.io/gogs/internal/tool" +) + +const ( + FOLLOWERS = "user/meta/followers" + STARS = "user/meta/stars" +) + +func Profile(c *context.Context, puser *context.ParamsUser) { + isShowKeys := false + if strings.HasSuffix(c.Params(":username"), ".keys") { + isShowKeys = true + } + + // Show SSH keys. + if isShowKeys { + ShowSSHKeys(c, puser.ID) + return + } + + if puser.IsOrganization() { + showOrgProfile(c) + return + } + + c.Title(puser.DisplayName()) + c.PageIs("UserProfile") + c.Data["Owner"] = puser + + orgs, err := db.GetOrgsByUserID(puser.ID, c.IsLogged && (c.User.IsAdmin || c.User.ID == puser.ID)) + if err != nil { + c.ServerError("GetOrgsByUserIDDesc", err) + return + } + + c.Data["Orgs"] = orgs + + tab := c.Query("tab") + c.Data["TabName"] = tab + switch tab { + case "activity": + retrieveFeeds(c, puser.User, -1, true) + if c.Written() { + return + } + default: + page := c.QueryInt("page") + if page <= 0 { + page = 1 + } + + showPrivate := c.IsLogged && (puser.ID == c.User.ID || c.User.IsAdmin) + c.Data["Repos"], err = db.GetUserRepositories(&db.UserRepoOptions{ + UserID: puser.ID, + Private: showPrivate, + Page: page, + PageSize: setting.UI.User.RepoPagingNum, + }) + if err != nil { + c.ServerError("GetRepositories", err) + return + } + + count := db.CountUserRepositories(puser.ID, showPrivate) + c.Data["Page"] = paginater.New(int(count), setting.UI.User.RepoPagingNum, page, 5) + } + + c.Success(PROFILE) +} + +func Followers(c *context.Context, puser *context.ParamsUser) { + c.Title(puser.DisplayName()) + c.PageIs("Followers") + c.Data["CardsTitle"] = c.Tr("user.followers") + c.Data["Owner"] = puser + repo2.RenderUserCards(c, puser.NumFollowers, puser.GetFollowers, FOLLOWERS) +} + +func Following(c *context.Context, puser *context.ParamsUser) { + c.Title(puser.DisplayName()) + c.PageIs("Following") + c.Data["CardsTitle"] = c.Tr("user.following") + c.Data["Owner"] = puser + repo2.RenderUserCards(c, puser.NumFollowing, puser.GetFollowing, FOLLOWERS) +} + +func Stars(c *context.Context) { + +} + +func Action(c *context.Context, puser *context.ParamsUser) { + var err error + switch c.Params(":action") { + case "follow": + err = db.FollowUser(c.UserID(), puser.ID) + case "unfollow": + err = db.UnfollowUser(c.UserID(), puser.ID) + } + + if err != nil { + c.ServerError(fmt.Sprintf("Action (%s)", c.Params(":action")), err) + return + } + + redirectTo := c.Query("redirect_to") + if !tool.IsSameSiteURLPath(redirectTo) { + redirectTo = puser.HomeLink() + } + c.Redirect(redirectTo) +} diff --git a/internal/route/user/setting.go b/internal/route/user/setting.go new file mode 100644 index 00000000..6b55966f --- /dev/null +++ b/internal/route/user/setting.go @@ -0,0 +1,669 @@ +// Copyright 2014 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package user + +import ( + "bytes" + "encoding/base64" + "fmt" + "html/template" + "image/png" + "io/ioutil" + "strings" + + "github.com/pquerna/otp" + "github.com/pquerna/otp/totp" + "github.com/unknwon/com" + log "gopkg.in/clog.v1" + + "gogs.io/gogs/internal/context" + "gogs.io/gogs/internal/db" + "gogs.io/gogs/internal/db/errors" + "gogs.io/gogs/internal/form" + "gogs.io/gogs/internal/mailer" + "gogs.io/gogs/internal/setting" + "gogs.io/gogs/internal/tool" +) + +const ( + SETTINGS_PROFILE = "user/settings/profile" + SETTINGS_AVATAR = "user/settings/avatar" + SETTINGS_PASSWORD = "user/settings/password" + SETTINGS_EMAILS = "user/settings/email" + SETTINGS_SSH_KEYS = "user/settings/sshkeys" + SETTINGS_SECURITY = "user/settings/security" + SETTINGS_TWO_FACTOR_ENABLE = "user/settings/two_factor_enable" + SETTINGS_TWO_FACTOR_RECOVERY_CODES = "user/settings/two_factor_recovery_codes" + SETTINGS_REPOSITORIES = "user/settings/repositories" + SETTINGS_ORGANIZATIONS = "user/settings/organizations" + SETTINGS_APPLICATIONS = "user/settings/applications" + SETTINGS_DELETE = "user/settings/delete" + NOTIFICATION = "user/notification" +) + +func Settings(c *context.Context) { + c.Title("settings.profile") + c.PageIs("SettingsProfile") + c.Data["origin_name"] = c.User.Name + c.Data["name"] = c.User.Name + c.Data["full_name"] = c.User.FullName + c.Data["email"] = c.User.Email + c.Data["website"] = c.User.Website + c.Data["location"] = c.User.Location + c.Success(SETTINGS_PROFILE) +} + +func SettingsPost(c *context.Context, f form.UpdateProfile) { + c.Title("settings.profile") + c.PageIs("SettingsProfile") + c.Data["origin_name"] = c.User.Name + + if c.HasError() { + c.Success(SETTINGS_PROFILE) + return + } + + // Non-local users are not allowed to change their username + if c.User.IsLocal() { + // Check if username characters have been changed + if c.User.LowerName != strings.ToLower(f.Name) { + if err := db.ChangeUserName(c.User, f.Name); err != nil { + c.FormErr("Name") + var msg string + switch { + case db.IsErrUserAlreadyExist(err): + msg = c.Tr("form.username_been_taken") + case db.IsErrEmailAlreadyUsed(err): + msg = c.Tr("form.email_been_used") + case db.IsErrNameReserved(err): + msg = c.Tr("form.name_reserved") + case db.IsErrNamePatternNotAllowed(err): + msg = c.Tr("form.name_pattern_not_allowed") + default: + c.ServerError("ChangeUserName", err) + return + } + + c.RenderWithErr(msg, SETTINGS_PROFILE, &f) + return + } + + log.Trace("Username changed: %s -> %s", c.User.Name, f.Name) + } + + // In case it's just a case change + c.User.Name = f.Name + c.User.LowerName = strings.ToLower(f.Name) + } + + c.User.FullName = f.FullName + c.User.Email = f.Email + c.User.Website = f.Website + c.User.Location = f.Location + if err := db.UpdateUser(c.User); err != nil { + c.ServerError("UpdateUser", err) + return + } + + c.Flash.Success(c.Tr("settings.update_profile_success")) + c.SubURLRedirect("/user/settings") +} + +// FIXME: limit upload size +func UpdateAvatarSetting(c *context.Context, f form.Avatar, ctxUser *db.User) error { + ctxUser.UseCustomAvatar = f.Source == form.AVATAR_LOCAL + if len(f.Gravatar) > 0 { + ctxUser.Avatar = tool.MD5(f.Gravatar) + ctxUser.AvatarEmail = f.Gravatar + } + + if f.Avatar != nil && f.Avatar.Filename != "" { + r, err := f.Avatar.Open() + if err != nil { + return fmt.Errorf("open avatar reader: %v", err) + } + defer r.Close() + + data, err := ioutil.ReadAll(r) + if err != nil { + return fmt.Errorf("read avatar content: %v", err) + } + if !tool.IsImageFile(data) { + return errors.New(c.Tr("settings.uploaded_avatar_not_a_image")) + } + if err = ctxUser.UploadAvatar(data); err != nil { + return fmt.Errorf("upload avatar: %v", err) + } + } else { + // No avatar is uploaded but setting has been changed to enable, + // generate a random one when needed. + if ctxUser.UseCustomAvatar && !com.IsFile(ctxUser.CustomAvatarPath()) { + if err := ctxUser.GenerateRandomAvatar(); err != nil { + log.Error(2, "generate random avatar [%d]: %v", ctxUser.ID, err) + } + } + } + + if err := db.UpdateUser(ctxUser); err != nil { + return fmt.Errorf("update user: %v", err) + } + + return nil +} + +func SettingsAvatar(c *context.Context) { + c.Title("settings.avatar") + c.PageIs("SettingsAvatar") + c.Success(SETTINGS_AVATAR) +} + +func SettingsAvatarPost(c *context.Context, f form.Avatar) { + if err := UpdateAvatarSetting(c, f, c.User); err != nil { + c.Flash.Error(err.Error()) + } else { + c.Flash.Success(c.Tr("settings.update_avatar_success")) + } + + c.SubURLRedirect("/user/settings/avatar") +} + +func SettingsDeleteAvatar(c *context.Context) { + if err := c.User.DeleteAvatar(); err != nil { + c.Flash.Error(fmt.Sprintf("Failed to delete avatar: %v", err)) + } + + c.SubURLRedirect("/user/settings/avatar") +} + +func SettingsPassword(c *context.Context) { + c.Title("settings.password") + c.PageIs("SettingsPassword") + c.Success(SETTINGS_PASSWORD) +} + +func SettingsPasswordPost(c *context.Context, f form.ChangePassword) { + c.Title("settings.password") + c.PageIs("SettingsPassword") + + if c.HasError() { + c.Success(SETTINGS_PASSWORD) + return + } + + if !c.User.ValidatePassword(f.OldPassword) { + c.Flash.Error(c.Tr("settings.password_incorrect")) + } else if f.Password != f.Retype { + c.Flash.Error(c.Tr("form.password_not_match")) + } else { + c.User.Passwd = f.Password + var err error + if c.User.Salt, err = db.GetUserSalt(); err != nil { + c.ServerError("GetUserSalt", err) + return + } + c.User.EncodePasswd() + if err := db.UpdateUser(c.User); err != nil { + c.ServerError("UpdateUser", err) + return + } + c.Flash.Success(c.Tr("settings.change_password_success")) + } + + c.SubURLRedirect("/user/settings/password") +} + +func SettingsEmails(c *context.Context) { + c.Title("settings.emails") + c.PageIs("SettingsEmails") + + emails, err := db.GetEmailAddresses(c.User.ID) + if err != nil { + c.ServerError("GetEmailAddresses", err) + return + } + c.Data["Emails"] = emails + + c.Success(SETTINGS_EMAILS) +} + +func SettingsEmailPost(c *context.Context, f form.AddEmail) { + c.Title("settings.emails") + c.PageIs("SettingsEmails") + + // Make emailaddress primary. + if c.Query("_method") == "PRIMARY" { + if err := db.MakeEmailPrimary(&db.EmailAddress{ID: c.QueryInt64("id")}); err != nil { + c.ServerError("MakeEmailPrimary", err) + return + } + + c.SubURLRedirect("/user/settings/email") + return + } + + // Add Email address. + emails, err := db.GetEmailAddresses(c.User.ID) + if err != nil { + c.ServerError("GetEmailAddresses", err) + return + } + c.Data["Emails"] = emails + + if c.HasError() { + c.Success(SETTINGS_EMAILS) + return + } + + email := &db.EmailAddress{ + UID: c.User.ID, + Email: f.Email, + IsActivated: !setting.Service.RegisterEmailConfirm, + } + if err := db.AddEmailAddress(email); err != nil { + if db.IsErrEmailAlreadyUsed(err) { + c.RenderWithErr(c.Tr("form.email_been_used"), SETTINGS_EMAILS, &f) + } else { + c.ServerError("AddEmailAddress", err) + } + return + } + + // Send confirmation email + if setting.Service.RegisterEmailConfirm { + mailer.SendActivateEmailMail(c.Context, db.NewMailerUser(c.User), email.Email) + + if err := c.Cache.Put("MailResendLimit_"+c.User.LowerName, c.User.LowerName, 180); err != nil { + log.Error(2, "Set cache 'MailResendLimit' failed: %v", err) + } + c.Flash.Info(c.Tr("settings.add_email_confirmation_sent", email.Email, setting.Service.ActiveCodeLives/60)) + } else { + c.Flash.Success(c.Tr("settings.add_email_success")) + } + + c.SubURLRedirect("/user/settings/email") +} + +func DeleteEmail(c *context.Context) { + if err := db.DeleteEmailAddress(&db.EmailAddress{ + ID: c.QueryInt64("id"), + UID: c.User.ID, + }); err != nil { + c.ServerError("DeleteEmailAddress", err) + return + } + + c.Flash.Success(c.Tr("settings.email_deletion_success")) + c.JSONSuccess(map[string]interface{}{ + "redirect": setting.AppSubURL + "/user/settings/email", + }) +} + +func SettingsSSHKeys(c *context.Context) { + c.Title("settings.ssh_keys") + c.PageIs("SettingsSSHKeys") + + keys, err := db.ListPublicKeys(c.User.ID) + if err != nil { + c.ServerError("ListPublicKeys", err) + return + } + c.Data["Keys"] = keys + + c.Success(SETTINGS_SSH_KEYS) +} + +func SettingsSSHKeysPost(c *context.Context, f form.AddSSHKey) { + c.Title("settings.ssh_keys") + c.PageIs("SettingsSSHKeys") + + keys, err := db.ListPublicKeys(c.User.ID) + if err != nil { + c.ServerError("ListPublicKeys", err) + return + } + c.Data["Keys"] = keys + + if c.HasError() { + c.Success(SETTINGS_SSH_KEYS) + return + } + + content, err := db.CheckPublicKeyString(f.Content) + if err != nil { + if db.IsErrKeyUnableVerify(err) { + c.Flash.Info(c.Tr("form.unable_verify_ssh_key")) + } else { + c.Flash.Error(c.Tr("form.invalid_ssh_key", err.Error())) + c.SubURLRedirect("/user/settings/ssh") + return + } + } + + if _, err = db.AddPublicKey(c.User.ID, f.Title, content); err != nil { + c.Data["HasError"] = true + switch { + case db.IsErrKeyAlreadyExist(err): + c.FormErr("Content") + c.RenderWithErr(c.Tr("settings.ssh_key_been_used"), SETTINGS_SSH_KEYS, &f) + case db.IsErrKeyNameAlreadyUsed(err): + c.FormErr("Title") + c.RenderWithErr(c.Tr("settings.ssh_key_name_used"), SETTINGS_SSH_KEYS, &f) + default: + c.ServerError("AddPublicKey", err) + } + return + } + + c.Flash.Success(c.Tr("settings.add_key_success", f.Title)) + c.SubURLRedirect("/user/settings/ssh") +} + +func DeleteSSHKey(c *context.Context) { + if err := db.DeletePublicKey(c.User, c.QueryInt64("id")); err != nil { + c.Flash.Error("DeletePublicKey: " + err.Error()) + } else { + c.Flash.Success(c.Tr("settings.ssh_key_deletion_success")) + } + + c.JSONSuccess(map[string]interface{}{ + "redirect": setting.AppSubURL + "/user/settings/ssh", + }) +} + +func SettingsSecurity(c *context.Context) { + c.Title("settings.security") + c.PageIs("SettingsSecurity") + + t, err := db.GetTwoFactorByUserID(c.UserID()) + if err != nil && !errors.IsTwoFactorNotFound(err) { + c.ServerError("GetTwoFactorByUserID", err) + return + } + c.Data["TwoFactor"] = t + + c.Success(SETTINGS_SECURITY) +} + +func SettingsTwoFactorEnable(c *context.Context) { + if c.User.IsEnabledTwoFactor() { + c.NotFound() + return + } + + c.Title("settings.two_factor_enable_title") + c.PageIs("SettingsSecurity") + + var key *otp.Key + var err error + keyURL := c.Session.Get("twoFactorURL") + if keyURL != nil { + key, _ = otp.NewKeyFromURL(keyURL.(string)) + } + if key == nil { + key, err = totp.Generate(totp.GenerateOpts{ + Issuer: setting.AppName, + AccountName: c.User.Email, + }) + if err != nil { + c.ServerError("Generate", err) + return + } + } + c.Data["TwoFactorSecret"] = key.Secret() + + img, err := key.Image(240, 240) + if err != nil { + c.ServerError("Image", err) + return + } + + var buf bytes.Buffer + if err = png.Encode(&buf, img); err != nil { + c.ServerError("Encode", err) + return + } + c.Data["QRCode"] = template.URL("data:image/png;base64," + base64.StdEncoding.EncodeToString(buf.Bytes())) + + c.Session.Set("twoFactorSecret", c.Data["TwoFactorSecret"]) + c.Session.Set("twoFactorURL", key.String()) + c.Success(SETTINGS_TWO_FACTOR_ENABLE) +} + +func SettingsTwoFactorEnablePost(c *context.Context) { + secret, ok := c.Session.Get("twoFactorSecret").(string) + if !ok { + c.NotFound() + return + } + + if !totp.Validate(c.Query("passcode"), secret) { + c.Flash.Error(c.Tr("settings.two_factor_invalid_passcode")) + c.SubURLRedirect("/user/settings/security/two_factor_enable") + return + } + + if err := db.NewTwoFactor(c.UserID(), secret); err != nil { + c.Flash.Error(c.Tr("settings.two_factor_enable_error", err)) + c.SubURLRedirect("/user/settings/security/two_factor_enable") + return + } + + c.Session.Delete("twoFactorSecret") + c.Session.Delete("twoFactorURL") + c.Flash.Success(c.Tr("settings.two_factor_enable_success")) + c.SubURLRedirect("/user/settings/security/two_factor_recovery_codes") +} + +func SettingsTwoFactorRecoveryCodes(c *context.Context) { + if !c.User.IsEnabledTwoFactor() { + c.NotFound() + return + } + + c.Title("settings.two_factor_recovery_codes_title") + c.PageIs("SettingsSecurity") + + recoveryCodes, err := db.GetRecoveryCodesByUserID(c.UserID()) + if err != nil { + c.ServerError("GetRecoveryCodesByUserID", err) + return + } + c.Data["RecoveryCodes"] = recoveryCodes + + c.Success(SETTINGS_TWO_FACTOR_RECOVERY_CODES) +} + +func SettingsTwoFactorRecoveryCodesPost(c *context.Context) { + if !c.User.IsEnabledTwoFactor() { + c.NotFound() + return + } + + if err := db.RegenerateRecoveryCodes(c.UserID()); err != nil { + c.Flash.Error(c.Tr("settings.two_factor_regenerate_recovery_codes_error", err)) + } else { + c.Flash.Success(c.Tr("settings.two_factor_regenerate_recovery_codes_success")) + } + + c.SubURLRedirect("/user/settings/security/two_factor_recovery_codes") +} + +func SettingsTwoFactorDisable(c *context.Context) { + if !c.User.IsEnabledTwoFactor() { + c.NotFound() + return + } + + if err := db.DeleteTwoFactor(c.UserID()); err != nil { + c.ServerError("DeleteTwoFactor", err) + return + } + + c.Flash.Success(c.Tr("settings.two_factor_disable_success")) + c.JSONSuccess(map[string]interface{}{ + "redirect": setting.AppSubURL + "/user/settings/security", + }) +} + +func SettingsRepos(c *context.Context) { + c.Title("settings.repos") + c.PageIs("SettingsRepositories") + + repos, err := db.GetUserAndCollaborativeRepositories(c.User.ID) + if err != nil { + c.ServerError("GetUserAndCollaborativeRepositories", err) + return + } + if err = db.RepositoryList(repos).LoadAttributes(); err != nil { + c.ServerError("LoadAttributes", err) + return + } + c.Data["Repos"] = repos + + c.Success(SETTINGS_REPOSITORIES) +} + +func SettingsLeaveRepo(c *context.Context) { + repo, err := db.GetRepositoryByID(c.QueryInt64("id")) + if err != nil { + c.NotFoundOrServerError("GetRepositoryByID", errors.IsRepoNotExist, err) + return + } + + if err = repo.DeleteCollaboration(c.User.ID); err != nil { + c.ServerError("DeleteCollaboration", err) + return + } + + c.Flash.Success(c.Tr("settings.repos.leave_success", repo.FullName())) + c.JSONSuccess(map[string]interface{}{ + "redirect": setting.AppSubURL + "/user/settings/repositories", + }) +} + +func SettingsOrganizations(c *context.Context) { + c.Title("settings.orgs") + c.PageIs("SettingsOrganizations") + + orgs, err := db.GetOrgsByUserID(c.User.ID, true) + if err != nil { + c.ServerError("GetOrgsByUserID", err) + return + } + c.Data["Orgs"] = orgs + + c.Success(SETTINGS_ORGANIZATIONS) +} + +func SettingsLeaveOrganization(c *context.Context) { + if err := db.RemoveOrgUser(c.QueryInt64("id"), c.User.ID); err != nil { + if db.IsErrLastOrgOwner(err) { + c.Flash.Error(c.Tr("form.last_org_owner")) + } else { + c.ServerError("RemoveOrgUser", err) + return + } + } + + c.JSONSuccess(map[string]interface{}{ + "redirect": setting.AppSubURL + "/user/settings/organizations", + }) +} + +func SettingsApplications(c *context.Context) { + c.Title("settings.applications") + c.PageIs("SettingsApplications") + + tokens, err := db.ListAccessTokens(c.User.ID) + if err != nil { + c.ServerError("ListAccessTokens", err) + return + } + c.Data["Tokens"] = tokens + + c.Success(SETTINGS_APPLICATIONS) +} + +func SettingsApplicationsPost(c *context.Context, f form.NewAccessToken) { + c.Title("settings.applications") + c.PageIs("SettingsApplications") + + if c.HasError() { + tokens, err := db.ListAccessTokens(c.User.ID) + if err != nil { + c.ServerError("ListAccessTokens", err) + return + } + + c.Data["Tokens"] = tokens + c.Success(SETTINGS_APPLICATIONS) + return + } + + t := &db.AccessToken{ + UID: c.User.ID, + Name: f.Name, + } + if err := db.NewAccessToken(t); err != nil { + if errors.IsAccessTokenNameAlreadyExist(err) { + c.Flash.Error(c.Tr("settings.token_name_exists")) + c.SubURLRedirect("/user/settings/applications") + } else { + c.ServerError("NewAccessToken", err) + } + return + } + + c.Flash.Success(c.Tr("settings.generate_token_succees")) + c.Flash.Info(t.Sha1) + c.SubURLRedirect("/user/settings/applications") +} + +func SettingsDeleteApplication(c *context.Context) { + if err := db.DeleteAccessTokenOfUserByID(c.User.ID, c.QueryInt64("id")); err != nil { + c.Flash.Error("DeleteAccessTokenByID: " + err.Error()) + } else { + c.Flash.Success(c.Tr("settings.delete_token_success")) + } + + c.JSONSuccess(map[string]interface{}{ + "redirect": setting.AppSubURL + "/user/settings/applications", + }) +} + +func SettingsDelete(c *context.Context) { + c.Title("settings.delete") + c.PageIs("SettingsDelete") + + if c.Req.Method == "POST" { + if _, err := db.UserLogin(c.User.Name, c.Query("password"), c.User.LoginSource); err != nil { + if errors.IsUserNotExist(err) { + c.RenderWithErr(c.Tr("form.enterred_invalid_password"), SETTINGS_DELETE, nil) + } else { + c.ServerError("UserLogin", err) + } + return + } + + if err := db.DeleteUser(c.User); err != nil { + switch { + case db.IsErrUserOwnRepos(err): + c.Flash.Error(c.Tr("form.still_own_repo")) + c.Redirect(setting.AppSubURL + "/user/settings/delete") + case db.IsErrUserHasOrgs(err): + c.Flash.Error(c.Tr("form.still_has_org")) + c.Redirect(setting.AppSubURL + "/user/settings/delete") + default: + c.ServerError("DeleteUser", err) + } + } else { + log.Trace("Account deleted: %s", c.User.Name) + c.Redirect(setting.AppSubURL + "/") + } + return + } + + c.Success(SETTINGS_DELETE) +} |