user_handler.go 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904
  1. package api
  2. import (
  3. "encoding/json"
  4. "errors"
  5. "fmt"
  6. "math/rand"
  7. "net/http"
  8. "net/url"
  9. "strconv"
  10. "strings"
  11. "time"
  12. "golang.org/x/crypto/bcrypt"
  13. "gorm.io/gorm"
  14. "github.com/go-chi/chi"
  15. "github.com/porter-dev/porter/internal/auth/token"
  16. "github.com/porter-dev/porter/internal/forms"
  17. "github.com/porter-dev/porter/internal/integrations/email"
  18. "github.com/porter-dev/porter/internal/models"
  19. "github.com/porter-dev/porter/internal/repository"
  20. segment "gopkg.in/segmentio/analytics-go.v3"
  21. )
  22. // Enumeration of user API error codes, represented as int64
  23. const (
  24. ErrUserDecode ErrorCode = iota + 600
  25. ErrUserValidateFields
  26. ErrUserDataRead
  27. )
  28. // HandleCreateUser validates a user form entry, converts the user to a gorm
  29. // model, and saves the user to the database
  30. func (app *App) HandleCreateUser(w http.ResponseWriter, r *http.Request) {
  31. session, err := app.Store.Get(r, app.ServerConf.CookieName)
  32. if err != nil {
  33. app.handleErrorDataRead(err, w)
  34. }
  35. form := &forms.CreateUserForm{}
  36. user, err := app.writeUser(
  37. form,
  38. app.Repo.User.CreateUser,
  39. w,
  40. r,
  41. doesUserExist,
  42. )
  43. if err == nil {
  44. // send to segment
  45. client := *app.segmentClient
  46. client.Enqueue(segment.Identify{
  47. UserId: fmt.Sprintf("%v", user.ID),
  48. Traits: segment.NewTraits().
  49. SetEmail(user.Email).
  50. Set("github", "false"),
  51. })
  52. client.Enqueue(segment.Track{
  53. UserId: fmt.Sprintf("%v", user.ID),
  54. Event: "New User",
  55. Properties: segment.NewProperties().
  56. Set("email", user.Email),
  57. })
  58. app.Logger.Info().Msgf("New user created: %d", user.ID)
  59. var redirect string
  60. if valR := session.Values["redirect"]; valR != nil {
  61. redirect = session.Values["redirect"].(string)
  62. }
  63. session.Values["authenticated"] = true
  64. session.Values["user_id"] = user.ID
  65. session.Values["email"] = user.Email
  66. session.Values["redirect"] = ""
  67. session.Save(r, w)
  68. w.WriteHeader(http.StatusCreated)
  69. if err := app.sendUser(w, user.ID, user.Email, false, redirect); err != nil {
  70. app.handleErrorFormDecoding(err, ErrUserDecode, w)
  71. return
  72. }
  73. }
  74. }
  75. // HandleAuthCheck checks whether current session is authenticated and returns user ID if so.
  76. func (app *App) HandleAuthCheck(w http.ResponseWriter, r *http.Request) {
  77. // first, check for token
  78. tok := app.getTokenFromRequest(r)
  79. if tok != nil {
  80. // read the user
  81. user, err := app.Repo.User.ReadUser(tok.IBy)
  82. if err != nil {
  83. http.Error(w, err.Error(), http.StatusInternalServerError)
  84. return
  85. }
  86. if err := app.sendUser(w, tok.IBy, user.Email, user.EmailVerified, ""); err != nil {
  87. app.handleErrorFormDecoding(err, ErrUserDecode, w)
  88. return
  89. }
  90. return
  91. }
  92. session, err := app.Store.Get(r, app.ServerConf.CookieName)
  93. if err != nil {
  94. http.Error(w, err.Error(), http.StatusInternalServerError)
  95. return
  96. }
  97. userID, _ := session.Values["user_id"].(uint)
  98. email, _ := session.Values["email"].(string)
  99. user, err := app.Repo.User.ReadUser(userID)
  100. if err != nil {
  101. http.Error(w, err.Error(), http.StatusInternalServerError)
  102. return
  103. }
  104. w.WriteHeader(http.StatusOK)
  105. if err := app.sendUser(w, userID, email, user.EmailVerified, ""); err != nil {
  106. app.handleErrorFormDecoding(err, ErrUserDecode, w)
  107. return
  108. }
  109. }
  110. // HandleCLILoginUser verifies that a user is logged in, and generates an access
  111. // token for usage from the CLI
  112. func (app *App) HandleCLILoginUser(w http.ResponseWriter, r *http.Request) {
  113. queryParams, _ := url.ParseQuery(r.URL.RawQuery)
  114. redirect := queryParams["redirect"][0]
  115. session, err := app.Store.Get(r, app.ServerConf.CookieName)
  116. if err != nil {
  117. http.Error(w, err.Error(), http.StatusInternalServerError)
  118. return
  119. }
  120. userID, _ := session.Values["user_id"].(uint)
  121. // generate the token
  122. jwt, err := token.GetTokenForUser(userID)
  123. if err != nil {
  124. app.handleErrorInternal(err, w)
  125. return
  126. }
  127. encoded, err := jwt.EncodeToken(&token.TokenGeneratorConf{
  128. TokenSecret: app.ServerConf.TokenGeneratorSecret,
  129. })
  130. if err != nil {
  131. app.handleErrorInternal(err, w)
  132. return
  133. }
  134. // generate 64 characters long authorization code
  135. const letters = "abcdefghijklmnopqrstuvwxyz123456789"
  136. code := make([]byte, 64)
  137. for i := range code {
  138. code[i] = letters[rand.Intn(len(letters))]
  139. }
  140. expiry := time.Now().Add(30 * time.Second)
  141. // create auth code object and send back authorization code
  142. authCode := &models.AuthCode{
  143. Token: encoded,
  144. AuthorizationCode: string(code),
  145. Expiry: &expiry,
  146. }
  147. authCode, err = app.Repo.AuthCode.CreateAuthCode(authCode)
  148. if err != nil {
  149. app.handleErrorInternal(err, w)
  150. return
  151. }
  152. http.Redirect(w, r, fmt.Sprintf("%s/?code=%s", redirect, url.QueryEscape(authCode.AuthorizationCode)), 302)
  153. }
  154. type ExchangeRequest struct {
  155. AuthorizationCode string `json:"authorization_code"`
  156. }
  157. type ExchangeResponse struct {
  158. Token string `json:"token"`
  159. }
  160. // HandleCLILoginExchangeToken exchanges an authorization code for a token
  161. func (app *App) HandleCLILoginExchangeToken(w http.ResponseWriter, r *http.Request) {
  162. // read the request body and look up the authorization token
  163. req := &ExchangeRequest{}
  164. if err := json.NewDecoder(r.Body).Decode(req); err != nil {
  165. app.handleErrorFormDecoding(err, ErrUserDecode, w)
  166. return
  167. }
  168. authCode, err := app.Repo.AuthCode.ReadAuthCode(req.AuthorizationCode)
  169. if err != nil || authCode.IsExpired() {
  170. http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
  171. return
  172. }
  173. res := &ExchangeResponse{
  174. Token: authCode.Token,
  175. }
  176. w.WriteHeader(http.StatusOK)
  177. if err := json.NewEncoder(w).Encode(res); err != nil {
  178. app.handleErrorFormDecoding(err, ErrUserDecode, w)
  179. return
  180. }
  181. }
  182. // HandleLoginUser checks the request header for cookie and validates the user.
  183. func (app *App) HandleLoginUser(w http.ResponseWriter, r *http.Request) {
  184. session, err := app.Store.Get(r, app.ServerConf.CookieName)
  185. if err != nil {
  186. app.handleErrorDataRead(err, w)
  187. return
  188. }
  189. form := &forms.LoginUserForm{}
  190. // decode from JSON to form value
  191. if err := json.NewDecoder(r.Body).Decode(form); err != nil {
  192. app.handleErrorFormDecoding(err, ErrUserDecode, w)
  193. return
  194. }
  195. storedUser, readErr := app.Repo.User.ReadUserByEmail(form.Email)
  196. if readErr != nil {
  197. app.sendExternalError(readErr, http.StatusUnauthorized, HTTPError{
  198. Errors: []string{"email not registered"},
  199. Code: http.StatusUnauthorized,
  200. }, w)
  201. return
  202. }
  203. if err := bcrypt.CompareHashAndPassword([]byte(storedUser.Password), []byte(form.Password)); err != nil {
  204. app.sendExternalError(readErr, http.StatusUnauthorized, HTTPError{
  205. Errors: []string{"incorrect password"},
  206. Code: http.StatusUnauthorized,
  207. }, w)
  208. return
  209. }
  210. var redirect string
  211. if valR := session.Values["redirect"]; valR != nil {
  212. redirect = session.Values["redirect"].(string)
  213. }
  214. // Set user as authenticated
  215. session.Values["authenticated"] = true
  216. session.Values["user_id"] = storedUser.ID
  217. session.Values["email"] = storedUser.Email
  218. session.Values["redirect"] = ""
  219. if err := session.Save(r, w); err != nil {
  220. app.Logger.Warn().Err(err)
  221. }
  222. w.WriteHeader(http.StatusOK)
  223. if err := app.sendUser(w, storedUser.ID, storedUser.Email, storedUser.EmailVerified, redirect); err != nil {
  224. app.handleErrorFormDecoding(err, ErrUserDecode, w)
  225. return
  226. }
  227. }
  228. // HandleLogoutUser detaches the user from the session
  229. func (app *App) HandleLogoutUser(w http.ResponseWriter, r *http.Request) {
  230. session, err := app.Store.Get(r, app.ServerConf.CookieName)
  231. if err != nil {
  232. app.handleErrorDataRead(err, w)
  233. }
  234. session.Values["authenticated"] = false
  235. session.Values["user_id"] = nil
  236. session.Values["email"] = nil
  237. session.Save(r, w)
  238. w.WriteHeader(http.StatusOK)
  239. }
  240. // HandleReadUser returns an externalized User (models.UserExternal)
  241. // based on an ID
  242. func (app *App) HandleReadUser(w http.ResponseWriter, r *http.Request) {
  243. user, err := app.readUser(w, r)
  244. // error already handled by helper
  245. if err != nil {
  246. return
  247. }
  248. extUser := user.Externalize()
  249. if err := json.NewEncoder(w).Encode(extUser); err != nil {
  250. app.handleErrorFormDecoding(err, ErrUserDecode, w)
  251. return
  252. }
  253. w.WriteHeader(http.StatusOK)
  254. }
  255. // HandleListUserProjects lists all projects belonging to a given user
  256. func (app *App) HandleListUserProjects(w http.ResponseWriter, r *http.Request) {
  257. id, err := strconv.ParseUint(chi.URLParam(r, "user_id"), 0, 64)
  258. if err != nil || id == 0 {
  259. app.handleErrorFormDecoding(err, ErrUserDecode, w)
  260. return
  261. }
  262. projects, err := app.Repo.Project.ListProjectsByUserID(uint(id))
  263. if err != nil {
  264. app.handleErrorRead(err, ErrUserDataRead, w)
  265. }
  266. projectsExt := make([]*models.ProjectExternal, 0)
  267. for _, project := range projects {
  268. projectsExt = append(projectsExt, project.Externalize())
  269. }
  270. w.WriteHeader(http.StatusOK)
  271. if err := json.NewEncoder(w).Encode(projectsExt); err != nil {
  272. app.handleErrorFormDecoding(err, ErrUserDecode, w)
  273. return
  274. }
  275. }
  276. // HandleDeleteUser removes a user after checking that the sent password is correct
  277. func (app *App) HandleDeleteUser(w http.ResponseWriter, r *http.Request) {
  278. id, err := strconv.ParseUint(chi.URLParam(r, "user_id"), 0, 64)
  279. if err != nil || id == 0 {
  280. app.handleErrorFormDecoding(err, ErrUserDecode, w)
  281. return
  282. }
  283. // TODO -- HASH AND VERIFY PASSWORD BEFORE USER DELETION
  284. form := &forms.DeleteUserForm{
  285. ID: uint(id),
  286. }
  287. user, err := app.writeUser(form, app.Repo.User.DeleteUser, w, r)
  288. if err == nil {
  289. app.Logger.Info().Msgf("User deleted: %d", user.ID)
  290. w.WriteHeader(http.StatusNoContent)
  291. }
  292. }
  293. // InitiateEmailVerifyUser initiates the email verification flow for a logged-in user
  294. func (app *App) InitiateEmailVerifyUser(w http.ResponseWriter, r *http.Request) {
  295. userID, err := app.getUserIDFromRequest(r)
  296. if err != nil {
  297. app.handleErrorInternal(err, w)
  298. return
  299. }
  300. user, err := app.Repo.User.ReadUser(userID)
  301. if err != nil {
  302. app.handleErrorInternal(err, w)
  303. return
  304. }
  305. // error already handled by helper
  306. if err != nil {
  307. return
  308. }
  309. form := &forms.InitiateResetUserPasswordForm{
  310. Email: user.Email,
  311. }
  312. // convert the form to a pw reset token model
  313. pwReset, rawToken, err := form.ToPWResetToken()
  314. if err != nil {
  315. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  316. return
  317. }
  318. // handle write to the database
  319. pwReset, err = app.Repo.PWResetToken.CreatePWResetToken(pwReset)
  320. if err != nil {
  321. app.handleErrorDataWrite(err, w)
  322. return
  323. }
  324. queryVals := url.Values{
  325. "token": []string{rawToken},
  326. "token_id": []string{fmt.Sprintf("%d", pwReset.ID)},
  327. }
  328. sgClient := email.SendgridClient{
  329. APIKey: app.ServerConf.SendgridAPIKey,
  330. VerifyEmailTemplateID: app.ServerConf.SendgridVerifyEmailTemplateID,
  331. SenderEmail: app.ServerConf.SendgridSenderEmail,
  332. }
  333. err = sgClient.SendEmailVerification(
  334. fmt.Sprintf("%s/api/email/verify/finalize?%s", app.ServerConf.ServerURL, queryVals.Encode()),
  335. form.Email,
  336. )
  337. if err != nil {
  338. app.handleErrorInternal(err, w)
  339. return
  340. }
  341. w.WriteHeader(http.StatusOK)
  342. return
  343. }
  344. // FinalizEmailVerifyUser completes the email verification flow for a user.
  345. func (app *App) FinalizEmailVerifyUser(w http.ResponseWriter, r *http.Request) {
  346. userID, err := app.getUserIDFromRequest(r)
  347. if err != nil {
  348. app.handleErrorInternal(err, w)
  349. return
  350. }
  351. user, err := app.Repo.User.ReadUser(userID)
  352. if err != nil {
  353. app.handleErrorInternal(err, w)
  354. return
  355. }
  356. vals, err := url.ParseQuery(r.URL.RawQuery)
  357. if err != nil {
  358. http.Redirect(w, r, "/dashboard?error="+url.QueryEscape("Invalid email verification URL"), 302)
  359. return
  360. }
  361. var tokenStr string
  362. var tokenID uint
  363. if tokenArr, ok := vals["token"]; ok && len(tokenArr) == 1 {
  364. tokenStr = tokenArr[0]
  365. } else {
  366. http.Redirect(w, r, "/dashboard?error="+url.QueryEscape("Invalid email verification URL: token required"), 302)
  367. return
  368. }
  369. if tokenIDArr, ok := vals["token_id"]; ok && len(tokenIDArr) == 1 {
  370. id, err := strconv.ParseUint(tokenIDArr[0], 10, 64)
  371. if err != nil {
  372. http.Redirect(w, r, "/dashboard?error="+url.QueryEscape("Invalid email verification URL: valid token id required"), 302)
  373. return
  374. }
  375. tokenID = uint(id)
  376. } else {
  377. http.Redirect(w, r, "/dashboard?error="+url.QueryEscape("Invalid email verification URL: valid token id required"), 302)
  378. return
  379. }
  380. // verify the token is valid
  381. token, err := app.Repo.PWResetToken.ReadPWResetToken(tokenID)
  382. if err != nil {
  383. http.Redirect(w, r, "/dashboard?error="+url.QueryEscape("Email verification error: valid token required"), 302)
  384. return
  385. }
  386. // make sure the token is still valid and has not expired
  387. if !token.IsValid || token.IsExpired() {
  388. http.Redirect(w, r, "/dashboard?error="+url.QueryEscape("Email verification error: valid token required"), 302)
  389. return
  390. }
  391. // make sure the token is correct
  392. if err := bcrypt.CompareHashAndPassword([]byte(token.Token), []byte(tokenStr)); err != nil {
  393. http.Redirect(w, r, "/dashboard?error="+url.QueryEscape("Email verification error: valid token required"), 302)
  394. return
  395. }
  396. user.EmailVerified = true
  397. user, err = app.Repo.User.UpdateUser(user)
  398. if err != nil {
  399. http.Redirect(w, r, "/dashboard?error="+url.QueryEscape("Could not verify email address"), 302)
  400. return
  401. }
  402. // invalidate the token
  403. token.IsValid = false
  404. _, err = app.Repo.PWResetToken.UpdatePWResetToken(token)
  405. if err != nil {
  406. http.Redirect(w, r, "/dashboard?error="+url.QueryEscape("Could not verify email address"), 302)
  407. return
  408. }
  409. http.Redirect(w, r, "/dashboard", 302)
  410. return
  411. }
  412. // InitiatePWResetUser initiates the password reset flow based on an email. The endpoint
  413. // checks if the email exists, but returns a 200 status code regardless, since we don't
  414. // want to leak in-use emails
  415. func (app *App) InitiatePWResetUser(w http.ResponseWriter, r *http.Request) {
  416. form := &forms.InitiateResetUserPasswordForm{}
  417. // decode from JSON to form value
  418. if err := json.NewDecoder(r.Body).Decode(form); err != nil {
  419. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  420. return
  421. }
  422. // validate the form
  423. if err := app.validator.Struct(form); err != nil {
  424. app.handleErrorFormValidation(err, ErrProjectValidateFields, w)
  425. return
  426. }
  427. // check that the email exists; return 200 status code even if it doesn't
  428. user, err := app.Repo.User.ReadUserByEmail(form.Email)
  429. if err == gorm.ErrRecordNotFound {
  430. w.WriteHeader(http.StatusOK)
  431. return
  432. } else if err != nil {
  433. app.handleErrorDataRead(err, w)
  434. return
  435. }
  436. // if the user is a Github user, send them a Github email
  437. if user.GithubUserID != 0 {
  438. sgClient := email.SendgridClient{
  439. APIKey: app.ServerConf.SendgridAPIKey,
  440. PWGHTemplateID: app.ServerConf.SendgridPWGHTemplateID,
  441. SenderEmail: app.ServerConf.SendgridSenderEmail,
  442. }
  443. err = sgClient.SendGHPWEmail(
  444. fmt.Sprintf("%s/api/oauth/login/github", app.ServerConf.ServerURL),
  445. form.Email,
  446. )
  447. if err != nil {
  448. app.handleErrorInternal(err, w)
  449. return
  450. }
  451. w.WriteHeader(http.StatusOK)
  452. return
  453. }
  454. // convert the form to a project model
  455. pwReset, rawToken, err := form.ToPWResetToken()
  456. if err != nil {
  457. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  458. return
  459. }
  460. // handle write to the database
  461. pwReset, err = app.Repo.PWResetToken.CreatePWResetToken(pwReset)
  462. if err != nil {
  463. app.handleErrorDataWrite(err, w)
  464. return
  465. }
  466. queryVals := url.Values{
  467. "token": []string{rawToken},
  468. "email": []string{form.Email},
  469. "token_id": []string{fmt.Sprintf("%d", pwReset.ID)},
  470. }
  471. sgClient := email.SendgridClient{
  472. APIKey: app.ServerConf.SendgridAPIKey,
  473. PWResetTemplateID: app.ServerConf.SendgridPWResetTemplateID,
  474. SenderEmail: app.ServerConf.SendgridSenderEmail,
  475. }
  476. err = sgClient.SendPWResetEmail(
  477. fmt.Sprintf("%s/password/reset/finalize?%s", app.ServerConf.ServerURL, queryVals.Encode()),
  478. form.Email,
  479. )
  480. if err != nil {
  481. app.handleErrorInternal(err, w)
  482. return
  483. }
  484. w.WriteHeader(http.StatusOK)
  485. return
  486. }
  487. // VerifyPWResetUser makes sure that the token is correct and still valid
  488. func (app *App) VerifyPWResetUser(w http.ResponseWriter, r *http.Request) {
  489. form := &forms.VerifyResetUserPasswordForm{}
  490. // decode from JSON to form value
  491. if err := json.NewDecoder(r.Body).Decode(form); err != nil {
  492. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  493. return
  494. }
  495. // validate the form
  496. if err := app.validator.Struct(form); err != nil {
  497. app.handleErrorFormValidation(err, ErrProjectValidateFields, w)
  498. return
  499. }
  500. token, err := app.Repo.PWResetToken.ReadPWResetToken(form.PWResetTokenID)
  501. if err != nil {
  502. w.WriteHeader(http.StatusForbidden)
  503. return
  504. }
  505. // make sure the token is still valid and has not expired
  506. if !token.IsValid || token.IsExpired() {
  507. w.WriteHeader(http.StatusForbidden)
  508. return
  509. }
  510. // check that the email matches
  511. if token.Email != form.Email {
  512. w.WriteHeader(http.StatusForbidden)
  513. return
  514. }
  515. // make sure the token is correct
  516. if err := bcrypt.CompareHashAndPassword([]byte(token.Token), []byte(form.Token)); err != nil {
  517. w.WriteHeader(http.StatusForbidden)
  518. return
  519. }
  520. w.WriteHeader(http.StatusOK)
  521. return
  522. }
  523. // FinalizPWResetUser completes the password reset flow based on an email.
  524. func (app *App) FinalizPWResetUser(w http.ResponseWriter, r *http.Request) {
  525. form := &forms.FinalizeResetUserPasswordForm{}
  526. // decode from JSON to form value
  527. if err := json.NewDecoder(r.Body).Decode(form); err != nil {
  528. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  529. return
  530. }
  531. // validate the form
  532. if err := app.validator.Struct(form); err != nil {
  533. app.handleErrorFormValidation(err, ErrProjectValidateFields, w)
  534. return
  535. }
  536. // verify the token is valid
  537. token, err := app.Repo.PWResetToken.ReadPWResetToken(form.PWResetTokenID)
  538. if err != nil {
  539. w.WriteHeader(http.StatusForbidden)
  540. return
  541. }
  542. // make sure the token is still valid and has not expired
  543. if !token.IsValid || token.IsExpired() {
  544. w.WriteHeader(http.StatusForbidden)
  545. return
  546. }
  547. // check that the email matches
  548. if token.Email != form.Email {
  549. w.WriteHeader(http.StatusForbidden)
  550. return
  551. }
  552. // make sure the token is correct
  553. if err := bcrypt.CompareHashAndPassword([]byte(token.Token), []byte(form.Token)); err != nil {
  554. w.WriteHeader(http.StatusForbidden)
  555. return
  556. }
  557. // check that the email exists
  558. user, err := app.Repo.User.ReadUserByEmail(form.Email)
  559. if err != nil {
  560. w.WriteHeader(http.StatusForbidden)
  561. return
  562. }
  563. hashedPW, err := bcrypt.GenerateFromPassword([]byte(form.NewPassword), 8)
  564. if err != nil {
  565. app.handleErrorDataWrite(err, w)
  566. return
  567. }
  568. user.Password = string(hashedPW)
  569. user, err = app.Repo.User.UpdateUser(user)
  570. if err != nil {
  571. app.handleErrorDataWrite(err, w)
  572. return
  573. }
  574. // invalidate the token
  575. token.IsValid = false
  576. _, err = app.Repo.PWResetToken.UpdatePWResetToken(token)
  577. if err != nil {
  578. app.handleErrorDataWrite(err, w)
  579. return
  580. }
  581. w.WriteHeader(http.StatusOK)
  582. return
  583. }
  584. // ------------------------ User handler helper functions ------------------------ //
  585. // writeUser will take a POST or PUT request to the /api/users endpoint and decode
  586. // the request into a forms.WriteUserForm model, convert it to a models.User, and
  587. // write to the database.
  588. func (app *App) writeUser(
  589. form forms.WriteUserForm,
  590. dbWrite repository.WriteUser,
  591. w http.ResponseWriter,
  592. r *http.Request,
  593. validators ...func(repo *repository.Repository, user *models.User) *HTTPError,
  594. ) (*models.User, error) {
  595. // decode from JSON to form value
  596. if err := json.NewDecoder(r.Body).Decode(form); err != nil {
  597. app.handleErrorFormDecoding(err, ErrUserDecode, w)
  598. return nil, err
  599. }
  600. // validate the form
  601. if err := app.validator.Struct(form); err != nil {
  602. app.handleErrorFormValidation(err, ErrUserValidateFields, w)
  603. return nil, err
  604. }
  605. // convert the form to a user model -- WriteUserForm must implement ToUser
  606. userModel, err := form.ToUser(app.Repo.User)
  607. if err != nil {
  608. app.handleErrorFormDecoding(err, ErrUserDecode, w)
  609. return nil, err
  610. }
  611. // Check any additional validators for any semantic errors
  612. // We have completed all syntax checks, so these will be sent
  613. // with http.StatusUnprocessableEntity (422), unless this is
  614. // an internal server error
  615. for _, validator := range validators {
  616. err := validator(app.Repo, userModel)
  617. if err != nil {
  618. goErr := errors.New(strings.Join(err.Errors, ", "))
  619. if err.Code == 500 {
  620. app.sendExternalError(
  621. goErr,
  622. http.StatusInternalServerError,
  623. *err,
  624. w,
  625. )
  626. } else {
  627. app.sendExternalError(
  628. goErr,
  629. http.StatusUnprocessableEntity,
  630. *err,
  631. w,
  632. )
  633. }
  634. return nil, goErr
  635. }
  636. }
  637. // handle write to the database
  638. user, err := dbWrite(userModel)
  639. if err != nil {
  640. app.handleErrorDataWrite(err, w)
  641. return nil, err
  642. }
  643. return user, nil
  644. }
  645. func (app *App) readUser(w http.ResponseWriter, r *http.Request) (*models.User, error) {
  646. id, err := strconv.ParseUint(chi.URLParam(r, "user_id"), 0, 64)
  647. if err != nil || id == 0 {
  648. app.handleErrorFormDecoding(err, ErrUserDecode, w)
  649. return nil, err
  650. }
  651. user, err := app.Repo.User.ReadUser(uint(id))
  652. if err != nil {
  653. app.handleErrorRead(err, ErrUserDataRead, w)
  654. return nil, err
  655. }
  656. return user, nil
  657. }
  658. func doesUserExist(repo *repository.Repository, user *models.User) *HTTPError {
  659. user, err := repo.User.ReadUserByEmail(user.Email)
  660. if user != nil && err == nil {
  661. return &HTTPError{
  662. Code: ErrUserValidateFields,
  663. Errors: []string{
  664. "email already taken",
  665. },
  666. }
  667. }
  668. if err != gorm.ErrRecordNotFound {
  669. return &ErrorDataRead
  670. }
  671. return nil
  672. }
  673. type SendUserExt struct {
  674. ID uint `json:"id"`
  675. Email string `json:"email"`
  676. EmailVerified bool `json:"email_verified"`
  677. Redirect string `json:"redirect,omitempty"`
  678. }
  679. func (app *App) sendUser(w http.ResponseWriter, userID uint, email string, emailVerified bool, redirect string) error {
  680. resUser := &SendUserExt{
  681. ID: userID,
  682. Email: email,
  683. EmailVerified: emailVerified,
  684. Redirect: redirect,
  685. }
  686. if err := json.NewEncoder(w).Encode(resUser); err != nil {
  687. return err
  688. }
  689. return nil
  690. }
  691. func (app *App) getUserIDFromRequest(r *http.Request) (uint, error) {
  692. session, err := app.Store.Get(r, app.ServerConf.CookieName)
  693. if err != nil {
  694. return 0, err
  695. }
  696. // first, check for token
  697. tok := app.getTokenFromRequest(r)
  698. if tok != nil {
  699. return tok.IBy, nil
  700. }
  701. userID, _ := session.Values["user_id"].(uint)
  702. return userID, nil
  703. }