From 099372d76c411c598285d637bd85c9b2dbc40948 Mon Sep 17 00:00:00 2001 From: David Schneiderbauer Date: Tue, 15 May 2018 12:07:32 +0200 Subject: [PATCH] Refactor User Settings (#3900) * moved avatar to profile page * combined password change, email and account deletion into account settings page * combined totp, access tokens, linked accounts and openid into security settings page * move access tokens to applications settings page * small change to restart drone build * fix change avatar url on profile page * redirect old settings urls to new ones * enforce only one autofocus attribute on settings pages * set correct redirect status code * fmt fix --- integrations/delete_user_test.go | 8 +- integrations/links_test.go | 7 +- options/locale/locale_en-US.ini | 3 +- routers/routes/routes.go | 66 +-- routers/user/setting.go | 379 +++++++++--------- routers/user/setting_openid.go | 47 +-- routers/user/setting_test.go | 2 +- templates/user/profile.tmpl | 2 +- templates/user/settings/account.tmpl | 135 +++++++ templates/user/settings/account_link.tmpl | 44 -- templates/user/settings/applications.tmpl | 33 +- templates/user/settings/avatar.tmpl | 46 --- templates/user/settings/delete.tmpl | 41 -- templates/user/settings/email.tmpl | 66 --- templates/user/settings/navbar.tmpl | 22 +- templates/user/settings/openid.tmpl | 71 ---- templates/user/settings/organization.tmpl | 2 +- templates/user/settings/profile.tmpl | 37 ++ templates/user/settings/repos.tmpl | 2 +- templates/user/settings/security.tmpl | 77 +--- .../user/settings/security_accountlinks.tmpl | 36 ++ templates/user/settings/security_openid.tmpl | 63 +++ templates/user/settings/security_twofa.tmpl | 35 ++ templates/user/settings/twofa.tmpl | 44 -- templates/user/settings/twofa_enroll.tmpl | 2 +- 25 files changed, 582 insertions(+), 688 deletions(-) create mode 100644 templates/user/settings/account.tmpl delete mode 100644 templates/user/settings/account_link.tmpl delete mode 100644 templates/user/settings/avatar.tmpl delete mode 100644 templates/user/settings/delete.tmpl delete mode 100644 templates/user/settings/email.tmpl delete mode 100644 templates/user/settings/openid.tmpl create mode 100644 templates/user/settings/security_accountlinks.tmpl create mode 100644 templates/user/settings/security_openid.tmpl create mode 100644 templates/user/settings/security_twofa.tmpl delete mode 100644 templates/user/settings/twofa.tmpl diff --git a/integrations/delete_user_test.go b/integrations/delete_user_test.go index a6e44c15c..2a5ca8986 100644 --- a/integrations/delete_user_test.go +++ b/integrations/delete_user_test.go @@ -43,8 +43,8 @@ func TestUserDeleteAccount(t *testing.T) { prepareTestEnv(t) session := loginUser(t, "user8") - csrf := GetCSRF(t, session, "/user/settings/delete") - urlStr := fmt.Sprintf("/user/settings/delete?password=%s", userPassword) + csrf := GetCSRF(t, session, "/user/settings/account") + urlStr := fmt.Sprintf("/user/settings/account/delete?password=%s", userPassword) req := NewRequestWithValues(t, "POST", urlStr, map[string]string{ "_csrf": csrf, }) @@ -58,8 +58,8 @@ func TestUserDeleteAccountStillOwnRepos(t *testing.T) { prepareTestEnv(t) session := loginUser(t, "user2") - csrf := GetCSRF(t, session, "/user/settings/delete") - urlStr := fmt.Sprintf("/user/settings/delete?password=%s", userPassword) + csrf := GetCSRF(t, session, "/user/settings/account") + urlStr := fmt.Sprintf("/user/settings/account/delete?password=%s", userPassword) req := NewRequestWithValues(t, "POST", urlStr, map[string]string{ "_csrf": csrf, }) diff --git a/integrations/links_test.go b/integrations/links_test.go index b0abbd708..803d99205 100644 --- a/integrations/links_test.go +++ b/integrations/links_test.go @@ -93,15 +93,12 @@ func testLinksAsUser(userName string, t *testing.T) { "/user2?tab=stars", "/user2?tab=activity", "/user/settings", - "/user/settings/avatar", + "/user/settings/account", "/user/settings/security", "/user/settings/security/two_factor/enroll", - "/user/settings/email", "/user/settings/keys", - "/user/settings/applications", - "/user/settings/account_link", "/user/settings/organization", - "/user/settings/delete", + "/user/settings/repos", } session := loginUser(t, userName) diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 3082569bf..8ef005660 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -306,12 +306,13 @@ form.name_pattern_not_allowed = The pattern '%s' is not allowed in a username. [settings] profile = Profile +account = Account password = Password security = Security avatar = Avatar ssh_gpg_keys = SSH / GPG Keys social = Social Accounts -applications = Access Tokens +applications = Applications orgs = Manage Organizations repos = Repositories delete = Delete Account diff --git a/routers/routes/routes.go b/routers/routes/routes.go index 9618d2526..40b5f4bfb 100644 --- a/routers/routes/routes.go +++ b/routers/routes/routes.go @@ -37,6 +37,7 @@ import ( "github.com/go-macaron/session" "github.com/go-macaron/toolbox" "gopkg.in/macaron.v1" + "net/http" ) // NewMacaron initializes Macaron instance. @@ -217,35 +218,54 @@ func RegisterRoutes(m *macaron.Macaron) { m.Group("/user/settings", func() { m.Get("", user.Settings) m.Post("", bindIgnErr(auth.UpdateProfileForm{}), user.SettingsPost) - m.Combo("/avatar").Get(user.SettingsAvatar). - Post(binding.MultipartForm(auth.AvatarForm{}), user.SettingsAvatarPost) + m.Post("/avatar", binding.MultipartForm(auth.AvatarForm{}), user.SettingsAvatarPost) m.Post("/avatar/delete", user.SettingsDeleteAvatar) - m.Combo("/email").Get(user.SettingsEmails). - Post(bindIgnErr(auth.AddEmailForm{}), user.SettingsEmailPost) - m.Post("/email/delete", user.DeleteEmail) - m.Get("/security", user.SettingsSecurity) - m.Post("/security", bindIgnErr(auth.ChangePasswordForm{}), user.SettingsSecurityPost) - m.Group("/openid", func() { - m.Combo("").Get(user.SettingsOpenID). - Post(bindIgnErr(auth.AddOpenIDForm{}), user.SettingsOpenIDPost) - m.Post("/delete", user.DeleteOpenID) - m.Post("/toggle_visibility", user.ToggleOpenIDVisibility) - }, openIDSignInEnabled) - m.Combo("/keys").Get(user.SettingsKeys). - Post(bindIgnErr(auth.AddKeyForm{}), user.SettingsKeysPost) - m.Post("/keys/delete", user.DeleteKey) + m.Group("/account", func() { + m.Combo("").Get(user.SettingsAccount).Post(bindIgnErr(auth.ChangePasswordForm{}), user.SettingsAccountPost) + m.Post("/email", bindIgnErr(auth.AddEmailForm{}), user.SettingsEmailPost) + m.Post("/email/delete", user.DeleteEmail) + m.Post("/delete", user.SettingsDelete) + }) + m.Group("/security", func() { + m.Get("", user.SettingsSecurity) + m.Group("/two_factor", func() { + m.Post("/regenerate_scratch", user.SettingsTwoFactorRegenerateScratch) + m.Post("/disable", user.SettingsTwoFactorDisable) + m.Get("/enroll", user.SettingsTwoFactorEnroll) + m.Post("/enroll", bindIgnErr(auth.TwoFactorAuthForm{}), user.SettingsTwoFactorEnrollPost) + }) + m.Group("/openid", func() { + m.Post("", bindIgnErr(auth.AddOpenIDForm{}), user.SettingsOpenIDPost) + m.Post("/delete", user.DeleteOpenID) + m.Post("/toggle_visibility", user.ToggleOpenIDVisibility) + }, openIDSignInEnabled) + m.Post("/account_link", user.SettingsDeleteAccountLink) + }) m.Combo("/applications").Get(user.SettingsApplications). Post(bindIgnErr(auth.NewAccessTokenForm{}), user.SettingsApplicationsPost) m.Post("/applications/delete", user.SettingsDeleteApplication) - m.Route("/delete", "GET,POST", user.SettingsDelete) - m.Combo("/account_link").Get(user.SettingsAccountLinks).Post(user.SettingsDeleteAccountLink) + m.Combo("/keys").Get(user.SettingsKeys). + Post(bindIgnErr(auth.AddKeyForm{}), user.SettingsKeysPost) + m.Post("/keys/delete", user.DeleteKey) m.Get("/organization", user.SettingsOrganization) m.Get("/repos", user.SettingsRepos) - m.Group("/security/two_factor", func() { - m.Post("/regenerate_scratch", user.SettingsTwoFactorRegenerateScratch) - m.Post("/disable", user.SettingsTwoFactorDisable) - m.Get("/enroll", user.SettingsTwoFactorEnroll) - m.Post("/enroll", bindIgnErr(auth.TwoFactorAuthForm{}), user.SettingsTwoFactorEnrollPost) + + // redirects from old settings urls to new ones + // TODO: can be removed on next major version + m.Get("/avatar", func(ctx *context.Context) { + ctx.Redirect(setting.AppSubURL+"/user/settings", http.StatusMovedPermanently) + }) + m.Get("/email", func(ctx *context.Context) { + ctx.Redirect(setting.AppSubURL+"/user/settings/account", http.StatusMovedPermanently) + }) + m.Get("/delete", func(ctx *context.Context) { + ctx.Redirect(setting.AppSubURL+"/user/settings/account", http.StatusMovedPermanently) + }) + m.Get("/openid", func(ctx *context.Context) { + ctx.Redirect(setting.AppSubURL+"/user/settings/security", http.StatusMovedPermanently) + }) + m.Get("/account_link", func(ctx *context.Context) { + ctx.Redirect(setting.AppSubURL+"/user/settings/security", http.StatusMovedPermanently) }) }, reqSignIn, func(ctx *context.Context) { ctx.Data["PageIsUserSettings"] = true diff --git a/routers/user/setting.go b/routers/user/setting.go index f4326bf0f..1c760e210 100644 --- a/routers/user/setting.go +++ b/routers/user/setting.go @@ -30,18 +30,13 @@ import ( const ( tplSettingsProfile base.TplName = "user/settings/profile" - tplSettingsAvatar base.TplName = "user/settings/avatar" - tplSettingsEmails base.TplName = "user/settings/email" - tplSettingsKeys base.TplName = "user/settings/keys" - tplSettingsSocial base.TplName = "user/settings/social" - tplSettingsApplications base.TplName = "user/settings/applications" - tplSettingsTwofa base.TplName = "user/settings/twofa" + tplSettingsAccount base.TplName = "user/settings/account" + tplSettingsSecurity base.TplName = "user/settings/security" tplSettingsTwofaEnroll base.TplName = "user/settings/twofa_enroll" - tplSettingsAccountLink base.TplName = "user/settings/account_link" + tplSettingsApplications base.TplName = "user/settings/applications" + tplSettingsKeys base.TplName = "user/settings/keys" tplSettingsOrganization base.TplName = "user/settings/organization" tplSettingsRepositories base.TplName = "user/settings/repos" - tplSettingsDelete base.TplName = "user/settings/delete" - tplSettingsSecurity base.TplName = "user/settings/security" ) // Settings render user's profile page @@ -168,13 +163,6 @@ func UpdateAvatarSetting(ctx *context.Context, form auth.AvatarForm, ctxUser *mo return nil } -// SettingsAvatar render user avatar page -func SettingsAvatar(ctx *context.Context) { - ctx.Data["Title"] = ctx.Tr("settings") - ctx.Data["PageIsSettingsAvatar"] = true - ctx.HTML(200, tplSettingsAvatar) -} - // SettingsAvatarPost response for change user's avatar request func SettingsAvatarPost(ctx *context.Context, form auth.AvatarForm) { if err := UpdateAvatarSetting(ctx, form, ctx.User); err != nil { @@ -183,7 +171,7 @@ func SettingsAvatarPost(ctx *context.Context, form auth.AvatarForm) { ctx.Flash.Success(ctx.Tr("settings.update_avatar_success")) } - ctx.Redirect(setting.AppSubURL + "/user/settings/avatar") + ctx.Redirect(setting.AppSubURL + "/user/settings") } // SettingsDeleteAvatar render delete avatar page @@ -192,38 +180,32 @@ func SettingsDeleteAvatar(ctx *context.Context) { ctx.Flash.Error(err.Error()) } - ctx.Redirect(setting.AppSubURL + "/user/settings/avatar") + ctx.Redirect(setting.AppSubURL + "/user/settings") } -// SettingsSecurity render change user's password page and 2FA -func SettingsSecurity(ctx *context.Context) { +// SettingsAccount renders change user's password, user's email and user suicide page +func SettingsAccount(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("settings") - ctx.Data["PageIsSettingsSecurity"] = true + ctx.Data["PageIsSettingsAccount"] = true ctx.Data["Email"] = ctx.User.Email - enrolled := true - _, err := models.GetTwoFactorByUID(ctx.User.ID) + emails, err := models.GetEmailAddresses(ctx.User.ID) if err != nil { - if models.IsErrTwoFactorNotEnrolled(err) { - enrolled = false - } else { - ctx.ServerError("SettingsTwoFactor", err) - return - } + ctx.ServerError("GetEmailAddresses", err) + return } + ctx.Data["Emails"] = emails - ctx.Data["TwofaEnrolled"] = enrolled - ctx.HTML(200, tplSettingsSecurity) + ctx.HTML(200, tplSettingsAccount) } -// SettingsSecurityPost response for change user's password -func SettingsSecurityPost(ctx *context.Context, form auth.ChangePasswordForm) { +// SettingsAccountPost response for change user's password +func SettingsAccountPost(ctx *context.Context, form auth.ChangePasswordForm) { ctx.Data["Title"] = ctx.Tr("settings") - ctx.Data["PageIsSettingsSecurity"] = true - ctx.Data["PageIsSettingsDelete"] = true + ctx.Data["PageIsSettingsAccount"] = true if ctx.HasError() { - ctx.HTML(200, tplSettingsSecurity) + ctx.HTML(200, tplSettingsAccount) return } @@ -248,28 +230,13 @@ func SettingsSecurityPost(ctx *context.Context, form auth.ChangePasswordForm) { ctx.Flash.Success(ctx.Tr("settings.change_password_success")) } - ctx.Redirect(setting.AppSubURL + "/user/settings/security") -} - -// SettingsEmails render user's emails page -func SettingsEmails(ctx *context.Context) { - ctx.Data["Title"] = ctx.Tr("settings") - ctx.Data["PageIsSettingsEmails"] = true - - emails, err := models.GetEmailAddresses(ctx.User.ID) - if err != nil { - ctx.ServerError("GetEmailAddresses", err) - return - } - ctx.Data["Emails"] = emails - - ctx.HTML(200, tplSettingsEmails) + ctx.Redirect(setting.AppSubURL + "/user/settings/account") } // SettingsEmailPost response for change user's email func SettingsEmailPost(ctx *context.Context, form auth.AddEmailForm) { ctx.Data["Title"] = ctx.Tr("settings") - ctx.Data["PageIsSettingsEmails"] = true + ctx.Data["PageIsSettingsAccount"] = true // Make emailaddress primary. if ctx.Query("_method") == "PRIMARY" { @@ -279,7 +246,7 @@ func SettingsEmailPost(ctx *context.Context, form auth.AddEmailForm) { } log.Trace("Email made primary: %s", ctx.User.Name) - ctx.Redirect(setting.AppSubURL + "/user/settings/email") + ctx.Redirect(setting.AppSubURL + "/user/settings/account") return } @@ -292,7 +259,7 @@ func SettingsEmailPost(ctx *context.Context, form auth.AddEmailForm) { ctx.Data["Emails"] = emails if ctx.HasError() { - ctx.HTML(200, tplSettingsEmails) + ctx.HTML(200, tplSettingsAccount) return } @@ -303,7 +270,7 @@ func SettingsEmailPost(ctx *context.Context, form auth.AddEmailForm) { } if err := models.AddEmailAddress(email); err != nil { if models.IsErrEmailAlreadyUsed(err) { - ctx.RenderWithErr(ctx.Tr("form.email_been_used"), tplSettingsEmails, &form) + ctx.RenderWithErr(ctx.Tr("form.email_been_used"), tplSettingsAccount, &form) return } ctx.ServerError("AddEmailAddress", err) @@ -323,7 +290,7 @@ func SettingsEmailPost(ctx *context.Context, form auth.AddEmailForm) { } log.Trace("Email address added: %s", email.Email) - ctx.Redirect(setting.AppSubURL + "/user/settings/email") + ctx.Redirect(setting.AppSubURL + "/user/settings/account") } // DeleteEmail response for delete user's email @@ -336,7 +303,164 @@ func DeleteEmail(ctx *context.Context) { ctx.Flash.Success(ctx.Tr("settings.email_deletion_success")) ctx.JSON(200, map[string]interface{}{ - "redirect": setting.AppSubURL + "/user/settings/email", + "redirect": setting.AppSubURL + "/user/settings/account", + }) +} + +// SettingsDelete render user suicide page and response for delete user himself +func SettingsDelete(ctx *context.Context) { + ctx.Data["Title"] = ctx.Tr("settings") + ctx.Data["PageIsSettingsAccount"] = true + + if _, err := models.UserSignIn(ctx.User.Name, ctx.Query("password")); err != nil { + if models.IsErrUserNotExist(err) { + ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_password"), tplSettingsAccount, nil) + } else { + ctx.ServerError("UserSignIn", err) + } + return + } + + if err := models.DeleteUser(ctx.User); err != nil { + switch { + case models.IsErrUserOwnRepos(err): + ctx.Flash.Error(ctx.Tr("form.still_own_repo")) + ctx.Redirect(setting.AppSubURL + "/user/settings/account") + case models.IsErrUserHasOrgs(err): + ctx.Flash.Error(ctx.Tr("form.still_has_org")) + ctx.Redirect(setting.AppSubURL + "/user/settings/account") + default: + ctx.ServerError("DeleteUser", err) + } + } else { + log.Trace("Account deleted: %s", ctx.User.Name) + ctx.Redirect(setting.AppSubURL + "/") + } +} + +// SettingsSecurity render change user's password page and 2FA +func SettingsSecurity(ctx *context.Context) { + ctx.Data["Title"] = ctx.Tr("settings") + ctx.Data["PageIsSettingsSecurity"] = true + + enrolled := true + _, err := models.GetTwoFactorByUID(ctx.User.ID) + if err != nil { + if models.IsErrTwoFactorNotEnrolled(err) { + enrolled = false + } else { + ctx.ServerError("SettingsTwoFactor", err) + return + } + } + ctx.Data["TwofaEnrolled"] = enrolled + + accountLinks, err := models.ListAccountLinks(ctx.User) + if err != nil { + ctx.ServerError("ListAccountLinks", err) + return + } + + // map the provider display name with the LoginSource + sources := make(map[*models.LoginSource]string) + for _, externalAccount := range accountLinks { + if loginSource, err := models.GetLoginSourceByID(externalAccount.LoginSourceID); err == nil { + var providerDisplayName string + if loginSource.IsOAuth2() { + providerTechnicalName := loginSource.OAuth2().Provider + providerDisplayName = models.OAuth2Providers[providerTechnicalName].DisplayName + } else { + providerDisplayName = loginSource.Name + } + sources[loginSource] = providerDisplayName + } + } + ctx.Data["AccountLinks"] = sources + + if ctx.Query("openid.return_to") != "" { + settingsOpenIDVerify(ctx) + return + } + + openid, err := models.GetUserOpenIDs(ctx.User.ID) + if err != nil { + ctx.ServerError("GetUserOpenIDs", err) + return + } + ctx.Data["OpenIDs"] = openid + + ctx.HTML(200, tplSettingsSecurity) +} + +// SettingsDeleteAccountLink delete a single account link +func SettingsDeleteAccountLink(ctx *context.Context) { + if _, err := models.RemoveAccountLink(ctx.User, ctx.QueryInt64("loginSourceID")); err != nil { + ctx.Flash.Error("RemoveAccountLink: " + err.Error()) + } else { + ctx.Flash.Success(ctx.Tr("settings.remove_account_link_success")) + } + + ctx.JSON(200, map[string]interface{}{ + "redirect": setting.AppSubURL + "/user/settings/security", + }) +} + +// SettingsApplications render manage access token page +func SettingsApplications(ctx *context.Context) { + ctx.Data["Title"] = ctx.Tr("settings") + ctx.Data["PageIsSettingsApplications"] = true + + tokens, err := models.ListAccessTokens(ctx.User.ID) + if err != nil { + ctx.ServerError("ListAccessTokens", err) + return + } + ctx.Data["Tokens"] = tokens + + ctx.HTML(200, tplSettingsApplications) +} + +// SettingsApplicationsPost response for add user's access token +func SettingsApplicationsPost(ctx *context.Context, form auth.NewAccessTokenForm) { + ctx.Data["Title"] = ctx.Tr("settings") + ctx.Data["PageIsSettingsApplications"] = true + + if ctx.HasError() { + tokens, err := models.ListAccessTokens(ctx.User.ID) + if err != nil { + ctx.ServerError("ListAccessTokens", err) + return + } + ctx.Data["Tokens"] = tokens + ctx.HTML(200, tplSettingsApplications) + return + } + + t := &models.AccessToken{ + UID: ctx.User.ID, + Name: form.Name, + } + if err := models.NewAccessToken(t); err != nil { + ctx.ServerError("NewAccessToken", err) + return + } + + ctx.Flash.Success(ctx.Tr("settings.generate_token_success")) + ctx.Flash.Info(t.Sha1) + + ctx.Redirect(setting.AppSubURL + "/user/settings/applications") +} + +// SettingsDeleteApplication response for delete user access token +func SettingsDeleteApplication(ctx *context.Context) { + if err := models.DeleteAccessTokenByID(ctx.QueryInt64("id"), ctx.User.ID); err != nil { + ctx.Flash.Error("DeleteAccessTokenByID: " + err.Error()) + } else { + ctx.Flash.Success(ctx.Tr("settings.delete_token_success")) + } + + ctx.JSON(200, map[string]interface{}{ + "redirect": setting.AppSubURL + "/user/settings/applications", }) } @@ -471,65 +595,6 @@ func DeleteKey(ctx *context.Context) { }) } -// SettingsApplications render user's access tokens page -func SettingsApplications(ctx *context.Context) { - ctx.Data["Title"] = ctx.Tr("settings") - ctx.Data["PageIsSettingsApplications"] = true - - tokens, err := models.ListAccessTokens(ctx.User.ID) - if err != nil { - ctx.ServerError("ListAccessTokens", err) - return - } - ctx.Data["Tokens"] = tokens - - ctx.HTML(200, tplSettingsApplications) -} - -// SettingsApplicationsPost response for add user's access token -func SettingsApplicationsPost(ctx *context.Context, form auth.NewAccessTokenForm) { - ctx.Data["Title"] = ctx.Tr("settings") - ctx.Data["PageIsSettingsApplications"] = true - - if ctx.HasError() { - tokens, err := models.ListAccessTokens(ctx.User.ID) - if err != nil { - ctx.ServerError("ListAccessTokens", err) - return - } - ctx.Data["Tokens"] = tokens - ctx.HTML(200, tplSettingsApplications) - return - } - - t := &models.AccessToken{ - UID: ctx.User.ID, - Name: form.Name, - } - if err := models.NewAccessToken(t); err != nil { - ctx.ServerError("NewAccessToken", err) - return - } - - ctx.Flash.Success(ctx.Tr("settings.generate_token_success")) - ctx.Flash.Info(t.Sha1) - - ctx.Redirect(setting.AppSubURL + "/user/settings/applications") -} - -// SettingsDeleteApplication response for delete user access token -func SettingsDeleteApplication(ctx *context.Context) { - if err := models.DeleteAccessTokenByID(ctx.QueryInt64("id"), ctx.User.ID); err != nil { - ctx.Flash.Error("DeleteAccessTokenByID: " + err.Error()) - } else { - ctx.Flash.Success(ctx.Tr("settings.delete_token_success")) - } - - ctx.JSON(200, map[string]interface{}{ - "redirect": setting.AppSubURL + "/user/settings/applications", - }) -} - // SettingsTwoFactorRegenerateScratch regenerates the user's 2FA scratch code. func SettingsTwoFactorRegenerateScratch(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("settings") @@ -695,86 +760,6 @@ func SettingsTwoFactorEnrollPost(ctx *context.Context, form auth.TwoFactorAuthFo ctx.Redirect(setting.AppSubURL + "/user/settings/security") } -// SettingsAccountLinks render the account links settings page -func SettingsAccountLinks(ctx *context.Context) { - ctx.Data["Title"] = ctx.Tr("settings") - ctx.Data["PageIsSettingsAccountLink"] = true - - accountLinks, err := models.ListAccountLinks(ctx.User) - if err != nil { - ctx.ServerError("ListAccountLinks", err) - return - } - - // map the provider display name with the LoginSource - sources := make(map[*models.LoginSource]string) - for _, externalAccount := range accountLinks { - if loginSource, err := models.GetLoginSourceByID(externalAccount.LoginSourceID); err == nil { - var providerDisplayName string - if loginSource.IsOAuth2() { - providerTechnicalName := loginSource.OAuth2().Provider - providerDisplayName = models.OAuth2Providers[providerTechnicalName].DisplayName - } else { - providerDisplayName = loginSource.Name - } - sources[loginSource] = providerDisplayName - } - } - ctx.Data["AccountLinks"] = sources - - ctx.HTML(200, tplSettingsAccountLink) -} - -// SettingsDeleteAccountLink delete a single account link -func SettingsDeleteAccountLink(ctx *context.Context) { - if _, err := models.RemoveAccountLink(ctx.User, ctx.QueryInt64("loginSourceID")); err != nil { - ctx.Flash.Error("RemoveAccountLink: " + err.Error()) - } else { - ctx.Flash.Success(ctx.Tr("settings.remove_account_link_success")) - } - - ctx.JSON(200, map[string]interface{}{ - "redirect": setting.AppSubURL + "/user/settings/account_link", - }) -} - -// SettingsDelete render user suicide page and response for delete user himself -func SettingsDelete(ctx *context.Context) { - ctx.Data["Title"] = ctx.Tr("settings") - ctx.Data["PageIsSettingsDelete"] = true - ctx.Data["Email"] = ctx.User.Email - - if ctx.Req.Method == "POST" { - if _, err := models.UserSignIn(ctx.User.Name, ctx.Query("password")); err != nil { - if models.IsErrUserNotExist(err) { - ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_password"), tplSettingsDelete, nil) - } else { - ctx.ServerError("UserSignIn", err) - } - return - } - - if err := models.DeleteUser(ctx.User); err != nil { - switch { - case models.IsErrUserOwnRepos(err): - ctx.Flash.Error(ctx.Tr("form.still_own_repo")) - ctx.Redirect(setting.AppSubURL + "/user/settings/delete") - case models.IsErrUserHasOrgs(err): - ctx.Flash.Error(ctx.Tr("form.still_has_org")) - ctx.Redirect(setting.AppSubURL + "/user/settings/delete") - default: - ctx.ServerError("DeleteUser", err) - } - } else { - log.Trace("Account deleted: %s", ctx.User.Name) - ctx.Redirect(setting.AppSubURL + "/") - } - return - } - - ctx.HTML(200, tplSettingsDelete) -} - // SettingsOrganization render all the organization of the user func SettingsOrganization(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("settings") diff --git a/routers/user/setting_openid.go b/routers/user/setting_openid.go index 92eb636e2..771646612 100644 --- a/routers/user/setting_openid.go +++ b/routers/user/setting_openid.go @@ -8,40 +8,15 @@ import ( "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/auth" "code.gitea.io/gitea/modules/auth/openid" - "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" ) -const ( - tplSettingsOpenID base.TplName = "user/settings/openid" -) - -// SettingsOpenID renders change user's openid page -func SettingsOpenID(ctx *context.Context) { - ctx.Data["Title"] = ctx.Tr("settings") - ctx.Data["PageIsSettingsOpenID"] = true - - if ctx.Query("openid.return_to") != "" { - settingsOpenIDVerify(ctx) - return - } - - openid, err := models.GetUserOpenIDs(ctx.User.ID) - if err != nil { - ctx.ServerError("GetUserOpenIDs", err) - return - } - ctx.Data["OpenIDs"] = openid - - ctx.HTML(200, tplSettingsOpenID) -} - // SettingsOpenIDPost response for change user's openid func SettingsOpenIDPost(ctx *context.Context, form auth.AddOpenIDForm) { ctx.Data["Title"] = ctx.Tr("settings") - ctx.Data["PageIsSettingsOpenID"] = true + ctx.Data["PageIsSettingsSecurity"] = true if ctx.HasError() { openid, err := models.GetUserOpenIDs(ctx.User.ID) @@ -50,7 +25,7 @@ func SettingsOpenIDPost(ctx *context.Context, form auth.AddOpenIDForm) { return } ctx.Data["OpenIDs"] = openid - ctx.HTML(200, tplSettingsOpenID) + ctx.HTML(200, tplSettingsSecurity) return } @@ -62,7 +37,7 @@ func SettingsOpenIDPost(ctx *context.Context, form auth.AddOpenIDForm) { id, err := openid.Normalize(form.Openid) if err != nil { - ctx.RenderWithErr(err.Error(), tplSettingsOpenID, &form) + ctx.RenderWithErr(err.Error(), tplSettingsSecurity, &form) return } form.Openid = id @@ -78,15 +53,15 @@ func SettingsOpenIDPost(ctx *context.Context, form auth.AddOpenIDForm) { // Check that the OpenID is not already used for _, obj := range oids { if obj.URI == id { - ctx.RenderWithErr(ctx.Tr("form.openid_been_used", id), tplSettingsOpenID, &form) + ctx.RenderWithErr(ctx.Tr("form.openid_been_used", id), tplSettingsSecurity, &form) return } } - redirectTo := setting.AppURL + "user/settings/openid" + redirectTo := setting.AppURL + "user/settings/security" url, err := openid.RedirectURL(id, redirectTo, setting.AppURL) if err != nil { - ctx.RenderWithErr(err.Error(), tplSettingsOpenID, &form) + ctx.RenderWithErr(err.Error(), tplSettingsSecurity, &form) return } ctx.Redirect(url) @@ -107,7 +82,7 @@ func settingsOpenIDVerify(ctx *context.Context) { id, err := openid.Verify(fullURL) if err != nil { - ctx.RenderWithErr(err.Error(), tplSettingsOpenID, &auth.AddOpenIDForm{ + ctx.RenderWithErr(err.Error(), tplSettingsSecurity, &auth.AddOpenIDForm{ Openid: id, }) return @@ -118,7 +93,7 @@ func settingsOpenIDVerify(ctx *context.Context) { oid := &models.UserOpenID{UID: ctx.User.ID, URI: id} if err = models.AddUserOpenID(oid); err != nil { if models.IsErrOpenIDAlreadyUsed(err) { - ctx.RenderWithErr(ctx.Tr("form.openid_been_used", id), tplSettingsOpenID, &auth.AddOpenIDForm{Openid: id}) + ctx.RenderWithErr(ctx.Tr("form.openid_been_used", id), tplSettingsSecurity, &auth.AddOpenIDForm{Openid: id}) return } ctx.ServerError("AddUserOpenID", err) @@ -127,7 +102,7 @@ func settingsOpenIDVerify(ctx *context.Context) { log.Trace("Associated OpenID %s to user %s", id, ctx.User.Name) ctx.Flash.Success(ctx.Tr("settings.add_openid_success")) - ctx.Redirect(setting.AppSubURL + "/user/settings/openid") + ctx.Redirect(setting.AppSubURL + "/user/settings/security") } // DeleteOpenID response for delete user's openid @@ -140,7 +115,7 @@ func DeleteOpenID(ctx *context.Context) { ctx.Flash.Success(ctx.Tr("settings.openid_deletion_success")) ctx.JSON(200, map[string]interface{}{ - "redirect": setting.AppSubURL + "/user/settings/openid", + "redirect": setting.AppSubURL + "/user/settings/security", }) } @@ -151,5 +126,5 @@ func ToggleOpenIDVisibility(ctx *context.Context) { return } - ctx.Redirect(setting.AppSubURL + "/user/settings/openid") + ctx.Redirect(setting.AppSubURL + "/user/settings/security") } diff --git a/routers/user/setting_test.go b/routers/user/setting_test.go index 72b1b8314..6aa9a0743 100644 --- a/routers/user/setting_test.go +++ b/routers/user/setting_test.go @@ -56,7 +56,7 @@ func TestChangePassword(t *testing.T) { test.LoadUser(t, ctx, 2) test.LoadRepo(t, ctx, 1) - SettingsSecurityPost(ctx, auth.ChangePasswordForm{ + SettingsAccountPost(ctx, auth.ChangePasswordForm{ OldPassword: req.OldPassword, Password: req.NewPassword, Retype: req.Retype, diff --git a/templates/user/profile.tmpl b/templates/user/profile.tmpl index 669ee3264..837f8bd94 100644 --- a/templates/user/profile.tmpl +++ b/templates/user/profile.tmpl @@ -5,7 +5,7 @@
{{if eq .SignedUserName .Owner.Name}} - + {{else}} diff --git a/templates/user/settings/account.tmpl b/templates/user/settings/account.tmpl new file mode 100644 index 000000000..290745222 --- /dev/null +++ b/templates/user/settings/account.tmpl @@ -0,0 +1,135 @@ +{{template "base/head" .}} + + + + + + +{{template "base/footer" .}} diff --git a/templates/user/settings/account_link.tmpl b/templates/user/settings/account_link.tmpl deleted file mode 100644 index 81ddf626e..000000000 --- a/templates/user/settings/account_link.tmpl +++ /dev/null @@ -1,44 +0,0 @@ -{{template "base/head" .}} - - - -{{template "base/footer" .}} diff --git a/templates/user/settings/applications.tmpl b/templates/user/settings/applications.tmpl index 4ad5eaa71..f1a3e4811 100644 --- a/templates/user/settings/applications.tmpl +++ b/templates/user/settings/applications.tmpl @@ -1,8 +1,7 @@ {{template "base/head" .}} -
+
{{template "user/settings/navbar" .}}
- {{template "base/alert" .}}

{{.i18n.Tr "settings.manage_access_token"}}

@@ -13,26 +12,26 @@
{{range .Tokens}}
-
- -
- -
- {{.Name}} -
- {{$.i18n.Tr "settings.add_on"}} {{.CreatedUnix.FormatShort}} {{if .HasUsed}}{{$.i18n.Tr "settings.last_used"}} {{.UpdatedUnix.FormatShort}}{{else}}{{$.i18n.Tr "settings.no_activity"}}{{end}} -
+
+ +
+ {{.Name}} +
+ {{$.i18n.Tr "settings.add_on"}} {{.CreatedUnix.FormatShort}} {{if .HasUsed}}{{$.i18n.Tr "settings.last_used"}} {{.UpdatedUnix.FormatShort}}{{else}}{{$.i18n.Tr "settings.no_activity"}}{{end}}
+
{{end}}
-

- {{.i18n.Tr "settings.generate_new_token"}} -

-
+
+
+ {{.i18n.Tr "settings.generate_new_token"}} +

{{.i18n.Tr "settings.new_token_desc"}}

{{.CsrfTokenHtml}} @@ -48,7 +47,7 @@
- + + {{template "base/footer" .}} diff --git a/templates/user/settings/avatar.tmpl b/templates/user/settings/avatar.tmpl deleted file mode 100644 index 72d2eb6ad..000000000 --- a/templates/user/settings/avatar.tmpl +++ /dev/null @@ -1,46 +0,0 @@ -{{template "base/head" .}} -
- {{template "user/settings/navbar" .}} -
- {{template "base/alert" .}} -

- {{.i18n.Tr "settings.avatar"}} -

-
- - - {{.CsrfTokenHtml}} - {{if not DisableGravatar}} -
-
- - -
-
-
- - -
- {{end}} - -
-
- - -
-
- -
- - -
- -
- - {{$.i18n.Tr "settings.delete_current_avatar"}} -
- -
-
-
-{{template "base/footer" .}} diff --git a/templates/user/settings/delete.tmpl b/templates/user/settings/delete.tmpl deleted file mode 100644 index 76ac7233b..000000000 --- a/templates/user/settings/delete.tmpl +++ /dev/null @@ -1,41 +0,0 @@ -{{template "base/head" .}} -
- {{template "user/settings/navbar" .}} -
- {{template "base/alert" .}} -

- {{.i18n.Tr "settings.delete_account"}} -

-
-
-

{{.i18n.Tr "settings.delete_prompt" | Str2html}}

-
-
- {{.CsrfTokenHtml}} - -
- - -
-
-
- {{.i18n.Tr "settings.confirm_delete_account"}} -
- {{.i18n.Tr "auth.forgot_password"}} -
-
-
-
-
- - -{{template "base/footer" .}} diff --git a/templates/user/settings/email.tmpl b/templates/user/settings/email.tmpl deleted file mode 100644 index 62dc1d5e5..000000000 --- a/templates/user/settings/email.tmpl +++ /dev/null @@ -1,66 +0,0 @@ -{{template "base/head" .}} -
- {{template "user/settings/navbar" .}} -
- {{template "base/alert" .}} -

- {{.i18n.Tr "settings.manage_emails"}} -

-
- -
-
-
- {{.CsrfTokenHtml}} -
- - -
- -
-
-
-
- - -{{template "base/footer" .}} diff --git a/templates/user/settings/navbar.tmpl b/templates/user/settings/navbar.tmpl index 4e0c7048e..18bd9a4f8 100644 --- a/templates/user/settings/navbar.tmpl +++ b/templates/user/settings/navbar.tmpl @@ -2,28 +2,17 @@ {{.i18n.Tr "settings.profile"}} - - {{.i18n.Tr "settings.avatar"}} + + {{.i18n.Tr "settings.account"}} {{.i18n.Tr "settings.security"}} - - {{.i18n.Tr "settings.emails"}} - - {{if .EnableOpenIDSignIn}} - - OpenID - - {{end}} - - {{.i18n.Tr "settings.ssh_gpg_keys"}} - {{.i18n.Tr "settings.applications"}} - - {{.i18n.Tr "settings.account_link"}} + + {{.i18n.Tr "settings.ssh_gpg_keys"}} {{.i18n.Tr "settings.organization"}} @@ -31,7 +20,4 @@ {{.i18n.Tr "settings.repos"}} - - {{.i18n.Tr "settings.delete"}} -
diff --git a/templates/user/settings/openid.tmpl b/templates/user/settings/openid.tmpl deleted file mode 100644 index 6ae4a8dee..000000000 --- a/templates/user/settings/openid.tmpl +++ /dev/null @@ -1,71 +0,0 @@ -{{template "base/head" .}} -
- {{template "user/settings/navbar" .}} -
- {{template "base/alert" .}} -

- {{.i18n.Tr "settings.manage_openid"}} -

-
-
-
- {{.i18n.Tr "settings.openid_desc"}} -
- {{range .OpenIDs}} -
-
- -
-
-
- {{$.CsrfTokenHtml}} - - {{if .Show}} - - {{else}} - - {{end}} - -
-
-
- {{.URI}} -
-
- {{end}} -
-
-
-
- {{.CsrfTokenHtml}} -
- - -
- -
-
-
-
- - -{{template "base/footer" .}} diff --git a/templates/user/settings/organization.tmpl b/templates/user/settings/organization.tmpl index 2d357cb3b..de541dcd1 100644 --- a/templates/user/settings/organization.tmpl +++ b/templates/user/settings/organization.tmpl @@ -1,5 +1,5 @@ {{template "base/head" .}} -
diff --git a/templates/user/settings/repos.tmpl b/templates/user/settings/repos.tmpl index 39d98c6d1..efb2c41c5 100644 --- a/templates/user/settings/repos.tmpl +++ b/templates/user/settings/repos.tmpl @@ -1,5 +1,5 @@ {{template "base/head" .}} -
+
{{template "user/settings/navbar" .}}
{{template "base/alert" .}} diff --git a/templates/user/settings/security.tmpl b/templates/user/settings/security.tmpl index b7cd222b3..8e7044f7d 100644 --- a/templates/user/settings/security.tmpl +++ b/templates/user/settings/security.tmpl @@ -1,79 +1,14 @@ {{template "base/head" .}} -
+
{{template "user/settings/navbar" .}}
{{template "base/alert" .}} -

- {{.i18n.Tr "settings.password"}} -

-
- {{if or (.SignedUser.IsLocal) (.SignedUser.IsOAuth2)}} -
- {{.CsrfTokenHtml}} - {{if .SignedUser.IsPasswordSet}} -
- - -
- {{end}} -
- - -
-
- - -
- -
- - {{.i18n.Tr "auth.forgot_password"}} -
-
- {{else}} -
-

{{$.i18n.Tr "settings.password_change_disabled"}}

-
- {{end}} -
-
- -

- {{.i18n.Tr "settings.twofa"}} -

-
-

{{.i18n.Tr "settings.twofa_desc"}}

- {{if .TwofaEnrolled}} -

{{$.i18n.Tr "settings.twofa_is_enrolled" | Str2html }}

-
- {{.CsrfTokenHtml}} -

{{.i18n.Tr "settings.regenerate_scratch_token_desc"}}

- -
-
- {{.CsrfTokenHtml}} -

{{.i18n.Tr "settings.twofa_disable_note"}}

-
{{$.i18n.Tr "settings.twofa_disable"}}
-
- {{else}} -

{{.i18n.Tr "settings.twofa_not_enrolled"}}

- - {{end}} -
-
-
- - {{template "base/footer" .}} diff --git a/templates/user/settings/security_accountlinks.tmpl b/templates/user/settings/security_accountlinks.tmpl new file mode 100644 index 000000000..93cc508a5 --- /dev/null +++ b/templates/user/settings/security_accountlinks.tmpl @@ -0,0 +1,36 @@ +

+ {{.i18n.Tr "settings.manage_account_links"}} +

+
+
+
+ {{.i18n.Tr "settings.manage_account_links_desc"}} +
+ {{if .AccountLinks}} + {{range $loginSource, $provider := .AccountLinks}} +
+
+ +
+
+ {{$provider}} + {{if $loginSource.IsActived}}{{$.i18n.Tr "settings.active"}}{{end}} +
+
+ {{end}} + {{end}} +
+
+ + diff --git a/templates/user/settings/security_openid.tmpl b/templates/user/settings/security_openid.tmpl new file mode 100644 index 000000000..12f4aab41 --- /dev/null +++ b/templates/user/settings/security_openid.tmpl @@ -0,0 +1,63 @@ +

+ {{.i18n.Tr "settings.manage_openid"}} +

+
+
+
+ {{.i18n.Tr "settings.openid_desc"}} +
+ {{range .OpenIDs}} +
+
+ +
+
+
+ {{$.CsrfTokenHtml}} + + {{if .Show}} + + {{else}} + + {{end}} + +
+
+
+ {{.URI}} +
+
+ {{end}} +
+
+
+
+ {{.CsrfTokenHtml}} +
+ + +
+ +
+
+ + diff --git a/templates/user/settings/security_twofa.tmpl b/templates/user/settings/security_twofa.tmpl new file mode 100644 index 000000000..05112a21b --- /dev/null +++ b/templates/user/settings/security_twofa.tmpl @@ -0,0 +1,35 @@ +

+ {{.i18n.Tr "settings.twofa"}} +

+
+

{{.i18n.Tr "settings.twofa_desc"}}

+ {{if .TwofaEnrolled}} +

{{$.i18n.Tr "settings.twofa_is_enrolled" | Str2html }}

+
+ {{.CsrfTokenHtml}} +

{{.i18n.Tr "settings.regenerate_scratch_token_desc"}}

+ +
+
+ {{.CsrfTokenHtml}} +

{{.i18n.Tr "settings.twofa_disable_note"}}

+
{{$.i18n.Tr "settings.twofa_disable"}}
+
+ {{else}} +

{{.i18n.Tr "settings.twofa_not_enrolled"}}

+ + {{end}} +
+ + diff --git a/templates/user/settings/twofa.tmpl b/templates/user/settings/twofa.tmpl deleted file mode 100644 index c6a7a7cba..000000000 --- a/templates/user/settings/twofa.tmpl +++ /dev/null @@ -1,44 +0,0 @@ -{{template "base/head" .}} -
- {{template "user/settings/navbar" .}} -
- {{template "base/alert" .}} -

- {{.i18n.Tr "settings.twofa"}} -

-
-

{{.i18n.Tr "settings.twofa_desc"}}

- {{if .TwofaEnrolled}} -

{{$.i18n.Tr "settings.twofa_is_enrolled" | Str2html }}

-
- {{.CsrfTokenHtml}} -

{{.i18n.Tr "settings.regenerate_scratch_token_desc"}}

- -
-
- {{.CsrfTokenHtml}} -

{{.i18n.Tr "settings.twofa_disable_note"}}

-
{{$.i18n.Tr "settings.twofa_disable"}}
-
- {{else}} -

{{.i18n.Tr "settings.twofa_not_enrolled"}}

- - {{end}} -
-
-
- - - -{{template "base/footer" .}} diff --git a/templates/user/settings/twofa_enroll.tmpl b/templates/user/settings/twofa_enroll.tmpl index 9238fb61a..0205532ac 100644 --- a/templates/user/settings/twofa_enroll.tmpl +++ b/templates/user/settings/twofa_enroll.tmpl @@ -1,5 +1,5 @@ {{template "base/head" .}} -
+
{{template "user/settings/navbar" .}}
{{template "base/alert" .}}