2
0

pw_reset.go 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307
  1. package user
  2. import (
  3. "fmt"
  4. "net/http"
  5. "net/url"
  6. "time"
  7. "github.com/porter-dev/porter/api/server/handlers"
  8. "github.com/porter-dev/porter/api/server/shared"
  9. "github.com/porter-dev/porter/api/server/shared/apierrors"
  10. "github.com/porter-dev/porter/api/server/shared/config"
  11. "github.com/porter-dev/porter/api/types"
  12. "github.com/porter-dev/porter/internal/models"
  13. "github.com/porter-dev/porter/internal/notifier"
  14. "github.com/porter-dev/porter/internal/random"
  15. "github.com/porter-dev/porter/internal/repository"
  16. "golang.org/x/crypto/bcrypt"
  17. "gorm.io/gorm"
  18. )
  19. type UserPasswordInitiateResetHandler struct {
  20. handlers.PorterHandlerReader
  21. }
  22. func NewUserPasswordInitiateResetHandler(
  23. config *config.Config,
  24. decoderValidator shared.RequestDecoderValidator,
  25. writer shared.ResultWriter,
  26. ) *UserPasswordInitiateResetHandler {
  27. return &UserPasswordInitiateResetHandler{
  28. PorterHandlerReader: handlers.NewDefaultPorterHandler(config, decoderValidator, writer),
  29. }
  30. }
  31. func (c *UserPasswordInitiateResetHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  32. request := &types.InitiateResetUserPasswordRequest{}
  33. ok := c.DecodeAndValidate(w, r, request)
  34. if !ok {
  35. return
  36. }
  37. // check that the email exists; return 200 status code even if it doesn't
  38. user, err := c.Repo().User().ReadUserByEmail(request.Email)
  39. if err == gorm.ErrRecordNotFound {
  40. w.WriteHeader(http.StatusOK)
  41. return
  42. } else if err != nil {
  43. c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  44. return
  45. }
  46. if err := checkUserRestrictions(c.Config().ServerConf, user.Email); err != nil {
  47. c.HandleAPIError(w, r, apierrors.NewErrForbidden(err))
  48. return
  49. }
  50. // if the user is a Github user, send them a Github email
  51. if user.GithubUserID != 0 {
  52. err := c.Config().UserNotifier.SendGithubRelinkEmail(
  53. &notifier.SendGithubRelinkEmailOpts{
  54. Email: user.Email,
  55. URL: fmt.Sprintf("%s/api/oauth/login/github", c.Config().ServerConf.ServerURL),
  56. },
  57. )
  58. if err != nil {
  59. c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  60. return
  61. }
  62. w.WriteHeader(http.StatusOK)
  63. return
  64. }
  65. pwReset, rawToken, err := CreatePWResetTokenForEmail(
  66. c.Repo().PWResetToken(),
  67. c.HandleAPIError,
  68. w,
  69. r,
  70. request,
  71. )
  72. if err != nil {
  73. return
  74. }
  75. queryVals := url.Values{
  76. "token": []string{rawToken},
  77. "email": []string{request.Email},
  78. "token_id": []string{fmt.Sprintf("%d", pwReset.ID)},
  79. }
  80. err = c.Config().UserNotifier.SendPasswordResetEmail(
  81. &notifier.SendPasswordResetEmailOpts{
  82. Email: user.Email,
  83. URL: fmt.Sprintf("%s/password/reset/finalize?%s", c.Config().ServerConf.ServerURL, queryVals.Encode()),
  84. },
  85. )
  86. if err != nil {
  87. c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  88. return
  89. }
  90. w.WriteHeader(http.StatusOK)
  91. return
  92. }
  93. type UserPasswordVerifyResetHandler struct {
  94. handlers.PorterHandlerReader
  95. }
  96. func NewUserPasswordVerifyResetHandler(
  97. config *config.Config,
  98. decoderValidator shared.RequestDecoderValidator,
  99. writer shared.ResultWriter,
  100. ) *UserPasswordVerifyResetHandler {
  101. return &UserPasswordVerifyResetHandler{
  102. PorterHandlerReader: handlers.NewDefaultPorterHandler(config, decoderValidator, writer),
  103. }
  104. }
  105. func (c *UserPasswordVerifyResetHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  106. request := &types.VerifyResetUserPasswordRequest{}
  107. ok := c.DecodeAndValidate(w, r, request)
  108. if !ok {
  109. return
  110. }
  111. VerifyToken(
  112. c.Repo().PWResetToken(),
  113. c.HandleAPIError,
  114. w,
  115. r,
  116. &request.VerifyTokenFinalizeRequest,
  117. request.Email,
  118. )
  119. }
  120. type UserPasswordFinalizeResetHandler struct {
  121. handlers.PorterHandlerReader
  122. }
  123. func NewUserPasswordFinalizeResetHandler(
  124. config *config.Config,
  125. decoderValidator shared.RequestDecoderValidator,
  126. writer shared.ResultWriter,
  127. ) *UserPasswordFinalizeResetHandler {
  128. return &UserPasswordFinalizeResetHandler{
  129. PorterHandlerReader: handlers.NewDefaultPorterHandler(config, decoderValidator, writer),
  130. }
  131. }
  132. func (c *UserPasswordFinalizeResetHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  133. request := &types.FinalizeResetUserPasswordRequest{}
  134. ok := c.DecodeAndValidate(w, r, request)
  135. if !ok {
  136. return
  137. }
  138. token, err := VerifyToken(
  139. c.Repo().PWResetToken(),
  140. c.HandleAPIError,
  141. w,
  142. r,
  143. &request.VerifyTokenFinalizeRequest,
  144. request.Email,
  145. )
  146. if err != nil {
  147. return
  148. }
  149. // check that the email exists
  150. user, err := c.Repo().User().ReadUserByEmail(request.Email)
  151. if err != nil {
  152. if err == gorm.ErrRecordNotFound {
  153. err = fmt.Errorf("finalize password reset failed: email does not exist")
  154. c.HandleAPIError(w, r, apierrors.NewErrForbidden(err))
  155. } else {
  156. c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  157. }
  158. return
  159. }
  160. hashedPW, err := bcrypt.GenerateFromPassword([]byte(request.NewPassword), 8)
  161. if err != nil {
  162. c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  163. return
  164. }
  165. user.Password = string(hashedPW)
  166. user, err = c.Repo().User().UpdateUser(user)
  167. if err != nil {
  168. c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  169. return
  170. }
  171. // invalidate the token
  172. token.IsValid = false
  173. _, err = c.Repo().PWResetToken().UpdatePWResetToken(token)
  174. if err != nil {
  175. c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  176. return
  177. }
  178. w.WriteHeader(http.StatusOK)
  179. return
  180. }
  181. func VerifyToken(
  182. pwResetRepo repository.PWResetTokenRepository,
  183. handleErr func(w http.ResponseWriter, r *http.Request, err apierrors.RequestError),
  184. w http.ResponseWriter,
  185. r *http.Request,
  186. request *types.VerifyTokenFinalizeRequest,
  187. email string,
  188. ) (*models.PWResetToken, error) {
  189. token, err := pwResetRepo.ReadPWResetToken(request.TokenID)
  190. if err != nil {
  191. if err == gorm.ErrRecordNotFound {
  192. err = fmt.Errorf("verify token failed: token does not exist")
  193. handleErr(w, r, apierrors.NewErrForbidden(err))
  194. return nil, err
  195. } else {
  196. handleErr(w, r, apierrors.NewErrInternal(err))
  197. }
  198. return nil, err
  199. }
  200. // make sure the token is still valid and has not expired
  201. if !token.IsValid || token.IsExpired() {
  202. err = fmt.Errorf("verify token failed: expired %t, valid %t", token.IsExpired(), token.IsValid)
  203. handleErr(w, r, apierrors.NewErrForbidden(err))
  204. return nil, err
  205. }
  206. // check that the email matches
  207. if token.Email != email {
  208. err = fmt.Errorf("verify token failed: token email does not match request email")
  209. handleErr(w, r, apierrors.NewErrForbidden(err))
  210. return nil, err
  211. }
  212. // make sure the token is correct
  213. if err := bcrypt.CompareHashAndPassword([]byte(token.Token), []byte(request.Token)); err != nil {
  214. err = fmt.Errorf("verify token failed: %s", err)
  215. handleErr(w, r, apierrors.NewErrForbidden(err))
  216. return nil, err
  217. }
  218. return token, nil
  219. }
  220. func CreatePWResetTokenForEmail(
  221. pwResetRepo repository.PWResetTokenRepository,
  222. handleErr func(w http.ResponseWriter, r *http.Request, err apierrors.RequestError),
  223. w http.ResponseWriter,
  224. r *http.Request,
  225. request *types.InitiateResetUserPasswordRequest,
  226. ) (*models.PWResetToken, string, error) {
  227. // convert the form to a project model
  228. expiry := time.Now().Add(30 * time.Minute)
  229. rawToken, err := random.StringWithCharset(32, "")
  230. if err != nil {
  231. handleErr(w, r, apierrors.NewErrInternal(err))
  232. return nil, "", err
  233. }
  234. hashedToken, err := bcrypt.GenerateFromPassword([]byte(rawToken), 8)
  235. if err != nil {
  236. handleErr(w, r, apierrors.NewErrInternal(err))
  237. return nil, "", err
  238. }
  239. pwReset := &models.PWResetToken{
  240. Email: request.Email,
  241. IsValid: true,
  242. Expiry: &expiry,
  243. Token: string(hashedToken),
  244. }
  245. // handle write to the database
  246. pwReset, err = pwResetRepo.CreatePWResetToken(pwReset)
  247. if err != nil {
  248. handleErr(w, r, apierrors.NewErrInternal(err))
  249. return nil, "", err
  250. }
  251. return pwReset, rawToken, nil
  252. }