invite_handler.go 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. package api
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "net/http"
  6. "net/url"
  7. "strconv"
  8. "github.com/go-chi/chi"
  9. "github.com/porter-dev/porter/internal/forms"
  10. "github.com/porter-dev/porter/internal/models"
  11. )
  12. // HandleCreateInvite creates a new invite for a project
  13. func (app *App) HandleCreateInvite(w http.ResponseWriter, r *http.Request) {
  14. projID, err := strconv.ParseUint(chi.URLParam(r, "project_id"), 0, 64)
  15. if err != nil || projID == 0 {
  16. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  17. return
  18. }
  19. form := &forms.CreateInvite{
  20. ProjectID: uint(projID),
  21. }
  22. // decode from JSON to form value
  23. if err := json.NewDecoder(r.Body).Decode(form); err != nil {
  24. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  25. return
  26. }
  27. // validate the form
  28. if err := app.validator.Struct(form); err != nil {
  29. app.handleErrorFormValidation(err, ErrProjectValidateFields, w)
  30. return
  31. }
  32. // convert the form to an invite
  33. invite, err := form.ToInvite()
  34. if err != nil {
  35. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  36. return
  37. }
  38. // handle write to the database
  39. invite, err = app.Repo.Invite.CreateInvite(invite)
  40. if err != nil {
  41. app.handleErrorDataWrite(err, w)
  42. return
  43. }
  44. app.Logger.Info().Msgf("New invite created: %d", invite.ID)
  45. w.WriteHeader(http.StatusCreated)
  46. inviteExt := invite.Externalize()
  47. if err := json.NewEncoder(w).Encode(inviteExt); err != nil {
  48. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  49. return
  50. }
  51. }
  52. // HandleAcceptInvite accepts an invite to a new project: if successful, a new role
  53. // is created for that user in the project
  54. func (app *App) HandleAcceptInvite(w http.ResponseWriter, r *http.Request) {
  55. session, err := app.Store.Get(r, app.ServerConf.CookieName)
  56. if err != nil {
  57. acceptInviteError(w, r)
  58. return
  59. }
  60. userID, _ := session.Values["user_id"].(uint)
  61. user, err := app.Repo.User.ReadUser(userID)
  62. if err != nil {
  63. acceptInviteError(w, r)
  64. return
  65. }
  66. token := chi.URLParam(r, "token")
  67. if token == "" {
  68. acceptInviteError(w, r)
  69. return
  70. }
  71. projID, err := strconv.ParseUint(chi.URLParam(r, "project_id"), 0, 64)
  72. if err != nil || projID == 0 {
  73. acceptInviteError(w, r)
  74. return
  75. }
  76. invite, err := app.Repo.Invite.ReadInviteByToken(token)
  77. if err != nil || invite.ProjectID != uint(projID) {
  78. vals := url.Values{}
  79. vals.Add("error", "Invalid invite token")
  80. http.Redirect(w, r, fmt.Sprintf("/dashboard?%s", vals.Encode()), 302)
  81. return
  82. }
  83. // check that the invite has not expired and has not been accepted
  84. if invite.IsExpired() || invite.IsAccepted() {
  85. vals := url.Values{}
  86. vals.Add("error", "Invite has expired")
  87. http.Redirect(w, r, fmt.Sprintf("/dashboard?%s", vals.Encode()), 302)
  88. return
  89. }
  90. // check that the invite email matches the user's email
  91. if user.Email != invite.Email {
  92. vals := url.Values{}
  93. vals.Add("error", "Wrong email for invite")
  94. http.Redirect(w, r, fmt.Sprintf("/dashboard?%s", vals.Encode()), 302)
  95. return
  96. }
  97. // create a new role for the user in the project
  98. projModel, err := app.Repo.Project.ReadProject(uint(projID))
  99. if err != nil {
  100. acceptInviteError(w, r)
  101. return
  102. }
  103. // create a new Role with the user as the admin
  104. _, err = app.Repo.Project.CreateProjectRole(projModel, &models.Role{
  105. UserID: userID,
  106. ProjectID: uint(projID),
  107. Kind: models.RoleAdmin,
  108. })
  109. if err != nil {
  110. acceptInviteError(w, r)
  111. return
  112. }
  113. // update the invite
  114. invite.UserID = userID
  115. _, err = app.Repo.Invite.UpdateInvite(invite)
  116. if err != nil {
  117. acceptInviteError(w, r)
  118. return
  119. }
  120. http.Redirect(w, r, "/dashboard", 302)
  121. return
  122. }
  123. func acceptInviteError(w http.ResponseWriter, r *http.Request) {
  124. vals := url.Values{}
  125. vals.Add("error", "could not accept invite")
  126. http.Redirect(w, r, fmt.Sprintf("/dashboard?%s", vals.Encode()), 302)
  127. return
  128. }
  129. // HandleListProjectInvites returns a list of invites for a project
  130. func (app *App) HandleListProjectInvites(w http.ResponseWriter, r *http.Request) {
  131. projID, err := strconv.ParseUint(chi.URLParam(r, "project_id"), 0, 64)
  132. if err != nil || projID == 0 {
  133. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  134. return
  135. }
  136. invites, err := app.Repo.Invite.ListInvitesByProjectID(uint(projID))
  137. if err != nil {
  138. app.handleErrorRead(err, ErrProjectDataRead, w)
  139. return
  140. }
  141. extInvites := make([]*models.InviteExternal, 0)
  142. for _, invite := range invites {
  143. extInvites = append(extInvites, invite.Externalize())
  144. }
  145. w.WriteHeader(http.StatusOK)
  146. if err := json.NewEncoder(w).Encode(extInvites); err != nil {
  147. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  148. return
  149. }
  150. }
  151. // HandleDeleteProjectInvite handles the deletion of an Invite via the invite ID
  152. func (app *App) HandleDeleteProjectInvite(w http.ResponseWriter, r *http.Request) {
  153. id, err := strconv.ParseUint(chi.URLParam(r, "invite_id"), 0, 64)
  154. if err != nil || id == 0 {
  155. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  156. return
  157. }
  158. invite, err := app.Repo.Invite.ReadInvite(uint(id))
  159. if err != nil {
  160. app.handleErrorRead(err, ErrProjectDataRead, w)
  161. return
  162. }
  163. err = app.Repo.Invite.DeleteInvite(invite)
  164. if err != nil {
  165. app.handleErrorRead(err, ErrProjectDataRead, w)
  166. return
  167. }
  168. w.WriteHeader(http.StatusOK)
  169. }