diff options
author | David Schneiderbauer <daviian@users.noreply.github.com> | 2018-05-15 12:07:32 +0200 |
---|---|---|
committer | Lauris BH <lauris@nix.lv> | 2018-05-15 13:07:32 +0300 |
commit | 099372d76c411c598285d637bd85c9b2dbc40948 (patch) | |
tree | bce801f16b8534e2ca0dadcbc24455de95f0501c | |
parent | 1546458f7df4a4f0e77b7ae5cb65baed6feae394 (diff) | |
download | gitea-099372d76c411c598285d637bd85c9b2dbc40948.tar.gz gitea-099372d76c411c598285d637bd85c9b2dbc40948.zip |
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
25 files changed, 582 insertions, 688 deletions
diff --git a/integrations/delete_user_test.go b/integrations/delete_user_test.go index a6e44c15c0..2a5ca89860 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 b0abbd7089..803d992055 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 3082569bf5..8ef0056608 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 9618d25268..40b5f4bfb3 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 f4326bf0f5..1c760e210c 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 92eb636e29..7716466120 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 72b1b83143..6aa9a07439 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 669ee3264b..837f8bd949 100644 --- a/templates/user/profile.tmpl +++ b/templates/user/profile.tmpl @@ -5,7 +5,7 @@ <div class="ui five wide column"> <div class="ui card"> {{if eq .SignedUserName .Owner.Name}} - <a class="image poping up" href="{{AppSubUrl}}/user/settings/avatar" id="profile-avatar" data-content="{{.i18n.Tr "user.change_avatar"}}" data-variation="inverted tiny" data-position="bottom center"> + <a class="image poping up" href="{{AppSubUrl}}/user/settings" id="profile-avatar" data-content="{{.i18n.Tr "user.change_avatar"}}" data-variation="inverted tiny" data-position="bottom center"> <img src="{{.Owner.SizedRelAvatarLink 290}}" title="{{.Owner.Name}}"/> </a> {{else}} diff --git a/templates/user/settings/account.tmpl b/templates/user/settings/account.tmpl new file mode 100644 index 0000000000..290745222d --- /dev/null +++ b/templates/user/settings/account.tmpl @@ -0,0 +1,135 @@ +{{template "base/head" .}} +<div class="user settings account"> + {{template "user/settings/navbar" .}} + <div class="ui container"> + {{template "base/alert" .}} + <h4 class="ui top attached header"> + {{.i18n.Tr "settings.password"}} + </h4> + <div class="ui attached segment"> + {{if or (.SignedUser.IsLocal) (.SignedUser.IsOAuth2)}} + <form class="ui form" action="{{.Link}}?tp=password" method="post"> + {{.CsrfTokenHtml}} + {{if .SignedUser.IsPasswordSet}} + <div class="required field {{if .Err_OldPassword}}error{{end}}"> + <label for="old_password">{{.i18n.Tr "settings.old_password"}}</label> + <input id="old_password" name="old_password" type="password" autocomplete="off" autofocus required> + </div> + {{end}} + <div class="required field {{if .Err_Password}}error{{end}}"> + <label for="password">{{.i18n.Tr "settings.new_password"}}</label> + <input id="password" name="password" type="password" autocomplete="off" required> + </div> + <div class="required field {{if .Err_Password}}error{{end}}"> + <label for="retype">{{.i18n.Tr "settings.retype_new_password"}}</label> + <input id="retype" name="retype" type="password" autocomplete="off" required> + </div> + + <div class="field"> + <button class="ui green button">{{$.i18n.Tr "settings.change_password"}}</button> + <a href="{{AppSubUrl}}/user/forgot_password?email={{.Email}}">{{.i18n.Tr "auth.forgot_password"}}</a> + </div> + </form> + {{else}} + <div class="ui info message"> + <p class="text left">{{$.i18n.Tr "settings.password_change_disabled"}}</p> + </div> + {{end}} + </div> + + <h4 class="ui top attached header"> + {{.i18n.Tr "settings.manage_emails"}} + </h4> + <div class="ui attached segment"> + <div class="ui email list"> + <div class="item"> + {{.i18n.Tr "settings.email_desc"}} + </div> + {{range .Emails}} + <div class="item"> + {{if not .IsPrimary}} + <div class="right floated content"> + <button class="ui red tiny button delete-button" id="delete-email" data-url="{{$.Link}}/email/delete" data-id="{{.ID}}"> + {{$.i18n.Tr "settings.delete_email"}} + </button> + </div> + {{if .IsActivated}} + <div class="right floated content"> + <form action="{{$.Link}}/email" method="post"> + {{$.CsrfTokenHtml}} + <input name="_method" type="hidden" value="PRIMARY"> + <input name="id" type="hidden" value="{{.ID}}"> + <button class="ui blue tiny button">{{$.i18n.Tr "settings.primary_email"}}</button> + </form> + </div> + {{end}} + {{end}} + <div class="content"> + <strong>{{.Email}}</strong> + {{if .IsPrimary}}<span class="text red">{{$.i18n.Tr "settings.primary"}}</span>{{end}} + </div> + </div> + {{end}} + </div> + </div> + <div class="ui attached bottom segment"> + <form class="ui form" action="{{.Link}}/email" method="post"> + {{.CsrfTokenHtml}} + <div class="required field {{if .Err_Email}}error{{end}}"> + <label for="email">{{.i18n.Tr "settings.add_new_email"}}</label> + <input id="email" name="email" type="email" required> + </div> + <button class="ui green button"> + {{.i18n.Tr "settings.add_email"}} + </button> + </form> + </div> + + <h4 class="ui top attached warning header"> + {{.i18n.Tr "settings.delete_account"}} + </h4> + <div class="ui attached warning segment"> + <div class="ui red message"> + <p class="text left"><i class="octicon octicon-alert"></i> {{.i18n.Tr "settings.delete_prompt" | Str2html}}</p> + </div> + <form class="ui form ignore-dirty" id="delete-form" action="{{.Link}}/delete" method="post"> + {{.CsrfTokenHtml}} + <input class="fake" type="password"> + <div class="required field {{if .Err_Password}}error{{end}}"> + <label for="password-confirmation">{{.i18n.Tr "password"}}</label> + <input id="password-confirmation" name="password" type="password" required> + </div> + <div class="field"> + <div class="ui red button delete-button" id="delete-account" data-type="form" data-form="#delete-form"> + {{.i18n.Tr "settings.confirm_delete_account"}} + </div> + <a href="{{AppSubUrl}}/user/forgot_password?email={{.Email}}">{{.i18n.Tr "auth.forgot_password"}}</a> + </div> + </form> + </div> + </div> +</div> + +<div class="ui small basic delete modal" id="delete-email"> + <div class="ui icon header"> + <i class="trash icon"></i> + {{.i18n.Tr "settings.email_deletion"}} + </div> + <div class="content"> + <p>{{.i18n.Tr "settings.email_deletion_desc"}}</p> + </div> + {{template "base/delete_modal_actions" .}} +</div> + +<div class="ui small basic delete modal" id="delete-account"> + <div class="ui icon header"> + <i class="trash icon"></i> + {{.i18n.Tr "settings.delete_account_title"}} + </div> + <div class="content"> + <p>{{.i18n.Tr "settings.delete_account_desc"}}</p> + </div> + {{template "base/delete_modal_actions" .}} +</div> + +{{template "base/footer" .}} diff --git a/templates/user/settings/account_link.tmpl b/templates/user/settings/account_link.tmpl deleted file mode 100644 index 81ddf626e1..0000000000 --- a/templates/user/settings/account_link.tmpl +++ /dev/null @@ -1,44 +0,0 @@ -{{template "base/head" .}} -<div class="user settings account_link"> - {{template "user/settings/navbar" .}} - <div class="ui container"> - {{template "base/alert" .}} - <h4 class="ui top attached header"> - {{.i18n.Tr "settings.manage_account_links"}} - </h4> - <div class="ui attached segment"> - <div class="ui key list"> - <div class="item"> - {{.i18n.Tr "settings.manage_account_links_desc"}} - </div> - {{if .AccountLinks}} - {{range $loginSource, $provider := .AccountLinks}} - <div class="item"> - <div class="right floated content"> - <button class="ui red tiny button delete-button" data-url="{{$.Link}}" data-id="{{$loginSource.ID}}"> - {{$.i18n.Tr "settings.delete_key"}} - </button> - </div> - <div class="content"> - <strong>{{$provider}}</strong> - {{if $loginSource.IsActived}}<span class="text red">{{$.i18n.Tr "settings.active"}}</span>{{end}} - </div> - </div> - {{end}} - {{end}} - </div> - </div> - </div> -</div> - -<div class="ui small basic delete modal"> - <div class="ui icon header"> - <i class="trash icon"></i> - {{.i18n.Tr "settings.remove_account_link"}} - </div> - <div class="content"> - <p>{{.i18n.Tr "settings.remove_account_link_desc"}}</p> - </div> - {{template "base/delete_modal_actions" .}} -</div> -{{template "base/footer" .}} diff --git a/templates/user/settings/applications.tmpl b/templates/user/settings/applications.tmpl index 4ad5eaa714..f1a3e48115 100644 --- a/templates/user/settings/applications.tmpl +++ b/templates/user/settings/applications.tmpl @@ -1,8 +1,7 @@ {{template "base/head" .}} -<div class="user settings"> +<div class="user settings applications"> {{template "user/settings/navbar" .}} <div class="ui container"> - {{template "base/alert" .}} <h4 class="ui top attached header"> {{.i18n.Tr "settings.manage_access_token"}} </h4> @@ -13,26 +12,26 @@ </div> {{range .Tokens}} <div class="item"> - <div class="right floated content"> - <button class="ui red tiny button delete-button" data-url="{{$.Link}}/delete" data-id="{{.ID}}"> + <div class="right floated content"> + <button class="ui red tiny button delete-button" id="delete-token" data-url="{{$.Link}}/delete" data-id="{{.ID}}"> {{$.i18n.Tr "settings.delete_token"}} </button> - </div> - <i class="big send icon {{if .HasRecentActivity}}green{{end}}" {{if .HasRecentActivity}}data-content="{{$.i18n.Tr "settings.token_state_desc"}}" data-variation="inverted tiny"{{end}}></i> - <div class="content"> - <strong>{{.Name}}</strong> - <div class="activity meta"> - <i>{{$.i18n.Tr "settings.add_on"}} <span>{{.CreatedUnix.FormatShort}}</span> — <i class="octicon octicon-info"></i> {{if .HasUsed}}{{$.i18n.Tr "settings.last_used"}} <span {{if .HasRecentActivity}}class="green"{{end}}>{{.UpdatedUnix.FormatShort}}</span>{{else}}{{$.i18n.Tr "settings.no_activity"}}{{end}}</i> - </div> + </div> + <i class="big send icon {{if .HasRecentActivity}}green{{end}}" {{if .HasRecentActivity}}data-content="{{$.i18n.Tr "settings.token_state_desc"}}" data-variation="inverted tiny"{{end}}></i> + <div class="content"> + <strong>{{.Name}}</strong> + <div class="activity meta"> + <i>{{$.i18n.Tr "settings.add_on"}} <span>{{.CreatedUnix.FormatShort}}</span> — <i class="octicon octicon-info"></i> {{if .HasUsed}}{{$.i18n.Tr "settings.last_used"}} <span {{if .HasRecentActivity}}class="green"{{end}}>{{.UpdatedUnix.FormatShort}}</span>{{else}}{{$.i18n.Tr "settings.no_activity"}}{{end}}</i> </div> + </div> </div> {{end}} </div> </div> - <h4 class="ui top attached header"> - {{.i18n.Tr "settings.generate_new_token"}} - </h4> - <div class="ui attached segment"> + <div class="ui attached bottom segment"> + <h5 class="ui top header"> + {{.i18n.Tr "settings.generate_new_token"}} + </h5> <p>{{.i18n.Tr "settings.new_token_desc"}}</p> <form class="ui form ignore-dirty" action="{{.Link}}" method="post"> {{.CsrfTokenHtml}} @@ -48,7 +47,7 @@ </div> </div> -<div class="ui small basic delete modal"> +<div class="ui small basic delete modal" id="delete-token"> <div class="ui icon header"> <i class="trash icon"></i> {{.i18n.Tr "settings.access_token_deletion"}} @@ -67,4 +66,6 @@ </div> </div> </div> + + {{template "base/footer" .}} diff --git a/templates/user/settings/avatar.tmpl b/templates/user/settings/avatar.tmpl deleted file mode 100644 index 72d2eb6ad4..0000000000 --- a/templates/user/settings/avatar.tmpl +++ /dev/null @@ -1,46 +0,0 @@ -{{template "base/head" .}} -<div class="user settings avatar"> - {{template "user/settings/navbar" .}} - <div class="ui container"> - {{template "base/alert" .}} - <h4 class="ui top attached header"> - {{.i18n.Tr "settings.avatar"}} - </h4> - <div class="ui attached segment"> - - <form class="ui form" action="{{.Link}}" method="post" enctype="multipart/form-data"> - {{.CsrfTokenHtml}} - {{if not DisableGravatar}} - <div class="inline field"> - <div class="ui radio checkbox"> - <input name="source" value="lookup" type="radio" {{if not .SignedUser.UseCustomAvatar}}checked{{end}}> - <label>{{.i18n.Tr "settings.lookup_avatar_by_mail"}}</label> - </div> - </div> - <div class="field {{if .Err_Gravatar}}error{{end}}"> - <label for="gravatar">Avatar {{.i18n.Tr "email"}}</label> - <input id="gravatar" name="gravatar" value="{{.SignedUser.AvatarEmail}}" /> - </div> - {{end}} - - <div class="inline field"> - <div class="ui radio checkbox"> - <input name="source" value="local" type="radio" {{if .SignedUser.UseCustomAvatar}}checked{{end}}> - <label>{{.i18n.Tr "settings.enable_custom_avatar"}}</label> - </div> - </div> - - <div class="inline field"> - <label for="avatar">{{.i18n.Tr "settings.choose_new_avatar"}}</label> - <input name="avatar" type="file" > - </div> - - <div class="field"> - <button class="ui green button">{{$.i18n.Tr "settings.update_avatar"}}</button> - <a class="ui red button delete-post" data-request-url="{{.Link}}/delete" data-done-url="{{.Link}}">{{$.i18n.Tr "settings.delete_current_avatar"}}</a> - </div> - </form> - </div> - </div> -</div> -{{template "base/footer" .}} diff --git a/templates/user/settings/delete.tmpl b/templates/user/settings/delete.tmpl deleted file mode 100644 index 76ac7233bd..0000000000 --- a/templates/user/settings/delete.tmpl +++ /dev/null @@ -1,41 +0,0 @@ -{{template "base/head" .}} -<div class="user settings delete"> - {{template "user/settings/navbar" .}} - <div class="ui container"> - {{template "base/alert" .}} - <h4 class="ui top attached warning header"> - {{.i18n.Tr "settings.delete_account"}} - </h4> - <div class="ui attached warning segment"> - <div class="ui red message"> - <p class="text left"><i class="octicon octicon-alert"></i> {{.i18n.Tr "settings.delete_prompt" | Str2html}}</p> - </div> - <form class="ui form ignore-dirty" id="delete-form" action="{{.Link}}" method="post"> - {{.CsrfTokenHtml}} - <input class="fake" type="password"> - <div class="required field {{if .Err_Password}}error{{end}}"> - <label for="password">{{.i18n.Tr "password"}}</label> - <input id="password" name="password" type="password" autofocus required> - </div> - <div class="field"> - <div class="ui red button delete-button" data-type="form" data-form="#delete-form"> - {{.i18n.Tr "settings.confirm_delete_account"}} - </div> - <a href="{{AppSubUrl}}/user/forgot_password?email={{.Email}}">{{.i18n.Tr "auth.forgot_password"}}</a> - </div> - </form> - </div> - </div> -</div> - -<div class="ui small basic delete modal"> - <div class="ui icon header"> - <i class="trash icon"></i> - {{.i18n.Tr "settings.delete_account_title"}} - </div> - <div class="content"> - <p>{{.i18n.Tr "settings.delete_account_desc"}}</p> - </div> - {{template "base/delete_modal_actions" .}} -</div> -{{template "base/footer" .}} diff --git a/templates/user/settings/email.tmpl b/templates/user/settings/email.tmpl deleted file mode 100644 index 62dc1d5e59..0000000000 --- a/templates/user/settings/email.tmpl +++ /dev/null @@ -1,66 +0,0 @@ -{{template "base/head" .}} -<div class="user settings emails"> - {{template "user/settings/navbar" .}} - <div class="ui container"> - {{template "base/alert" .}} - <h4 class="ui top attached header"> - {{.i18n.Tr "settings.manage_emails"}} - </h4> - <div class="ui attached segment"> - <div class="ui email list"> - <div class="item"> - {{.i18n.Tr "settings.email_desc"}} - </div> - {{range .Emails}} - <div class="item"> - {{if not .IsPrimary}} - <div class="right floated content"> - <button class="ui red tiny button delete-button" data-url="{{$.Link}}/delete" data-id="{{.ID}}"> - {{$.i18n.Tr "settings.delete_email"}} - </button> - </div> - {{if .IsActivated}} - <div class="right floated content"> - <form action="{{$.Link}}" method="post"> - {{$.CsrfTokenHtml}} - <input name="_method" type="hidden" value="PRIMARY"> - <input name="id" type="hidden" value="{{.ID}}"> - <button class="ui blue tiny button">{{$.i18n.Tr "settings.primary_email"}}</button> - </form> - </div> - {{end}} - {{end}} - <div class="content"> - <strong>{{.Email}}</strong> - {{if .IsPrimary}}<span class="text red">{{$.i18n.Tr "settings.primary"}}</span>{{end}} - </div> - </div> - {{end}} - </div> - </div> - <div class="ui attached bottom segment"> - <form class="ui form" action="{{.Link}}" method="post"> - {{.CsrfTokenHtml}} - <div class="required field {{if .Err_Email}}error{{end}}"> - <label for="email">{{.i18n.Tr "settings.add_new_email"}}</label> - <input id="email" name="email" type="email" autofocus required> - </div> - <button class="ui green button"> - {{.i18n.Tr "settings.add_email"}} - </button> - </form> - </div> - </div> -</div> - -<div class="ui small basic delete modal"> - <div class="ui icon header"> - <i class="trash icon"></i> - {{.i18n.Tr "settings.email_deletion"}} - </div> - <div class="content"> - <p>{{.i18n.Tr "settings.email_deletion_desc"}}</p> - </div> - {{template "base/delete_modal_actions" .}} -</div> -{{template "base/footer" .}} diff --git a/templates/user/settings/navbar.tmpl b/templates/user/settings/navbar.tmpl index 4e0c7048ee..18bd9a4f8d 100644 --- a/templates/user/settings/navbar.tmpl +++ b/templates/user/settings/navbar.tmpl @@ -2,28 +2,17 @@ <a class="{{if .PageIsSettingsProfile}}active{{end}} item" href="{{AppSubUrl}}/user/settings"> {{.i18n.Tr "settings.profile"}} </a> - <a class="{{if .PageIsSettingsAvatar}}active{{end}} item" href="{{AppSubUrl}}/user/settings/avatar"> - {{.i18n.Tr "settings.avatar"}} + <a class="{{if .PageIsSettingsAccount}}active{{end}} item" href="{{AppSubUrl}}/user/settings/account"> + {{.i18n.Tr "settings.account"}} </a> <a class="{{if .PageIsSettingsSecurity}}active{{end}} item" href="{{AppSubUrl}}/user/settings/security"> {{.i18n.Tr "settings.security"}} </a> - <a class="{{if .PageIsSettingsEmails}}active{{end}} item" href="{{AppSubUrl}}/user/settings/email"> - {{.i18n.Tr "settings.emails"}} - </a> - {{if .EnableOpenIDSignIn}} - <a class="{{if .PageIsSettingsOpenID}}active{{end}} item" href="{{AppSubUrl}}/user/settings/openid"> - OpenID - </a> - {{end}} - <a class="{{if .PageIsSettingsKeys}}active{{end}} item" href="{{AppSubUrl}}/user/settings/keys"> - {{.i18n.Tr "settings.ssh_gpg_keys"}} - </a> <a class="{{if .PageIsSettingsApplications}}active{{end}} item" href="{{AppSubUrl}}/user/settings/applications"> {{.i18n.Tr "settings.applications"}} </a> - <a class="{{if .PageIsSettingsAccountLink}}active{{end}} item" href="{{AppSubUrl}}/user/settings/account_link"> - {{.i18n.Tr "settings.account_link"}} + <a class="{{if .PageIsSettingsKeys}}active{{end}} item" href="{{AppSubUrl}}/user/settings/keys"> + {{.i18n.Tr "settings.ssh_gpg_keys"}} </a> <a class="{{if .PageIsSettingsOrganization}}active{{end}} item" href="{{AppSubUrl}}/user/settings/organization"> {{.i18n.Tr "settings.organization"}} @@ -31,7 +20,4 @@ <a class="{{if .PageIsSettingsRepos}}active{{end}} item" href="{{AppSubUrl}}/user/settings/repos"> {{.i18n.Tr "settings.repos"}} </a> - <a class="{{if .PageIsSettingsDelete}}active{{end}} item" href="{{AppSubUrl}}/user/settings/delete"> - {{.i18n.Tr "settings.delete"}} - </a> </div> diff --git a/templates/user/settings/openid.tmpl b/templates/user/settings/openid.tmpl deleted file mode 100644 index 6ae4a8dee8..0000000000 --- a/templates/user/settings/openid.tmpl +++ /dev/null @@ -1,71 +0,0 @@ -{{template "base/head" .}} -<div class="user settings openid"> - {{template "user/settings/navbar" .}} - <div class="ui container"> - {{template "base/alert" .}} - <h4 class="ui top attached header"> - {{.i18n.Tr "settings.manage_openid"}} - </h4> - <div class="ui attached segment"> - <div class="ui openid list"> - <div class="item"> - {{.i18n.Tr "settings.openid_desc"}} - </div> - {{range .OpenIDs}} - <div class="item"> - <div class="right floated content"> - <button class="ui red tiny button delete-button" data-url="{{$.Link}}/delete" data-id="{{.ID}}"> - {{$.i18n.Tr "settings.delete_key"}} - </button> - </div> - <div class="right floated content"> - <form action="{{$.Link}}/toggle_visibility" method="post"> - {{$.CsrfTokenHtml}} - <input name="id" type="hidden" value="{{.ID}}"> - {{if .Show}} - <button class="ui tiny button"> - <i class="icon fa-eye"></i> - {{$.i18n.Tr "settings.hide_openid"}} - </button> - {{else}} - <button class="ui tiny button"> - <i class="icon fa-eye-slash"></i> - {{$.i18n.Tr "settings.show_openid"}} - </button> - {{end}} - </button> - </form> - </div> - <div class="content"> - <strong>{{.URI}}</strong> - </div> - </div> - {{end}} - </div> - </div> - <div class="ui attached bottom segment"> - <form class="ui form" action="{{.Link}}" method="post"> - {{.CsrfTokenHtml}} - <div class="required field {{if .Err_OpenID}}error{{end}}"> - <label for="openid">{{.i18n.Tr "settings.add_new_openid"}}</label> - <input id="openid" name="openid" type="text" autofocus required> - </div> - <button class="ui green button"> - {{.i18n.Tr "settings.add_openid"}} - </button> - </form> - </div> - </div> -</div> - -<div class="ui small basic delete modal"> - <div class="ui icon header"> - <i class="trash icon"></i> - {{.i18n.Tr "settings.openid_deletion"}} - </div> - <div class="content"> - <p>{{.i18n.Tr "settings.openid_deletion_desc"}}</p> - </div> - {{template "base/delete_modal_actions" .}} -</div> -{{template "base/footer" .}} diff --git a/templates/user/settings/organization.tmpl b/templates/user/settings/organization.tmpl index 2d357cb3b5..de541dcd17 100644 --- a/templates/user/settings/organization.tmpl +++ b/templates/user/settings/organization.tmpl @@ -1,5 +1,5 @@ {{template "base/head" .}} -<div class="user settings account_link"> +<div class="user settings organization"> {{template "user/settings/navbar" .}} <div class="ui container"> {{template "base/alert" .}} diff --git a/templates/user/settings/profile.tmpl b/templates/user/settings/profile.tmpl index 4e6930e0f5..e5bb2df011 100644 --- a/templates/user/settings/profile.tmpl +++ b/templates/user/settings/profile.tmpl @@ -58,7 +58,44 @@ <button class="ui green button">{{$.i18n.Tr "settings.update_profile"}}</button> </div> </form> + </div> + + <h4 class="ui top attached header"> + {{.i18n.Tr "settings.avatar"}} + </h4> + <div class="ui attached segment"> + <form class="ui form" action="{{.Link}}/avatar" method="post" enctype="multipart/form-data"> + {{.CsrfTokenHtml}} + {{if not DisableGravatar}} + <div class="inline field"> + <div class="ui radio checkbox"> + <input name="source" value="lookup" type="radio" {{if not .SignedUser.UseCustomAvatar}}checked{{end}}> + <label>{{.i18n.Tr "settings.lookup_avatar_by_mail"}}</label> + </div> + </div> + <div class="field {{if .Err_Gravatar}}error{{end}}"> + <label for="gravatar">Avatar {{.i18n.Tr "email"}}</label> + <input id="gravatar" name="gravatar" value="{{.SignedUser.AvatarEmail}}" /> + </div> + {{end}} + <div class="inline field"> + <div class="ui radio checkbox"> + <input name="source" value="local" type="radio" {{if .SignedUser.UseCustomAvatar}}checked{{end}}> + <label>{{.i18n.Tr "settings.enable_custom_avatar"}}</label> + </div> + </div> + + <div class="inline field"> + <label for="avatar">{{.i18n.Tr "settings.choose_new_avatar"}}</label> + <input name="avatar" type="file" > + </div> + + <div class="field"> + <button class="ui green button">{{$.i18n.Tr "settings.update_avatar"}}</button> + <a class="ui red button delete-post" data-request-url="{{.Link}}/avatar/delete" data-done-url="{{.Link}}">{{$.i18n.Tr "settings.delete_current_avatar"}}</a> + </div> + </form> </div> </div> </div> diff --git a/templates/user/settings/repos.tmpl b/templates/user/settings/repos.tmpl index 39d98c6d1e..efb2c41c5b 100644 --- a/templates/user/settings/repos.tmpl +++ b/templates/user/settings/repos.tmpl @@ -1,5 +1,5 @@ {{template "base/head" .}}
-<div class="user settings">
+<div class="user settings repos">
{{template "user/settings/navbar" .}}
<div class="ui container">
{{template "base/alert" .}}
diff --git a/templates/user/settings/security.tmpl b/templates/user/settings/security.tmpl index b7cd222b30..8e7044f7df 100644 --- a/templates/user/settings/security.tmpl +++ b/templates/user/settings/security.tmpl @@ -1,79 +1,14 @@ {{template "base/head" .}} -<div class="user settings password"> +<div class="user settings security"> {{template "user/settings/navbar" .}} <div class="ui container"> {{template "base/alert" .}} - <h4 class="ui top attached header"> - {{.i18n.Tr "settings.password"}} - </h4> - <div class="ui attached segment"> - {{if or (.SignedUser.IsLocal) (.SignedUser.IsOAuth2)}} - <form class="ui form" action="{{.Link}}?tp=password" method="post"> - {{.CsrfTokenHtml}} - {{if .SignedUser.IsPasswordSet}} - <div class="required field {{if .Err_OldPassword}}error{{end}}"> - <label for="old_password">{{.i18n.Tr "settings.old_password"}}</label> - <input id="old_password" name="old_password" type="password" autocomplete="off" autofocus required> - </div> - {{end}} - <div class="required field {{if .Err_Password}}error{{end}}"> - <label for="password">{{.i18n.Tr "settings.new_password"}}</label> - <input id="password" name="password" type="password" autocomplete="off" required> - </div> - <div class="required field {{if .Err_Password}}error{{end}}"> - <label for="retype">{{.i18n.Tr "settings.retype_new_password"}}</label> - <input id="retype" name="retype" type="password" autocomplete="off" required> - </div> - - <div class="field"> - <button class="ui green button">{{$.i18n.Tr "settings.change_password"}}</button> - <a href="{{AppSubUrl}}/user/forgot_password?email={{.Email}}">{{.i18n.Tr "auth.forgot_password"}}</a> - </div> - </form> - {{else}} - <div class="ui info message"> - <p class="text left">{{$.i18n.Tr "settings.password_change_disabled"}}</p> - </div> - {{end}} - </div> - <br/> - - <h4 class="ui top attached header"> - {{.i18n.Tr "settings.twofa"}} - </h4> - <div class="ui attached segment"> - <p>{{.i18n.Tr "settings.twofa_desc"}}</p> - {{if .TwofaEnrolled}} - <p>{{$.i18n.Tr "settings.twofa_is_enrolled" | Str2html }}</p> - <form class="ui form" action="{{.Link}}/two_factor/regenerate_scratch" method="post" enctype="multipart/form-data"> - {{.CsrfTokenHtml}} - <p>{{.i18n.Tr "settings.regenerate_scratch_token_desc"}}</p> - <button class="ui blue button">{{$.i18n.Tr "settings.twofa_scratch_token_regenerate"}}</button> - </form> - <form class="ui form" action="{{.Link}}/two_factor/disable" method="post" enctype="multipart/form-data" id="disable-form"> - {{.CsrfTokenHtml}} - <p>{{.i18n.Tr "settings.twofa_disable_note"}}</p> - <div class="ui red button delete-button" data-type="form" data-form="#disable-form">{{$.i18n.Tr "settings.twofa_disable"}}</div> - </form> - {{else}} - <p>{{.i18n.Tr "settings.twofa_not_enrolled"}}</p> - <div class="inline field"> - <a class="ui green button" href="{{.Link}}/two_factor/enroll">{{$.i18n.Tr "settings.twofa_enroll"}}</a> - </div> - {{end}} - </div> - </div> -</div> - -<div class="ui small basic delete modal"> - <div class="ui icon header"> - <i class="trash icon"></i> - {{.i18n.Tr "settings.twofa_disable"}} - </div> - <div class="content"> - <p>{{.i18n.Tr "settings.twofa_disable_desc"}}</p> + {{template "user/settings/security_twofa" .}} + {{template "user/settings/security_accountlinks" .}} + {{if .EnableOpenIDSignIn}} + {{template "user/settings/security_openid" .}} + {{end}} </div> - {{template "base/delete_modal_actions" .}} </div> {{template "base/footer" .}} diff --git a/templates/user/settings/security_accountlinks.tmpl b/templates/user/settings/security_accountlinks.tmpl new file mode 100644 index 0000000000..93cc508a54 --- /dev/null +++ b/templates/user/settings/security_accountlinks.tmpl @@ -0,0 +1,36 @@ +<h4 class="ui top attached header"> + {{.i18n.Tr "settings.manage_account_links"}} +</h4> +<div class="ui attached segment"> + <div class="ui key list"> + <div class="item"> + {{.i18n.Tr "settings.manage_account_links_desc"}} + </div> + {{if .AccountLinks}} + {{range $loginSource, $provider := .AccountLinks}} + <div class="item"> + <div class="right floated content"> + <button class="ui red tiny button delete-button" id="delete-account-link" data-url="{{$.Link}}/account_link" data-id="{{$loginSource.ID}}"> + {{$.i18n.Tr "settings.delete_key"}} + </button> + </div> + <div class="content"> + <strong>{{$provider}}</strong> + {{if $loginSource.IsActived}}<span class="text red">{{$.i18n.Tr "settings.active"}}</span>{{end}} + </div> + </div> + {{end}} + {{end}} + </div> +</div> + +<div class="ui small basic delete modal" id="delete-account-link"> + <div class="ui icon header"> + <i class="trash icon"></i> + {{.i18n.Tr "settings.remove_account_link"}} + </div> + <div class="content"> + <p>{{.i18n.Tr "settings.remove_account_link_desc"}}</p> + </div> + {{template "base/delete_modal_actions" .}} +</div> diff --git a/templates/user/settings/security_openid.tmpl b/templates/user/settings/security_openid.tmpl new file mode 100644 index 0000000000..12f4aab419 --- /dev/null +++ b/templates/user/settings/security_openid.tmpl @@ -0,0 +1,63 @@ +<h4 class="ui top attached header"> + {{.i18n.Tr "settings.manage_openid"}} +</h4> +<div class="ui attached segment"> + <div class="ui openid list"> + <div class="item"> + {{.i18n.Tr "settings.openid_desc"}} + </div> + {{range .OpenIDs}} + <div class="item"> + <div class="right floated content"> + <button class="ui red tiny button delete-button" id="delete-openid" data-url="{{$.Link}}/openid/delete" data-id="{{.ID}}"> + {{$.i18n.Tr "settings.delete_key"}} + </button> + </div> + <div class="right floated content"> + <form action="{{$.Link}}/openid/toggle_visibility" method="post"> + {{$.CsrfTokenHtml}} + <input name="id" type="hidden" value="{{.ID}}"> + {{if .Show}} + <button class="ui tiny button"> + <i class="icon fa-eye"></i> + {{$.i18n.Tr "settings.hide_openid"}} + </button> + {{else}} + <button class="ui tiny button"> + <i class="icon fa-eye-slash"></i> + {{$.i18n.Tr "settings.show_openid"}} + </button> + {{end}} + </button> + </form> + </div> + <div class="content"> + <strong>{{.URI}}</strong> + </div> + </div> + {{end}} + </div> +</div> +<div class="ui attached bottom segment"> + <form class="ui form" action="{{.Link}}/openid" method="post"> + {{.CsrfTokenHtml}} + <div class="required field {{if .Err_OpenID}}error{{end}}"> + <label for="openid">{{.i18n.Tr "settings.add_new_openid"}}</label> + <input id="openid" name="openid" type="text" autofocus required> + </div> + <button class="ui green button"> + {{.i18n.Tr "settings.add_openid"}} + </button> + </form> +</div> + +<div class="ui small basic delete modal" id="delete-openid"> + <div class="ui icon header"> + <i class="trash icon"></i> + {{.i18n.Tr "settings.openid_deletion"}} + </div> + <div class="content"> + <p>{{.i18n.Tr "settings.openid_deletion_desc"}}</p> + </div> + {{template "base/delete_modal_actions" .}} +</div> diff --git a/templates/user/settings/security_twofa.tmpl b/templates/user/settings/security_twofa.tmpl new file mode 100644 index 0000000000..05112a21bb --- /dev/null +++ b/templates/user/settings/security_twofa.tmpl @@ -0,0 +1,35 @@ +<h4 class="ui top attached header"> + {{.i18n.Tr "settings.twofa"}} +</h4> +<div class="ui attached segment"> + <p>{{.i18n.Tr "settings.twofa_desc"}}</p> + {{if .TwofaEnrolled}} + <p>{{$.i18n.Tr "settings.twofa_is_enrolled" | Str2html }}</p> + <form class="ui form" action="{{.Link}}/two_factor/regenerate_scratch" method="post" enctype="multipart/form-data"> + {{.CsrfTokenHtml}} + <p>{{.i18n.Tr "settings.regenerate_scratch_token_desc"}}</p> + <button class="ui blue button">{{$.i18n.Tr "settings.twofa_scratch_token_regenerate"}}</button> + </form> + <form class="ui form" action="{{.Link}}/two_factor/disable" method="post" enctype="multipart/form-data" id="disable-form"> + {{.CsrfTokenHtml}} + <p>{{.i18n.Tr "settings.twofa_disable_note"}}</p> + <div class="ui red button delete-button" id="disable-twofa" data-type="form" data-form="#disable-form">{{$.i18n.Tr "settings.twofa_disable"}}</div> + </form> + {{else}} + <p>{{.i18n.Tr "settings.twofa_not_enrolled"}}</p> + <div class="inline field"> + <a class="ui green button" href="{{.Link}}/two_factor/enroll">{{$.i18n.Tr "settings.twofa_enroll"}}</a> + </div> + {{end}} +</div> + +<div class="ui small basic delete modal" id="disable-twofa"> + <div class="ui icon header"> + <i class="trash icon"></i> + {{.i18n.Tr "settings.twofa_disable"}} + </div> + <div class="content"> + <p>{{.i18n.Tr "settings.twofa_disable_desc"}}</p> + </div> + {{template "base/delete_modal_actions" .}} +</div> diff --git a/templates/user/settings/twofa.tmpl b/templates/user/settings/twofa.tmpl deleted file mode 100644 index c6a7a7cba0..0000000000 --- a/templates/user/settings/twofa.tmpl +++ /dev/null @@ -1,44 +0,0 @@ -{{template "base/head" .}} -<div class="user settings delete"> - {{template "user/settings/navbar" .}} - <div class="ui container"> - {{template "base/alert" .}} - <h4 class="ui top attached header"> - {{.i18n.Tr "settings.twofa"}} - </h4> - <div class="ui attached segment"> - <p>{{.i18n.Tr "settings.twofa_desc"}}</p> - {{if .TwofaEnrolled}} - <p>{{$.i18n.Tr "settings.twofa_is_enrolled" | Str2html }}</p> - <form class="ui form" action="{{.Link}}/regenerate_scratch" method="post" enctype="multipart/form-data"> - {{.CsrfTokenHtml}} - <p>{{.i18n.Tr "settings.regenerate_scratch_token_desc"}}</p> - <button class="ui blue button">{{$.i18n.Tr "settings.twofa_scratch_token_regenerate"}}</button> - </form> - <form class="ui form" action="{{.Link}}/disable" method="post" enctype="multipart/form-data" id="disable-form"> - {{.CsrfTokenHtml}} - <p>{{.i18n.Tr "settings.twofa_disable_note"}}</p> - <div class="ui red button delete-button" data-type="form" data-form="#disable-form">{{$.i18n.Tr "settings.twofa_disable"}}</div> - </form> - {{else}} - <p>{{.i18n.Tr "settings.twofa_not_enrolled"}}</p> - <div class="inline field"> - <a class="ui green button" href="{{.Link}}/enroll">{{$.i18n.Tr "settings.twofa_enroll"}}</a> - </div> - {{end}} - </div> - </div> -</div> - -<div class="ui small basic delete modal"> - <div class="ui icon header"> - <i class="trash icon"></i> - {{.i18n.Tr "settings.twofa_disable"}} - </div> - <div class="content"> - <p>{{.i18n.Tr "settings.twofa_disable_desc"}}</p> - </div> - {{template "base/delete_modal_actions" .}} -</div> - -{{template "base/footer" .}} diff --git a/templates/user/settings/twofa_enroll.tmpl b/templates/user/settings/twofa_enroll.tmpl index 9238fb61a5..0205532ac8 100644 --- a/templates/user/settings/twofa_enroll.tmpl +++ b/templates/user/settings/twofa_enroll.tmpl @@ -1,5 +1,5 @@ {{template "base/head" .}} -<div class="user settings delete"> +<div class="user settings twofa"> {{template "user/settings/navbar" .}} <div class="ui container"> {{template "base/alert" .}} |