You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

720 lines
20 KiB

10 years ago
Oauth2 consumer (#679) * initial stuff for oauth2 login, fails on: * login button on the signIn page to start the OAuth2 flow and a callback for each provider Only GitHub is implemented for now * show login button only when the OAuth2 consumer is configured (and activated) * create macaron group for oauth2 urls * prevent net/http in modules (other then oauth2) * use a new data sessions oauth2 folder for storing the oauth2 session data * add missing 2FA when this is enabled on the user * add password option for OAuth2 user , for use with git over http and login to the GUI * add tip for registering a GitHub OAuth application * at startup of Gitea register all configured providers and also on adding/deleting of new providers * custom handling of errors in oauth2 request init + show better tip * add ExternalLoginUser model and migration script to add it to database * link a external account to an existing account (still need to handle wrong login and signup) and remove if user is removed * remove the linked external account from the user his settings * if user is unknown we allow him to register a new account or link it to some existing account * sign up with button on signin page (als change OAuth2Provider structure so we can store basic stuff about providers) * from gorilla/sessions docs: "Important Note: If you aren't using gorilla/mux, you need to wrap your handlers with context.ClearHandler as or else you will leak memory!" (we're using gorilla/sessions for storing oauth2 sessions) * use updated goth lib that now supports getting the OAuth2 user if the AccessToken is still valid instead of re-authenticating (prevent flooding the OAuth2 provider)
7 years ago
10 years ago
9 years ago
10 years ago
10 years ago
10 years ago
10 years ago
7 years ago
Add support for federated avatars (#3320) * Add support for federated avatars Fixes #3105 Removes avatar fetching duplication code Adds an "Enable Federated Avatar" checkbox in user settings (defaults to unchecked) Moves avatar settings all in the same form, making local and remote avatars mutually exclusive Renames UploadAvatarForm to AvatarForm as it's not anymore only for uploading * Run gofmt on all modified files * Move Avatar form in its own page * Add go-libravatar dependency to vendor/ dir Hopefully helps with accepting the contribution. See also #3214 * Revert "Add go-libravatar dependency to vendor/ dir" This reverts commit a8cb93ae640bbb90f7d25012fc257bda9fae9b82. * Make federated avatar setting a global configuration Removes the per-user setting * Move avatar handling back to base tool, disable federated avatar in offline mode * Format, handle error * Properly set fallback host * Use unsupported github.com mirror for importing go-libravatar * Remove comment showing life exists outside of github.com ... pity, but contribution would not be accepted otherwise * Use Combo for Get and Post methods over /avatar * FEDERATED_AVATAR -> ENABLE_FEDERATED_AVATAR * Fix persistance of federated avatar lookup checkbox at install time * Federated Avatars -> Enable Federated Avatars * Use len(string) == 0 instead of string == "" * Move import line where it belong See https://github.com/Unknwon/go-code-convention/blob/master/en-US/import_packages.md Pity the import url is still the unofficial one, but oh well... * Save a line (and waste much more expensive time) * Remove redundant parens * Remove an empty line * Remove empty lines * Reorder lines to make diff smaller * Remove another newline Unknwon review got me start a fight against newlines * Move DISABLE_GRAVATAR and ENABLE_FEDERATED_AVATAR after OFFLINE_MODE On re-reading the diff I figured what Unknwon meant here: https://github.com/gogits/gogs/pull/3320/files#r73741106 * Remove newlines that weren't there before my intervention
8 years ago
8 years ago
Add support for federated avatars (#3320) * Add support for federated avatars Fixes #3105 Removes avatar fetching duplication code Adds an "Enable Federated Avatar" checkbox in user settings (defaults to unchecked) Moves avatar settings all in the same form, making local and remote avatars mutually exclusive Renames UploadAvatarForm to AvatarForm as it's not anymore only for uploading * Run gofmt on all modified files * Move Avatar form in its own page * Add go-libravatar dependency to vendor/ dir Hopefully helps with accepting the contribution. See also #3214 * Revert "Add go-libravatar dependency to vendor/ dir" This reverts commit a8cb93ae640bbb90f7d25012fc257bda9fae9b82. * Make federated avatar setting a global configuration Removes the per-user setting * Move avatar handling back to base tool, disable federated avatar in offline mode * Format, handle error * Properly set fallback host * Use unsupported github.com mirror for importing go-libravatar * Remove comment showing life exists outside of github.com ... pity, but contribution would not be accepted otherwise * Use Combo for Get and Post methods over /avatar * FEDERATED_AVATAR -> ENABLE_FEDERATED_AVATAR * Fix persistance of federated avatar lookup checkbox at install time * Federated Avatars -> Enable Federated Avatars * Use len(string) == 0 instead of string == "" * Move import line where it belong See https://github.com/Unknwon/go-code-convention/blob/master/en-US/import_packages.md Pity the import url is still the unofficial one, but oh well... * Save a line (and waste much more expensive time) * Remove redundant parens * Remove an empty line * Remove empty lines * Reorder lines to make diff smaller * Remove another newline Unknwon review got me start a fight against newlines * Move DISABLE_GRAVATAR and ENABLE_FEDERATED_AVATAR after OFFLINE_MODE On re-reading the diff I figured what Unknwon meant here: https://github.com/gogits/gogs/pull/3320/files#r73741106 * Remove newlines that weren't there before my intervention
8 years ago
10 years ago
Add support for federated avatars (#3320) * Add support for federated avatars Fixes #3105 Removes avatar fetching duplication code Adds an "Enable Federated Avatar" checkbox in user settings (defaults to unchecked) Moves avatar settings all in the same form, making local and remote avatars mutually exclusive Renames UploadAvatarForm to AvatarForm as it's not anymore only for uploading * Run gofmt on all modified files * Move Avatar form in its own page * Add go-libravatar dependency to vendor/ dir Hopefully helps with accepting the contribution. See also #3214 * Revert "Add go-libravatar dependency to vendor/ dir" This reverts commit a8cb93ae640bbb90f7d25012fc257bda9fae9b82. * Make federated avatar setting a global configuration Removes the per-user setting * Move avatar handling back to base tool, disable federated avatar in offline mode * Format, handle error * Properly set fallback host * Use unsupported github.com mirror for importing go-libravatar * Remove comment showing life exists outside of github.com ... pity, but contribution would not be accepted otherwise * Use Combo for Get and Post methods over /avatar * FEDERATED_AVATAR -> ENABLE_FEDERATED_AVATAR * Fix persistance of federated avatar lookup checkbox at install time * Federated Avatars -> Enable Federated Avatars * Use len(string) == 0 instead of string == "" * Move import line where it belong See https://github.com/Unknwon/go-code-convention/blob/master/en-US/import_packages.md Pity the import url is still the unofficial one, but oh well... * Save a line (and waste much more expensive time) * Remove redundant parens * Remove an empty line * Remove empty lines * Reorder lines to make diff smaller * Remove another newline Unknwon review got me start a fight against newlines * Move DISABLE_GRAVATAR and ENABLE_FEDERATED_AVATAR after OFFLINE_MODE On re-reading the diff I figured what Unknwon meant here: https://github.com/gogits/gogs/pull/3320/files#r73741106 * Remove newlines that weren't there before my intervention
8 years ago
10 years ago
10 years ago
Add support for federated avatars (#3320) * Add support for federated avatars Fixes #3105 Removes avatar fetching duplication code Adds an "Enable Federated Avatar" checkbox in user settings (defaults to unchecked) Moves avatar settings all in the same form, making local and remote avatars mutually exclusive Renames UploadAvatarForm to AvatarForm as it's not anymore only for uploading * Run gofmt on all modified files * Move Avatar form in its own page * Add go-libravatar dependency to vendor/ dir Hopefully helps with accepting the contribution. See also #3214 * Revert "Add go-libravatar dependency to vendor/ dir" This reverts commit a8cb93ae640bbb90f7d25012fc257bda9fae9b82. * Make federated avatar setting a global configuration Removes the per-user setting * Move avatar handling back to base tool, disable federated avatar in offline mode * Format, handle error * Properly set fallback host * Use unsupported github.com mirror for importing go-libravatar * Remove comment showing life exists outside of github.com ... pity, but contribution would not be accepted otherwise * Use Combo for Get and Post methods over /avatar * FEDERATED_AVATAR -> ENABLE_FEDERATED_AVATAR * Fix persistance of federated avatar lookup checkbox at install time * Federated Avatars -> Enable Federated Avatars * Use len(string) == 0 instead of string == "" * Move import line where it belong See https://github.com/Unknwon/go-code-convention/blob/master/en-US/import_packages.md Pity the import url is still the unofficial one, but oh well... * Save a line (and waste much more expensive time) * Remove redundant parens * Remove an empty line * Remove empty lines * Reorder lines to make diff smaller * Remove another newline Unknwon review got me start a fight against newlines * Move DISABLE_GRAVATAR and ENABLE_FEDERATED_AVATAR after OFFLINE_MODE On re-reading the diff I figured what Unknwon meant here: https://github.com/gogits/gogs/pull/3320/files#r73741106 * Remove newlines that weren't there before my intervention
8 years ago
Add support for federated avatars (#3320) * Add support for federated avatars Fixes #3105 Removes avatar fetching duplication code Adds an "Enable Federated Avatar" checkbox in user settings (defaults to unchecked) Moves avatar settings all in the same form, making local and remote avatars mutually exclusive Renames UploadAvatarForm to AvatarForm as it's not anymore only for uploading * Run gofmt on all modified files * Move Avatar form in its own page * Add go-libravatar dependency to vendor/ dir Hopefully helps with accepting the contribution. See also #3214 * Revert "Add go-libravatar dependency to vendor/ dir" This reverts commit a8cb93ae640bbb90f7d25012fc257bda9fae9b82. * Make federated avatar setting a global configuration Removes the per-user setting * Move avatar handling back to base tool, disable federated avatar in offline mode * Format, handle error * Properly set fallback host * Use unsupported github.com mirror for importing go-libravatar * Remove comment showing life exists outside of github.com ... pity, but contribution would not be accepted otherwise * Use Combo for Get and Post methods over /avatar * FEDERATED_AVATAR -> ENABLE_FEDERATED_AVATAR * Fix persistance of federated avatar lookup checkbox at install time * Federated Avatars -> Enable Federated Avatars * Use len(string) == 0 instead of string == "" * Move import line where it belong See https://github.com/Unknwon/go-code-convention/blob/master/en-US/import_packages.md Pity the import url is still the unofficial one, but oh well... * Save a line (and waste much more expensive time) * Remove redundant parens * Remove an empty line * Remove empty lines * Reorder lines to make diff smaller * Remove another newline Unknwon review got me start a fight against newlines * Move DISABLE_GRAVATAR and ENABLE_FEDERATED_AVATAR after OFFLINE_MODE On re-reading the diff I figured what Unknwon meant here: https://github.com/gogits/gogs/pull/3320/files#r73741106 * Remove newlines that weren't there before my intervention
8 years ago
Add support for federated avatars (#3320) * Add support for federated avatars Fixes #3105 Removes avatar fetching duplication code Adds an "Enable Federated Avatar" checkbox in user settings (defaults to unchecked) Moves avatar settings all in the same form, making local and remote avatars mutually exclusive Renames UploadAvatarForm to AvatarForm as it's not anymore only for uploading * Run gofmt on all modified files * Move Avatar form in its own page * Add go-libravatar dependency to vendor/ dir Hopefully helps with accepting the contribution. See also #3214 * Revert "Add go-libravatar dependency to vendor/ dir" This reverts commit a8cb93ae640bbb90f7d25012fc257bda9fae9b82. * Make federated avatar setting a global configuration Removes the per-user setting * Move avatar handling back to base tool, disable federated avatar in offline mode * Format, handle error * Properly set fallback host * Use unsupported github.com mirror for importing go-libravatar * Remove comment showing life exists outside of github.com ... pity, but contribution would not be accepted otherwise * Use Combo for Get and Post methods over /avatar * FEDERATED_AVATAR -> ENABLE_FEDERATED_AVATAR * Fix persistance of federated avatar lookup checkbox at install time * Federated Avatars -> Enable Federated Avatars * Use len(string) == 0 instead of string == "" * Move import line where it belong See https://github.com/Unknwon/go-code-convention/blob/master/en-US/import_packages.md Pity the import url is still the unofficial one, but oh well... * Save a line (and waste much more expensive time) * Remove redundant parens * Remove an empty line * Remove empty lines * Reorder lines to make diff smaller * Remove another newline Unknwon review got me start a fight against newlines * Move DISABLE_GRAVATAR and ENABLE_FEDERATED_AVATAR after OFFLINE_MODE On re-reading the diff I figured what Unknwon meant here: https://github.com/gogits/gogs/pull/3320/files#r73741106 * Remove newlines that weren't there before my intervention
8 years ago
10 years ago
10 years ago
Oauth2 consumer (#679) * initial stuff for oauth2 login, fails on: * login button on the signIn page to start the OAuth2 flow and a callback for each provider Only GitHub is implemented for now * show login button only when the OAuth2 consumer is configured (and activated) * create macaron group for oauth2 urls * prevent net/http in modules (other then oauth2) * use a new data sessions oauth2 folder for storing the oauth2 session data * add missing 2FA when this is enabled on the user * add password option for OAuth2 user , for use with git over http and login to the GUI * add tip for registering a GitHub OAuth application * at startup of Gitea register all configured providers and also on adding/deleting of new providers * custom handling of errors in oauth2 request init + show better tip * add ExternalLoginUser model and migration script to add it to database * link a external account to an existing account (still need to handle wrong login and signup) and remove if user is removed * remove the linked external account from the user his settings * if user is unknown we allow him to register a new account or link it to some existing account * sign up with button on signin page (als change OAuth2Provider structure so we can store basic stuff about providers) * from gorilla/sessions docs: "Important Note: If you aren't using gorilla/mux, you need to wrap your handlers with context.ClearHandler as or else you will leak memory!" (we're using gorilla/sessions for storing oauth2 sessions) * use updated goth lib that now supports getting the OAuth2 user if the AccessToken is still valid instead of re-authenticating (prevent flooding the OAuth2 provider)
7 years ago
10 years ago
8 years ago
10 years ago
10 years ago
9 years ago
9 years ago
Oauth2 consumer (#679) * initial stuff for oauth2 login, fails on: * login button on the signIn page to start the OAuth2 flow and a callback for each provider Only GitHub is implemented for now * show login button only when the OAuth2 consumer is configured (and activated) * create macaron group for oauth2 urls * prevent net/http in modules (other then oauth2) * use a new data sessions oauth2 folder for storing the oauth2 session data * add missing 2FA when this is enabled on the user * add password option for OAuth2 user , for use with git over http and login to the GUI * add tip for registering a GitHub OAuth application * at startup of Gitea register all configured providers and also on adding/deleting of new providers * custom handling of errors in oauth2 request init + show better tip * add ExternalLoginUser model and migration script to add it to database * link a external account to an existing account (still need to handle wrong login and signup) and remove if user is removed * remove the linked external account from the user his settings * if user is unknown we allow him to register a new account or link it to some existing account * sign up with button on signin page (als change OAuth2Provider structure so we can store basic stuff about providers) * from gorilla/sessions docs: "Important Note: If you aren't using gorilla/mux, you need to wrap your handlers with context.ClearHandler as or else you will leak memory!" (we're using gorilla/sessions for storing oauth2 sessions) * use updated goth lib that now supports getting the OAuth2 user if the AccessToken is still valid instead of re-authenticating (prevent flooding the OAuth2 provider)
7 years ago
10 years ago
  1. // Copyright 2014 The Gogs Authors. All rights reserved.
  2. // Use of this source code is governed by a MIT-style
  3. // license that can be found in the LICENSE file.
  4. package user
  5. import (
  6. "bytes"
  7. "errors"
  8. "fmt"
  9. "io/ioutil"
  10. "strings"
  11. "github.com/Unknwon/com"
  12. "github.com/pquerna/otp"
  13. "github.com/pquerna/otp/totp"
  14. "encoding/base64"
  15. "html/template"
  16. "image/png"
  17. "code.gitea.io/gitea/models"
  18. "code.gitea.io/gitea/modules/auth"
  19. "code.gitea.io/gitea/modules/base"
  20. "code.gitea.io/gitea/modules/context"
  21. "code.gitea.io/gitea/modules/log"
  22. "code.gitea.io/gitea/modules/setting"
  23. )
  24. const (
  25. tplSettingsProfile base.TplName = "user/settings/profile"
  26. tplSettingsAvatar base.TplName = "user/settings/avatar"
  27. tplSettingsPassword base.TplName = "user/settings/password"
  28. tplSettingsEmails base.TplName = "user/settings/email"
  29. tplSettingsSSHKeys base.TplName = "user/settings/sshkeys"
  30. tplSettingsSocial base.TplName = "user/settings/social"
  31. tplSettingsApplications base.TplName = "user/settings/applications"
  32. tplSettingsTwofa base.TplName = "user/settings/twofa"
  33. tplSettingsTwofaEnroll base.TplName = "user/settings/twofa_enroll"
  34. tplSettingsAccountLink base.TplName = "user/settings/account_link"
  35. tplSettingsDelete base.TplName = "user/settings/delete"
  36. tplSecurity base.TplName = "user/security"
  37. )
  38. // Settings render user's profile page
  39. func Settings(ctx *context.Context) {
  40. ctx.Data["Title"] = ctx.Tr("settings")
  41. ctx.Data["PageIsSettingsProfile"] = true
  42. ctx.HTML(200, tplSettingsProfile)
  43. }
  44. func handleUsernameChange(ctx *context.Context, newName string) {
  45. // Non-local users are not allowed to change their username.
  46. if len(newName) == 0 || !ctx.User.IsLocal() {
  47. return
  48. }
  49. // Check if user name has been changed
  50. if ctx.User.LowerName != strings.ToLower(newName) {
  51. if err := models.ChangeUserName(ctx.User, newName); err != nil {
  52. switch {
  53. case models.IsErrUserAlreadyExist(err):
  54. ctx.Flash.Error(ctx.Tr("newName_been_taken"))
  55. ctx.Redirect(setting.AppSubURL + "/user/settings")
  56. case models.IsErrEmailAlreadyUsed(err):
  57. ctx.Flash.Error(ctx.Tr("form.email_been_used"))
  58. ctx.Redirect(setting.AppSubURL + "/user/settings")
  59. case models.IsErrNameReserved(err):
  60. ctx.Flash.Error(ctx.Tr("user.newName_reserved"))
  61. ctx.Redirect(setting.AppSubURL + "/user/settings")
  62. case models.IsErrNamePatternNotAllowed(err):
  63. ctx.Flash.Error(ctx.Tr("user.newName_pattern_not_allowed"))
  64. ctx.Redirect(setting.AppSubURL + "/user/settings")
  65. default:
  66. ctx.Handle(500, "ChangeUserName", err)
  67. }
  68. return
  69. }
  70. log.Trace("User name changed: %s -> %s", ctx.User.Name, newName)
  71. }
  72. // In case it's just a case change
  73. ctx.User.Name = newName
  74. ctx.User.LowerName = strings.ToLower(newName)
  75. }
  76. // SettingsPost response for change user's profile
  77. func SettingsPost(ctx *context.Context, form auth.UpdateProfileForm) {
  78. ctx.Data["Title"] = ctx.Tr("settings")
  79. ctx.Data["PageIsSettingsProfile"] = true
  80. if ctx.HasError() {
  81. ctx.HTML(200, tplSettingsProfile)
  82. return
  83. }
  84. handleUsernameChange(ctx, form.Name)
  85. if ctx.Written() {
  86. return
  87. }
  88. ctx.User.FullName = form.FullName
  89. ctx.User.Email = form.Email
  90. ctx.User.KeepEmailPrivate = form.KeepEmailPrivate
  91. ctx.User.Website = form.Website
  92. ctx.User.Location = form.Location
  93. if err := models.UpdateUserSetting(ctx.User); err != nil {
  94. if _, ok := err.(models.ErrEmailAlreadyUsed); ok {
  95. ctx.Flash.Error(ctx.Tr("form.email_been_used"))
  96. ctx.Redirect(setting.AppSubURL + "/user/settings")
  97. return
  98. }
  99. ctx.Handle(500, "UpdateUser", err)
  100. return
  101. }
  102. log.Trace("User settings updated: %s", ctx.User.Name)
  103. ctx.Flash.Success(ctx.Tr("settings.update_profile_success"))
  104. ctx.Redirect(setting.AppSubURL + "/user/settings")
  105. }
  106. // UpdateAvatarSetting update user's avatar
  107. // FIXME: limit size.
  108. func UpdateAvatarSetting(ctx *context.Context, form auth.AvatarForm, ctxUser *models.User) error {
  109. ctxUser.UseCustomAvatar = form.Source == auth.AvatarLocal
  110. if len(form.Gravatar) > 0 {
  111. ctxUser.Avatar = base.EncodeMD5(form.Gravatar)
  112. ctxUser.AvatarEmail = form.Gravatar
  113. }
  114. if form.Avatar != nil {
  115. fr, err := form.Avatar.Open()
  116. if err != nil {
  117. return fmt.Errorf("Avatar.Open: %v", err)
  118. }
  119. defer fr.Close()
  120. data, err := ioutil.ReadAll(fr)
  121. if err != nil {
  122. return fmt.Errorf("ioutil.ReadAll: %v", err)
  123. }
  124. if !base.IsImageFile(data) {
  125. return errors.New(ctx.Tr("settings.uploaded_avatar_not_a_image"))
  126. }
  127. if err = ctxUser.UploadAvatar(data); err != nil {
  128. return fmt.Errorf("UploadAvatar: %v", err)
  129. }
  130. } else {
  131. // No avatar is uploaded but setting has been changed to enable,
  132. // generate a random one when needed.
  133. if ctxUser.UseCustomAvatar && !com.IsFile(ctxUser.CustomAvatarPath()) {
  134. if err := ctxUser.GenerateRandomAvatar(); err != nil {
  135. log.Error(4, "GenerateRandomAvatar[%d]: %v", ctxUser.ID, err)
  136. }
  137. }
  138. }
  139. if err := models.UpdateUser(ctxUser); err != nil {
  140. return fmt.Errorf("UpdateUser: %v", err)
  141. }
  142. return nil
  143. }
  144. // SettingsAvatar render user avatar page
  145. func SettingsAvatar(ctx *context.Context) {
  146. ctx.Data["Title"] = ctx.Tr("settings")
  147. ctx.Data["PageIsSettingsAvatar"] = true
  148. ctx.HTML(200, tplSettingsAvatar)
  149. }
  150. // SettingsAvatarPost response for change user's avatar request
  151. func SettingsAvatarPost(ctx *context.Context, form auth.AvatarForm) {
  152. if err := UpdateAvatarSetting(ctx, form, ctx.User); err != nil {
  153. ctx.Flash.Error(err.Error())
  154. } else {
  155. ctx.Flash.Success(ctx.Tr("settings.update_avatar_success"))
  156. }
  157. ctx.Redirect(setting.AppSubURL + "/user/settings/avatar")
  158. }
  159. // SettingsDeleteAvatar render delete avatar page
  160. func SettingsDeleteAvatar(ctx *context.Context) {
  161. if err := ctx.User.DeleteAvatar(); err != nil {
  162. ctx.Flash.Error(err.Error())
  163. }
  164. ctx.Redirect(setting.AppSubURL + "/user/settings/avatar")
  165. }
  166. // SettingsPassword render change user's password page
  167. func SettingsPassword(ctx *context.Context) {
  168. ctx.Data["Title"] = ctx.Tr("settings")
  169. ctx.Data["PageIsSettingsPassword"] = true
  170. ctx.Data["Email"] = ctx.User.Email
  171. ctx.HTML(200, tplSettingsPassword)
  172. }
  173. // SettingsPasswordPost response for change user's password
  174. func SettingsPasswordPost(ctx *context.Context, form auth.ChangePasswordForm) {
  175. ctx.Data["Title"] = ctx.Tr("settings")
  176. ctx.Data["PageIsSettingsPassword"] = true
  177. ctx.Data["PageIsSettingsDelete"] = true
  178. if ctx.HasError() {
  179. ctx.HTML(200, tplSettingsPassword)
  180. return
  181. }
  182. if ctx.User.IsPasswordSet() && !ctx.User.ValidatePassword(form.OldPassword) {
  183. ctx.Flash.Error(ctx.Tr("settings.password_incorrect"))
  184. } else if form.Password != form.Retype {
  185. ctx.Flash.Error(ctx.Tr("form.password_not_match"))
  186. } else {
  187. ctx.User.Passwd = form.Password
  188. var err error
  189. if ctx.User.Salt, err = models.GetUserSalt(); err != nil {
  190. ctx.Handle(500, "UpdateUser", err)
  191. return
  192. }
  193. ctx.User.EncodePasswd()
  194. if err := models.UpdateUser(ctx.User); err != nil {
  195. ctx.Handle(500, "UpdateUser", err)
  196. return
  197. }
  198. log.Trace("User password updated: %s", ctx.User.Name)
  199. ctx.Flash.Success(ctx.Tr("settings.change_password_success"))
  200. }
  201. ctx.Redirect(setting.AppSubURL + "/user/settings/password")
  202. }
  203. // SettingsEmails render user's emails page
  204. func SettingsEmails(ctx *context.Context) {
  205. ctx.Data["Title"] = ctx.Tr("settings")
  206. ctx.Data["PageIsSettingsEmails"] = true
  207. emails, err := models.GetEmailAddresses(ctx.User.ID)
  208. if err != nil {
  209. ctx.Handle(500, "GetEmailAddresses", err)
  210. return
  211. }
  212. ctx.Data["Emails"] = emails
  213. ctx.HTML(200, tplSettingsEmails)
  214. }
  215. // SettingsEmailPost response for change user's email
  216. func SettingsEmailPost(ctx *context.Context, form auth.AddEmailForm) {
  217. ctx.Data["Title"] = ctx.Tr("settings")
  218. ctx.Data["PageIsSettingsEmails"] = true
  219. // Make emailaddress primary.
  220. if ctx.Query("_method") == "PRIMARY" {
  221. if err := models.MakeEmailPrimary(&models.EmailAddress{ID: ctx.QueryInt64("id")}); err != nil {
  222. ctx.Handle(500, "MakeEmailPrimary", err)
  223. return
  224. }
  225. log.Trace("Email made primary: %s", ctx.User.Name)
  226. ctx.Redirect(setting.AppSubURL + "/user/settings/email")
  227. return
  228. }
  229. // Add Email address.
  230. emails, err := models.GetEmailAddresses(ctx.User.ID)
  231. if err != nil {
  232. ctx.Handle(500, "GetEmailAddresses", err)
  233. return
  234. }
  235. ctx.Data["Emails"] = emails
  236. if ctx.HasError() {
  237. ctx.HTML(200, tplSettingsEmails)
  238. return
  239. }
  240. email := &models.EmailAddress{
  241. UID: ctx.User.ID,
  242. Email: form.Email,
  243. IsActivated: !setting.Service.RegisterEmailConfirm,
  244. }
  245. if err := models.AddEmailAddress(email); err != nil {
  246. if models.IsErrEmailAlreadyUsed(err) {
  247. ctx.RenderWithErr(ctx.Tr("form.email_been_used"), tplSettingsEmails, &form)
  248. return
  249. }
  250. ctx.Handle(500, "AddEmailAddress", err)
  251. return
  252. }
  253. // Send confirmation email
  254. if setting.Service.RegisterEmailConfirm {
  255. models.SendActivateEmailMail(ctx.Context, ctx.User, email)
  256. if err := ctx.Cache.Put("MailResendLimit_"+ctx.User.LowerName, ctx.User.LowerName, 180); err != nil {
  257. log.Error(4, "Set cache(MailResendLimit) fail: %v", err)
  258. }
  259. ctx.Flash.Info(ctx.Tr("settings.add_email_confirmation_sent", email.Email, setting.Service.ActiveCodeLives/60))
  260. } else {
  261. ctx.Flash.Success(ctx.Tr("settings.add_email_success"))
  262. }
  263. log.Trace("Email address added: %s", email.Email)
  264. ctx.Redirect(setting.AppSubURL + "/user/settings/email")
  265. }
  266. // DeleteEmail response for delete user's email
  267. func DeleteEmail(ctx *context.Context) {
  268. if err := models.DeleteEmailAddress(&models.EmailAddress{ID: ctx.QueryInt64("id"), UID: ctx.User.ID}); err != nil {
  269. ctx.Handle(500, "DeleteEmail", err)
  270. return
  271. }
  272. log.Trace("Email address deleted: %s", ctx.User.Name)
  273. ctx.Flash.Success(ctx.Tr("settings.email_deletion_success"))
  274. ctx.JSON(200, map[string]interface{}{
  275. "redirect": setting.AppSubURL + "/user/settings/email",
  276. })
  277. }
  278. // SettingsSSHKeys render user's SSH public keys page
  279. func SettingsSSHKeys(ctx *context.Context) {
  280. ctx.Data["Title"] = ctx.Tr("settings")
  281. ctx.Data["PageIsSettingsSSHKeys"] = true
  282. keys, err := models.ListPublicKeys(ctx.User.ID)
  283. if err != nil {
  284. ctx.Handle(500, "ListPublicKeys", err)
  285. return
  286. }
  287. ctx.Data["Keys"] = keys
  288. ctx.HTML(200, tplSettingsSSHKeys)
  289. }
  290. // SettingsSSHKeysPost response for change user's SSH keys
  291. func SettingsSSHKeysPost(ctx *context.Context, form auth.AddSSHKeyForm) {
  292. ctx.Data["Title"] = ctx.Tr("settings")
  293. ctx.Data["PageIsSettingsSSHKeys"] = true
  294. keys, err := models.ListPublicKeys(ctx.User.ID)
  295. if err != nil {
  296. ctx.Handle(500, "ListPublicKeys", err)
  297. return
  298. }
  299. ctx.Data["Keys"] = keys
  300. if ctx.HasError() {
  301. ctx.HTML(200, tplSettingsSSHKeys)
  302. return
  303. }
  304. content, err := models.CheckPublicKeyString(form.Content)
  305. if err != nil {
  306. if models.IsErrKeyUnableVerify(err) {
  307. ctx.Flash.Info(ctx.Tr("form.unable_verify_ssh_key"))
  308. } else {
  309. ctx.Flash.Error(ctx.Tr("form.invalid_ssh_key", err.Error()))
  310. ctx.Redirect(setting.AppSubURL + "/user/settings/ssh")
  311. return
  312. }
  313. }
  314. if _, err = models.AddPublicKey(ctx.User.ID, form.Title, content); err != nil {
  315. ctx.Data["HasError"] = true
  316. switch {
  317. case models.IsErrKeyAlreadyExist(err):
  318. ctx.Data["Err_Content"] = true
  319. ctx.RenderWithErr(ctx.Tr("settings.ssh_key_been_used"), tplSettingsSSHKeys, &form)
  320. case models.IsErrKeyNameAlreadyUsed(err):
  321. ctx.Data["Err_Title"] = true
  322. ctx.RenderWithErr(ctx.Tr("settings.ssh_key_name_used"), tplSettingsSSHKeys, &form)
  323. default:
  324. ctx.Handle(500, "AddPublicKey", err)
  325. }
  326. return
  327. }
  328. ctx.Flash.Success(ctx.Tr("settings.add_key_success", form.Title))
  329. ctx.Redirect(setting.AppSubURL + "/user/settings/ssh")
  330. }
  331. // DeleteSSHKey response for delete user's SSH key
  332. func DeleteSSHKey(ctx *context.Context) {
  333. if err := models.DeletePublicKey(ctx.User, ctx.QueryInt64("id")); err != nil {
  334. ctx.Flash.Error("DeletePublicKey: " + err.Error())
  335. } else {
  336. ctx.Flash.Success(ctx.Tr("settings.ssh_key_deletion_success"))
  337. }
  338. ctx.JSON(200, map[string]interface{}{
  339. "redirect": setting.AppSubURL + "/user/settings/ssh",
  340. })
  341. }
  342. // SettingsApplications render user's access tokens page
  343. func SettingsApplications(ctx *context.Context) {
  344. ctx.Data["Title"] = ctx.Tr("settings")
  345. ctx.Data["PageIsSettingsApplications"] = true
  346. tokens, err := models.ListAccessTokens(ctx.User.ID)
  347. if err != nil {
  348. ctx.Handle(500, "ListAccessTokens", err)
  349. return
  350. }
  351. ctx.Data["Tokens"] = tokens
  352. ctx.HTML(200, tplSettingsApplications)
  353. }
  354. // SettingsApplicationsPost response for add user's access token
  355. func SettingsApplicationsPost(ctx *context.Context, form auth.NewAccessTokenForm) {
  356. ctx.Data["Title"] = ctx.Tr("settings")
  357. ctx.Data["PageIsSettingsApplications"] = true
  358. if ctx.HasError() {
  359. tokens, err := models.ListAccessTokens(ctx.User.ID)
  360. if err != nil {
  361. ctx.Handle(500, "ListAccessTokens", err)
  362. return
  363. }
  364. ctx.Data["Tokens"] = tokens
  365. ctx.HTML(200, tplSettingsApplications)
  366. return
  367. }
  368. t := &models.AccessToken{
  369. UID: ctx.User.ID,
  370. Name: form.Name,
  371. }
  372. if err := models.NewAccessToken(t); err != nil {
  373. ctx.Handle(500, "NewAccessToken", err)
  374. return
  375. }
  376. ctx.Flash.Success(ctx.Tr("settings.generate_token_succees"))
  377. ctx.Flash.Info(t.Sha1)
  378. ctx.Redirect(setting.AppSubURL + "/user/settings/applications")
  379. }
  380. // SettingsDeleteApplication response for delete user access token
  381. func SettingsDeleteApplication(ctx *context.Context) {
  382. if err := models.DeleteAccessTokenByID(ctx.QueryInt64("id"), ctx.User.ID); err != nil {
  383. ctx.Flash.Error("DeleteAccessTokenByID: " + err.Error())
  384. } else {
  385. ctx.Flash.Success(ctx.Tr("settings.delete_token_success"))
  386. }
  387. ctx.JSON(200, map[string]interface{}{
  388. "redirect": setting.AppSubURL + "/user/settings/applications",
  389. })
  390. }
  391. // SettingsTwoFactor renders the 2FA page.
  392. func SettingsTwoFactor(ctx *context.Context) {
  393. ctx.Data["Title"] = ctx.Tr("settings")
  394. ctx.Data["PageIsSettingsTwofa"] = true
  395. enrolled := true
  396. _, err := models.GetTwoFactorByUID(ctx.User.ID)
  397. if err != nil {
  398. if models.IsErrTwoFactorNotEnrolled(err) {
  399. enrolled = false
  400. } else {
  401. ctx.Handle(500, "SettingsTwoFactor", err)
  402. return
  403. }
  404. }
  405. ctx.Data["TwofaEnrolled"] = enrolled
  406. ctx.HTML(200, tplSettingsTwofa)
  407. }
  408. // SettingsTwoFactorRegenerateScratch regenerates the user's 2FA scratch code.
  409. func SettingsTwoFactorRegenerateScratch(ctx *context.Context) {
  410. ctx.Data["Title"] = ctx.Tr("settings")
  411. ctx.Data["PageIsSettingsTwofa"] = true
  412. t, err := models.GetTwoFactorByUID(ctx.User.ID)
  413. if err != nil {
  414. ctx.Handle(500, "SettingsTwoFactor", err)
  415. return
  416. }
  417. if err = t.GenerateScratchToken(); err != nil {
  418. ctx.Handle(500, "SettingsTwoFactor", err)
  419. return
  420. }
  421. if err = models.UpdateTwoFactor(t); err != nil {
  422. ctx.Handle(500, "SettingsTwoFactor", err)
  423. return
  424. }
  425. ctx.Flash.Success(ctx.Tr("settings.twofa_scratch_token_regenerated", t.ScratchToken))
  426. ctx.Redirect(setting.AppSubURL + "/user/settings/two_factor")
  427. }
  428. // SettingsTwoFactorDisable deletes the user's 2FA settings.
  429. func SettingsTwoFactorDisable(ctx *context.Context) {
  430. ctx.Data["Title"] = ctx.Tr("settings")
  431. ctx.Data["PageIsSettingsTwofa"] = true
  432. t, err := models.GetTwoFactorByUID(ctx.User.ID)
  433. if err != nil {
  434. ctx.Handle(500, "SettingsTwoFactor", err)
  435. return
  436. }
  437. if err = models.DeleteTwoFactorByID(t.ID, ctx.User.ID); err != nil {
  438. ctx.Handle(500, "SettingsTwoFactor", err)
  439. return
  440. }
  441. ctx.Flash.Success(ctx.Tr("settings.twofa_disabled"))
  442. ctx.Redirect(setting.AppSubURL + "/user/settings/two_factor")
  443. }
  444. func twofaGenerateSecretAndQr(ctx *context.Context) bool {
  445. var otpKey *otp.Key
  446. var err error
  447. uri := ctx.Session.Get("twofaUri")
  448. if uri != nil {
  449. otpKey, err = otp.NewKeyFromURL(uri.(string))
  450. }
  451. if otpKey == nil {
  452. err = nil // clear the error, in case the URL was invalid
  453. otpKey, err = totp.Generate(totp.GenerateOpts{
  454. Issuer: setting.AppName,
  455. AccountName: ctx.User.Name,
  456. })
  457. if err != nil {
  458. ctx.Handle(500, "SettingsTwoFactor", err)
  459. return false
  460. }
  461. }
  462. ctx.Data["TwofaSecret"] = otpKey.Secret()
  463. img, err := otpKey.Image(320, 240)
  464. if err != nil {
  465. ctx.Handle(500, "SettingsTwoFactor", err)
  466. return false
  467. }
  468. var imgBytes bytes.Buffer
  469. if err = png.Encode(&imgBytes, img); err != nil {
  470. ctx.Handle(500, "SettingsTwoFactor", err)
  471. return false
  472. }
  473. ctx.Data["QrUri"] = template.URL("data:image/png;base64," + base64.StdEncoding.EncodeToString(imgBytes.Bytes()))
  474. ctx.Session.Set("twofaSecret", otpKey.Secret())
  475. ctx.Session.Set("twofaUri", otpKey.String())
  476. return true
  477. }
  478. // SettingsTwoFactorEnroll shows the page where the user can enroll into 2FA.
  479. func SettingsTwoFactorEnroll(ctx *context.Context) {
  480. ctx.Data["Title"] = ctx.Tr("settings")
  481. ctx.Data["PageIsSettingsTwofa"] = true
  482. t, err := models.GetTwoFactorByUID(ctx.User.ID)
  483. if t != nil {
  484. // already enrolled
  485. ctx.Handle(500, "SettingsTwoFactor", err)
  486. return
  487. }
  488. if err != nil && !models.IsErrTwoFactorNotEnrolled(err) {
  489. ctx.Handle(500, "SettingsTwoFactor", err)
  490. return
  491. }
  492. if !twofaGenerateSecretAndQr(ctx) {
  493. return
  494. }
  495. ctx.HTML(200, tplSettingsTwofaEnroll)
  496. }
  497. // SettingsTwoFactorEnrollPost handles enrolling the user into 2FA.
  498. func SettingsTwoFactorEnrollPost(ctx *context.Context, form auth.TwoFactorAuthForm) {
  499. ctx.Data["Title"] = ctx.Tr("settings")
  500. ctx.Data["PageIsSettingsTwofa"] = true
  501. t, err := models.GetTwoFactorByUID(ctx.User.ID)
  502. if t != nil {
  503. // already enrolled
  504. ctx.Handle(500, "SettingsTwoFactor", err)
  505. return
  506. }
  507. if err != nil && !models.IsErrTwoFactorNotEnrolled(err) {
  508. ctx.Handle(500, "SettingsTwoFactor", err)
  509. return
  510. }
  511. if ctx.HasError() {
  512. if !twofaGenerateSecretAndQr(ctx) {
  513. return
  514. }
  515. ctx.HTML(200, tplSettingsTwofaEnroll)
  516. return
  517. }
  518. secret := ctx.Session.Get("twofaSecret").(string)
  519. if !totp.Validate(form.Passcode, secret) {
  520. if !twofaGenerateSecretAndQr(ctx) {
  521. return
  522. }
  523. ctx.Flash.Error(ctx.Tr("settings.passcode_invalid"))
  524. ctx.HTML(200, tplSettingsTwofaEnroll)
  525. return
  526. }
  527. t = &models.TwoFactor{
  528. UID: ctx.User.ID,
  529. }
  530. err = t.SetSecret(secret)
  531. if err != nil {
  532. ctx.Handle(500, "SettingsTwoFactor", err)
  533. return
  534. }
  535. err = t.GenerateScratchToken()
  536. if err != nil {
  537. ctx.Handle(500, "SettingsTwoFactor", err)
  538. return
  539. }
  540. if err = models.NewTwoFactor(t); err != nil {
  541. ctx.Handle(500, "SettingsTwoFactor", err)
  542. return
  543. }
  544. ctx.Session.Delete("twofaSecret")
  545. ctx.Session.Delete("twofaUri")
  546. ctx.Flash.Success(ctx.Tr("settings.twofa_enrolled", t.ScratchToken))
  547. ctx.Redirect(setting.AppSubURL + "/user/settings/two_factor")
  548. }
  549. // SettingsAccountLinks render the account links settings page
  550. func SettingsAccountLinks(ctx *context.Context) {
  551. ctx.Data["Title"] = ctx.Tr("settings")
  552. ctx.Data["PageIsSettingsAccountLink"] = true
  553. accountLinks, err := models.ListAccountLinks(ctx.User)
  554. if err != nil {
  555. ctx.Handle(500, "ListAccountLinks", err)
  556. return
  557. }
  558. // map the provider display name with the LoginSource
  559. sources := make(map[*models.LoginSource]string)
  560. for _, externalAccount := range accountLinks {
  561. if loginSource, err := models.GetLoginSourceByID(externalAccount.LoginSourceID); err == nil {
  562. var providerDisplayName string
  563. if loginSource.IsOAuth2() {
  564. providerTechnicalName := loginSource.OAuth2().Provider
  565. providerDisplayName = models.OAuth2Providers[providerTechnicalName].DisplayName
  566. } else {
  567. providerDisplayName = loginSource.Name
  568. }
  569. sources[loginSource] = providerDisplayName
  570. }
  571. }
  572. ctx.Data["AccountLinks"] = sources
  573. ctx.HTML(200, tplSettingsAccountLink)
  574. }
  575. // SettingsDeleteAccountLink delete a single account link
  576. func SettingsDeleteAccountLink(ctx *context.Context) {
  577. if _, err := models.RemoveAccountLink(ctx.User, ctx.QueryInt64("loginSourceID")); err != nil {
  578. ctx.Flash.Error("RemoveAccountLink: " + err.Error())
  579. } else {
  580. ctx.Flash.Success(ctx.Tr("settings.remove_account_link_success"))
  581. }
  582. ctx.JSON(200, map[string]interface{}{
  583. "redirect": setting.AppSubURL + "/user/settings/account_link",
  584. })
  585. }
  586. // SettingsDelete render user suicide page and response for delete user himself
  587. func SettingsDelete(ctx *context.Context) {
  588. ctx.Data["Title"] = ctx.Tr("settings")
  589. ctx.Data["PageIsSettingsDelete"] = true
  590. ctx.Data["Email"] = ctx.User.Email
  591. if ctx.Req.Method == "POST" {
  592. if _, err := models.UserSignIn(ctx.User.Name, ctx.Query("password")); err != nil {
  593. if models.IsErrUserNotExist(err) {
  594. ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_password"), tplSettingsDelete, nil)
  595. } else {
  596. ctx.Handle(500, "UserSignIn", err)
  597. }
  598. return
  599. }
  600. if err := models.DeleteUser(ctx.User); err != nil {
  601. switch {
  602. case models.IsErrUserOwnRepos(err):
  603. ctx.Flash.Error(ctx.Tr("form.still_own_repo"))
  604. ctx.Redirect(setting.AppSubURL + "/user/settings/delete")
  605. case models.IsErrUserHasOrgs(err):
  606. ctx.Flash.Error(ctx.Tr("form.still_has_org"))
  607. ctx.Redirect(setting.AppSubURL + "/user/settings/delete")
  608. default:
  609. ctx.Handle(500, "DeleteUser", err)
  610. }
  611. } else {
  612. log.Trace("Account deleted: %s", ctx.User.Name)
  613. ctx.Redirect(setting.AppSubURL + "/")
  614. }
  615. return
  616. }
  617. ctx.HTML(200, tplSettingsDelete)
  618. }