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.

password.go 9.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346
  1. // Copyright 2019 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package auth
  4. import (
  5. "errors"
  6. "net/http"
  7. "code.gitea.io/gitea/models/auth"
  8. user_model "code.gitea.io/gitea/models/user"
  9. "code.gitea.io/gitea/modules/auth/password"
  10. "code.gitea.io/gitea/modules/base"
  11. "code.gitea.io/gitea/modules/context"
  12. "code.gitea.io/gitea/modules/log"
  13. "code.gitea.io/gitea/modules/setting"
  14. "code.gitea.io/gitea/modules/timeutil"
  15. "code.gitea.io/gitea/modules/web"
  16. "code.gitea.io/gitea/modules/web/middleware"
  17. "code.gitea.io/gitea/routers/utils"
  18. "code.gitea.io/gitea/services/forms"
  19. "code.gitea.io/gitea/services/mailer"
  20. )
  21. var (
  22. // tplMustChangePassword template for updating a user's password
  23. tplMustChangePassword base.TplName = "user/auth/change_passwd"
  24. tplForgotPassword base.TplName = "user/auth/forgot_passwd"
  25. tplResetPassword base.TplName = "user/auth/reset_passwd"
  26. )
  27. // ForgotPasswd render the forget password page
  28. func ForgotPasswd(ctx *context.Context) {
  29. ctx.Data["Title"] = ctx.Tr("auth.forgot_password_title")
  30. if setting.MailService == nil {
  31. log.Warn(ctx.Tr("auth.disable_forgot_password_mail_admin"))
  32. ctx.Data["IsResetDisable"] = true
  33. ctx.HTML(http.StatusOK, tplForgotPassword)
  34. return
  35. }
  36. ctx.Data["Email"] = ctx.FormString("email")
  37. ctx.Data["IsResetRequest"] = true
  38. ctx.HTML(http.StatusOK, tplForgotPassword)
  39. }
  40. // ForgotPasswdPost response for forget password request
  41. func ForgotPasswdPost(ctx *context.Context) {
  42. ctx.Data["Title"] = ctx.Tr("auth.forgot_password_title")
  43. if setting.MailService == nil {
  44. ctx.NotFound("ForgotPasswdPost", nil)
  45. return
  46. }
  47. ctx.Data["IsResetRequest"] = true
  48. email := ctx.FormString("email")
  49. ctx.Data["Email"] = email
  50. u, err := user_model.GetUserByEmail(ctx, email)
  51. if err != nil {
  52. if user_model.IsErrUserNotExist(err) {
  53. ctx.Data["ResetPwdCodeLives"] = timeutil.MinutesToFriendly(setting.Service.ResetPwdCodeLives, ctx.Locale)
  54. ctx.Data["IsResetSent"] = true
  55. ctx.HTML(http.StatusOK, tplForgotPassword)
  56. return
  57. }
  58. ctx.ServerError("user.ResetPasswd(check existence)", err)
  59. return
  60. }
  61. if !u.IsLocal() && !u.IsOAuth2() {
  62. ctx.Data["Err_Email"] = true
  63. ctx.RenderWithErr(ctx.Tr("auth.non_local_account"), tplForgotPassword, nil)
  64. return
  65. }
  66. if setting.CacheService.Enabled && ctx.Cache.IsExist("MailResendLimit_"+u.LowerName) {
  67. ctx.Data["ResendLimited"] = true
  68. ctx.HTML(http.StatusOK, tplForgotPassword)
  69. return
  70. }
  71. mailer.SendResetPasswordMail(u)
  72. if setting.CacheService.Enabled {
  73. if err = ctx.Cache.Put("MailResendLimit_"+u.LowerName, u.LowerName, 180); err != nil {
  74. log.Error("Set cache(MailResendLimit) fail: %v", err)
  75. }
  76. }
  77. ctx.Data["ResetPwdCodeLives"] = timeutil.MinutesToFriendly(setting.Service.ResetPwdCodeLives, ctx.Locale)
  78. ctx.Data["IsResetSent"] = true
  79. ctx.HTML(http.StatusOK, tplForgotPassword)
  80. }
  81. func commonResetPassword(ctx *context.Context) (*user_model.User, *auth.TwoFactor) {
  82. code := ctx.FormString("code")
  83. ctx.Data["Title"] = ctx.Tr("auth.reset_password")
  84. ctx.Data["Code"] = code
  85. if nil != ctx.Doer {
  86. ctx.Data["user_signed_in"] = true
  87. }
  88. if len(code) == 0 {
  89. ctx.Flash.Error(ctx.Tr("auth.invalid_code"))
  90. return nil, nil
  91. }
  92. // Fail early, don't frustrate the user
  93. u := user_model.VerifyUserActiveCode(code)
  94. if u == nil {
  95. ctx.Flash.Error(ctx.Tr("auth.invalid_code"))
  96. return nil, nil
  97. }
  98. twofa, err := auth.GetTwoFactorByUID(u.ID)
  99. if err != nil {
  100. if !auth.IsErrTwoFactorNotEnrolled(err) {
  101. ctx.Error(http.StatusInternalServerError, "CommonResetPassword", err.Error())
  102. return nil, nil
  103. }
  104. } else {
  105. ctx.Data["has_two_factor"] = true
  106. ctx.Data["scratch_code"] = ctx.FormBool("scratch_code")
  107. }
  108. // Show the user that they are affecting the account that they intended to
  109. ctx.Data["user_email"] = u.Email
  110. if nil != ctx.Doer && u.ID != ctx.Doer.ID {
  111. ctx.Flash.Error(ctx.Tr("auth.reset_password_wrong_user", ctx.Doer.Email, u.Email))
  112. return nil, nil
  113. }
  114. return u, twofa
  115. }
  116. // ResetPasswd render the account recovery page
  117. func ResetPasswd(ctx *context.Context) {
  118. ctx.Data["IsResetForm"] = true
  119. commonResetPassword(ctx)
  120. if ctx.Written() {
  121. return
  122. }
  123. ctx.HTML(http.StatusOK, tplResetPassword)
  124. }
  125. // ResetPasswdPost response from account recovery request
  126. func ResetPasswdPost(ctx *context.Context) {
  127. u, twofa := commonResetPassword(ctx)
  128. if ctx.Written() {
  129. return
  130. }
  131. if u == nil {
  132. // Flash error has been set
  133. ctx.HTML(http.StatusOK, tplResetPassword)
  134. return
  135. }
  136. // Validate password length.
  137. passwd := ctx.FormString("password")
  138. if len(passwd) < setting.MinPasswordLength {
  139. ctx.Data["IsResetForm"] = true
  140. ctx.Data["Err_Password"] = true
  141. ctx.RenderWithErr(ctx.Tr("auth.password_too_short", setting.MinPasswordLength), tplResetPassword, nil)
  142. return
  143. } else if !password.IsComplexEnough(passwd) {
  144. ctx.Data["IsResetForm"] = true
  145. ctx.Data["Err_Password"] = true
  146. ctx.RenderWithErr(password.BuildComplexityError(ctx.Locale), tplResetPassword, nil)
  147. return
  148. } else if pwned, err := password.IsPwned(ctx, passwd); pwned || err != nil {
  149. errMsg := ctx.Tr("auth.password_pwned")
  150. if err != nil {
  151. log.Error(err.Error())
  152. errMsg = ctx.Tr("auth.password_pwned_err")
  153. }
  154. ctx.Data["IsResetForm"] = true
  155. ctx.Data["Err_Password"] = true
  156. ctx.RenderWithErr(errMsg, tplResetPassword, nil)
  157. return
  158. }
  159. // Handle two-factor
  160. regenerateScratchToken := false
  161. if twofa != nil {
  162. if ctx.FormBool("scratch_code") {
  163. if !twofa.VerifyScratchToken(ctx.FormString("token")) {
  164. ctx.Data["IsResetForm"] = true
  165. ctx.Data["Err_Token"] = true
  166. ctx.RenderWithErr(ctx.Tr("auth.twofa_scratch_token_incorrect"), tplResetPassword, nil)
  167. return
  168. }
  169. regenerateScratchToken = true
  170. } else {
  171. passcode := ctx.FormString("passcode")
  172. ok, err := twofa.ValidateTOTP(passcode)
  173. if err != nil {
  174. ctx.Error(http.StatusInternalServerError, "ValidateTOTP", err.Error())
  175. return
  176. }
  177. if !ok || twofa.LastUsedPasscode == passcode {
  178. ctx.Data["IsResetForm"] = true
  179. ctx.Data["Err_Passcode"] = true
  180. ctx.RenderWithErr(ctx.Tr("auth.twofa_passcode_incorrect"), tplResetPassword, nil)
  181. return
  182. }
  183. twofa.LastUsedPasscode = passcode
  184. if err = auth.UpdateTwoFactor(twofa); err != nil {
  185. ctx.ServerError("ResetPasswdPost: UpdateTwoFactor", err)
  186. return
  187. }
  188. }
  189. }
  190. var err error
  191. if u.Rands, err = user_model.GetUserSalt(); err != nil {
  192. ctx.ServerError("UpdateUser", err)
  193. return
  194. }
  195. if err = u.SetPassword(passwd); err != nil {
  196. ctx.ServerError("UpdateUser", err)
  197. return
  198. }
  199. u.MustChangePassword = false
  200. if err := user_model.UpdateUserCols(ctx, u, "must_change_password", "passwd", "passwd_hash_algo", "rands", "salt"); err != nil {
  201. ctx.ServerError("UpdateUser", err)
  202. return
  203. }
  204. log.Trace("User password reset: %s", u.Name)
  205. ctx.Data["IsResetFailed"] = true
  206. remember := len(ctx.FormString("remember")) != 0
  207. if regenerateScratchToken {
  208. // Invalidate the scratch token.
  209. _, err = twofa.GenerateScratchToken()
  210. if err != nil {
  211. ctx.ServerError("UserSignIn", err)
  212. return
  213. }
  214. if err = auth.UpdateTwoFactor(twofa); err != nil {
  215. ctx.ServerError("UserSignIn", err)
  216. return
  217. }
  218. handleSignInFull(ctx, u, remember, false)
  219. if ctx.Written() {
  220. return
  221. }
  222. ctx.Flash.Info(ctx.Tr("auth.twofa_scratch_used"))
  223. ctx.Redirect(setting.AppSubURL + "/user/settings/security")
  224. return
  225. }
  226. handleSignIn(ctx, u, remember)
  227. }
  228. // MustChangePassword renders the page to change a user's password
  229. func MustChangePassword(ctx *context.Context) {
  230. ctx.Data["Title"] = ctx.Tr("auth.must_change_password")
  231. ctx.Data["ChangePasscodeLink"] = setting.AppSubURL + "/user/settings/change_password"
  232. ctx.Data["MustChangePassword"] = true
  233. ctx.HTML(http.StatusOK, tplMustChangePassword)
  234. }
  235. // MustChangePasswordPost response for updating a user's password after their
  236. // account was created by an admin
  237. func MustChangePasswordPost(ctx *context.Context) {
  238. form := web.GetForm(ctx).(*forms.MustChangePasswordForm)
  239. ctx.Data["Title"] = ctx.Tr("auth.must_change_password")
  240. ctx.Data["ChangePasscodeLink"] = setting.AppSubURL + "/user/settings/change_password"
  241. if ctx.HasError() {
  242. ctx.HTML(http.StatusOK, tplMustChangePassword)
  243. return
  244. }
  245. u := ctx.Doer
  246. // Make sure only requests for users who are eligible to change their password via
  247. // this method passes through
  248. if !u.MustChangePassword {
  249. ctx.ServerError("MustUpdatePassword", errors.New("cannot update password.. Please visit the settings page"))
  250. return
  251. }
  252. if form.Password != form.Retype {
  253. ctx.Data["Err_Password"] = true
  254. ctx.RenderWithErr(ctx.Tr("form.password_not_match"), tplMustChangePassword, &form)
  255. return
  256. }
  257. if len(form.Password) < setting.MinPasswordLength {
  258. ctx.Data["Err_Password"] = true
  259. ctx.RenderWithErr(ctx.Tr("auth.password_too_short", setting.MinPasswordLength), tplMustChangePassword, &form)
  260. return
  261. }
  262. if !password.IsComplexEnough(form.Password) {
  263. ctx.Data["Err_Password"] = true
  264. ctx.RenderWithErr(password.BuildComplexityError(ctx.Locale), tplMustChangePassword, &form)
  265. return
  266. }
  267. pwned, err := password.IsPwned(ctx, form.Password)
  268. if pwned {
  269. ctx.Data["Err_Password"] = true
  270. errMsg := ctx.Tr("auth.password_pwned")
  271. if err != nil {
  272. log.Error(err.Error())
  273. errMsg = ctx.Tr("auth.password_pwned_err")
  274. }
  275. ctx.RenderWithErr(errMsg, tplMustChangePassword, &form)
  276. return
  277. }
  278. if err = u.SetPassword(form.Password); err != nil {
  279. ctx.ServerError("UpdateUser", err)
  280. return
  281. }
  282. u.MustChangePassword = false
  283. if err := user_model.UpdateUserCols(ctx, u, "must_change_password", "passwd", "passwd_hash_algo", "salt"); err != nil {
  284. ctx.ServerError("UpdateUser", err)
  285. return
  286. }
  287. ctx.Flash.Success(ctx.Tr("settings.change_password_success"))
  288. log.Trace("User updated password: %s", u.Name)
  289. if redirectTo := ctx.GetSiteCookie("redirect_to"); len(redirectTo) > 0 && !utils.IsExternalURL(redirectTo) {
  290. middleware.DeleteRedirectToCookie(ctx.Resp)
  291. ctx.RedirectToFirst(redirectTo)
  292. return
  293. }
  294. ctx.Redirect(setting.AppSubURL + "/")
  295. }