gitea源码

password.go 9.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  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/log"
  11. "code.gitea.io/gitea/modules/optional"
  12. "code.gitea.io/gitea/modules/setting"
  13. "code.gitea.io/gitea/modules/templates"
  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/services/context"
  18. "code.gitea.io/gitea/services/forms"
  19. "code.gitea.io/gitea/services/mailer"
  20. user_service "code.gitea.io/gitea/services/user"
  21. )
  22. var (
  23. // tplMustChangePassword template for updating a user's password
  24. tplMustChangePassword templates.TplName = "user/auth/change_passwd"
  25. tplForgotPassword templates.TplName = "user/auth/forgot_passwd"
  26. tplResetPassword templates.TplName = "user/auth/reset_passwd"
  27. )
  28. // ForgotPasswd render the forget password page
  29. func ForgotPasswd(ctx *context.Context) {
  30. ctx.Data["Title"] = ctx.Tr("auth.forgot_password_title")
  31. if setting.MailService == nil {
  32. log.Warn("no mail service configured")
  33. ctx.Data["IsResetDisable"] = true
  34. ctx.HTML(http.StatusOK, tplForgotPassword)
  35. return
  36. }
  37. ctx.Data["Email"] = ctx.FormString("email")
  38. ctx.Data["IsResetRequest"] = true
  39. ctx.HTML(http.StatusOK, tplForgotPassword)
  40. }
  41. // ForgotPasswdPost response for forget password request
  42. func ForgotPasswdPost(ctx *context.Context) {
  43. ctx.Data["Title"] = ctx.Tr("auth.forgot_password_title")
  44. if setting.MailService == nil {
  45. ctx.NotFound(nil)
  46. return
  47. }
  48. ctx.Data["IsResetRequest"] = true
  49. email := ctx.FormString("email")
  50. ctx.Data["Email"] = email
  51. u, err := user_model.GetUserByEmail(ctx, email)
  52. if err != nil {
  53. if user_model.IsErrUserNotExist(err) {
  54. ctx.Data["ResetPwdCodeLives"] = timeutil.MinutesToFriendly(setting.Service.ResetPwdCodeLives, ctx.Locale)
  55. ctx.Data["IsResetSent"] = true
  56. ctx.HTML(http.StatusOK, tplForgotPassword)
  57. return
  58. }
  59. ctx.ServerError("user.ResetPasswd(check existence)", err)
  60. return
  61. }
  62. if !u.IsLocal() && !u.IsOAuth2() {
  63. ctx.Data["Err_Email"] = true
  64. ctx.RenderWithErr(ctx.Tr("auth.non_local_account"), tplForgotPassword, nil)
  65. return
  66. }
  67. if ctx.Cache.IsExist("MailResendLimit_" + u.LowerName) {
  68. ctx.Data["ResendLimited"] = true
  69. ctx.HTML(http.StatusOK, tplForgotPassword)
  70. return
  71. }
  72. mailer.SendResetPasswordMail(u)
  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. ctx.Data["ResetPwdCodeLives"] = timeutil.MinutesToFriendly(setting.Service.ResetPwdCodeLives, ctx.Locale)
  77. ctx.Data["IsResetSent"] = true
  78. ctx.HTML(http.StatusOK, tplForgotPassword)
  79. }
  80. func commonResetPassword(ctx *context.Context) (*user_model.User, *auth.TwoFactor) {
  81. code := ctx.FormString("code")
  82. ctx.Data["Title"] = ctx.Tr("auth.reset_password")
  83. ctx.Data["Code"] = code
  84. if nil != ctx.Doer {
  85. ctx.Data["user_signed_in"] = true
  86. }
  87. if len(code) == 0 {
  88. ctx.Flash.Error(ctx.Tr("auth.invalid_code_forgot_password", setting.AppSubURL+"/user/forgot_password"), true)
  89. return nil, nil
  90. }
  91. // Fail early, don't frustrate the user
  92. u := user_model.VerifyUserTimeLimitCode(ctx, &user_model.TimeLimitCodeOptions{Purpose: user_model.TimeLimitCodeResetPassword}, code)
  93. if u == nil {
  94. ctx.Flash.Error(ctx.Tr("auth.invalid_code_forgot_password", setting.AppSubURL+"/user/forgot_password"), true)
  95. return nil, nil
  96. }
  97. twofa, err := auth.GetTwoFactorByUID(ctx, u.ID)
  98. if err != nil {
  99. if !auth.IsErrTwoFactorNotEnrolled(err) {
  100. ctx.HTTPError(http.StatusInternalServerError, "CommonResetPassword", err.Error())
  101. return nil, nil
  102. }
  103. } else {
  104. ctx.Data["has_two_factor"] = true
  105. ctx.Data["scratch_code"] = ctx.FormBool("scratch_code")
  106. }
  107. // Show the user that they are affecting the account that they intended to
  108. ctx.Data["user_email"] = u.Email
  109. if nil != ctx.Doer && u.ID != ctx.Doer.ID {
  110. ctx.Flash.Error(ctx.Tr("auth.reset_password_wrong_user", ctx.Doer.Email, u.Email), true)
  111. return nil, nil
  112. }
  113. return u, twofa
  114. }
  115. // ResetPasswd render the account recovery page
  116. func ResetPasswd(ctx *context.Context) {
  117. ctx.Data["IsResetForm"] = true
  118. commonResetPassword(ctx)
  119. if ctx.Written() {
  120. return
  121. }
  122. ctx.HTML(http.StatusOK, tplResetPassword)
  123. }
  124. // ResetPasswdPost response from account recovery request
  125. func ResetPasswdPost(ctx *context.Context) {
  126. u, twofa := commonResetPassword(ctx)
  127. if ctx.Written() {
  128. return
  129. }
  130. if u == nil {
  131. // Flash error has been set
  132. ctx.HTML(http.StatusOK, tplResetPassword)
  133. return
  134. }
  135. // Handle two-factor
  136. regenerateScratchToken := false
  137. if twofa != nil {
  138. if ctx.FormBool("scratch_code") {
  139. if !twofa.VerifyScratchToken(ctx.FormString("token")) {
  140. ctx.Data["IsResetForm"] = true
  141. ctx.Data["Err_Token"] = true
  142. ctx.RenderWithErr(ctx.Tr("auth.twofa_scratch_token_incorrect"), tplResetPassword, nil)
  143. return
  144. }
  145. regenerateScratchToken = true
  146. } else {
  147. passcode := ctx.FormString("passcode")
  148. ok, err := twofa.ValidateTOTP(passcode)
  149. if err != nil {
  150. ctx.HTTPError(http.StatusInternalServerError, "ValidateTOTP", err.Error())
  151. return
  152. }
  153. if !ok || twofa.LastUsedPasscode == passcode {
  154. ctx.Data["IsResetForm"] = true
  155. ctx.Data["Err_Passcode"] = true
  156. ctx.RenderWithErr(ctx.Tr("auth.twofa_passcode_incorrect"), tplResetPassword, nil)
  157. return
  158. }
  159. twofa.LastUsedPasscode = passcode
  160. if err = auth.UpdateTwoFactor(ctx, twofa); err != nil {
  161. ctx.ServerError("ResetPasswdPost: UpdateTwoFactor", err)
  162. return
  163. }
  164. }
  165. }
  166. opts := &user_service.UpdateAuthOptions{
  167. Password: optional.Some(ctx.FormString("password")),
  168. MustChangePassword: optional.Some(false),
  169. }
  170. if err := user_service.UpdateAuth(ctx, u, opts); err != nil {
  171. ctx.Data["IsResetForm"] = true
  172. ctx.Data["Err_Password"] = true
  173. switch {
  174. case errors.Is(err, password.ErrMinLength):
  175. ctx.RenderWithErr(ctx.Tr("auth.password_too_short", setting.MinPasswordLength), tplResetPassword, nil)
  176. case errors.Is(err, password.ErrComplexity):
  177. ctx.RenderWithErr(password.BuildComplexityError(ctx.Locale), tplResetPassword, nil)
  178. case errors.Is(err, password.ErrIsPwned):
  179. ctx.RenderWithErr(ctx.Tr("auth.password_pwned", "https://haveibeenpwned.com/Passwords"), tplResetPassword, nil)
  180. case password.IsErrIsPwnedRequest(err):
  181. ctx.RenderWithErr(ctx.Tr("auth.password_pwned_err"), tplResetPassword, nil)
  182. default:
  183. ctx.ServerError("UpdateAuth", err)
  184. }
  185. return
  186. }
  187. log.Trace("User password reset: %s", u.Name)
  188. ctx.Data["IsResetFailed"] = true
  189. remember := len(ctx.FormString("remember")) != 0
  190. if regenerateScratchToken {
  191. // Invalidate the scratch token.
  192. _, err := twofa.GenerateScratchToken()
  193. if err != nil {
  194. ctx.ServerError("UserSignIn", err)
  195. return
  196. }
  197. if err = auth.UpdateTwoFactor(ctx, twofa); err != nil {
  198. ctx.ServerError("UserSignIn", err)
  199. return
  200. }
  201. handleSignInFull(ctx, u, remember, false)
  202. if ctx.Written() {
  203. return
  204. }
  205. ctx.Flash.Info(ctx.Tr("auth.twofa_scratch_used"))
  206. ctx.Redirect(setting.AppSubURL + "/user/settings/security")
  207. return
  208. }
  209. handleSignIn(ctx, u, remember)
  210. }
  211. // MustChangePassword renders the page to change a user's password
  212. func MustChangePassword(ctx *context.Context) {
  213. ctx.Data["Title"] = ctx.Tr("auth.must_change_password")
  214. ctx.Data["ChangePasscodeLink"] = setting.AppSubURL + "/user/settings/change_password"
  215. ctx.Data["MustChangePassword"] = true
  216. ctx.HTML(http.StatusOK, tplMustChangePassword)
  217. }
  218. // MustChangePasswordPost response for updating a user's password after their
  219. // account was created by an admin
  220. func MustChangePasswordPost(ctx *context.Context) {
  221. form := web.GetForm(ctx).(*forms.MustChangePasswordForm)
  222. ctx.Data["Title"] = ctx.Tr("auth.must_change_password")
  223. ctx.Data["ChangePasscodeLink"] = setting.AppSubURL + "/user/settings/change_password"
  224. if ctx.HasError() {
  225. ctx.HTML(http.StatusOK, tplMustChangePassword)
  226. return
  227. }
  228. // Make sure only requests for users who are eligible to change their password via
  229. // this method passes through
  230. if !ctx.Doer.MustChangePassword {
  231. ctx.ServerError("MustUpdatePassword", errors.New("cannot update password. Please visit the settings page"))
  232. return
  233. }
  234. if form.Password != form.Retype {
  235. ctx.Data["Err_Password"] = true
  236. ctx.RenderWithErr(ctx.Tr("form.password_not_match"), tplMustChangePassword, &form)
  237. return
  238. }
  239. opts := &user_service.UpdateAuthOptions{
  240. Password: optional.Some(form.Password),
  241. MustChangePassword: optional.Some(false),
  242. }
  243. if err := user_service.UpdateAuth(ctx, ctx.Doer, opts); err != nil {
  244. switch {
  245. case errors.Is(err, password.ErrMinLength):
  246. ctx.Data["Err_Password"] = true
  247. ctx.RenderWithErr(ctx.Tr("auth.password_too_short", setting.MinPasswordLength), tplMustChangePassword, &form)
  248. case errors.Is(err, password.ErrComplexity):
  249. ctx.Data["Err_Password"] = true
  250. ctx.RenderWithErr(password.BuildComplexityError(ctx.Locale), tplMustChangePassword, &form)
  251. case errors.Is(err, password.ErrIsPwned):
  252. ctx.Data["Err_Password"] = true
  253. ctx.RenderWithErr(ctx.Tr("auth.password_pwned", "https://haveibeenpwned.com/Passwords"), tplMustChangePassword, &form)
  254. case password.IsErrIsPwnedRequest(err):
  255. ctx.Data["Err_Password"] = true
  256. ctx.RenderWithErr(ctx.Tr("auth.password_pwned_err"), tplMustChangePassword, &form)
  257. default:
  258. ctx.ServerError("UpdateAuth", err)
  259. }
  260. return
  261. }
  262. ctx.Flash.Success(ctx.Tr("settings.change_password_success"))
  263. log.Trace("User updated password: %s", ctx.Doer.Name)
  264. if redirectTo := ctx.GetSiteCookie("redirect_to"); redirectTo != "" {
  265. middleware.DeleteRedirectToCookie(ctx.Resp)
  266. ctx.RedirectToCurrentSite(redirectTo)
  267. return
  268. }
  269. ctx.Redirect(setting.AppSubURL + "/")
  270. }