create.go 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. //go:build ee
  2. // +build ee
  3. package invite
  4. import (
  5. "errors"
  6. "fmt"
  7. "net/http"
  8. "strings"
  9. "time"
  10. "github.com/porter-dev/porter/api/server/handlers"
  11. "github.com/porter-dev/porter/api/server/shared"
  12. "github.com/porter-dev/porter/api/server/shared/apierrors"
  13. "github.com/porter-dev/porter/api/server/shared/config"
  14. "github.com/porter-dev/porter/api/types"
  15. "github.com/porter-dev/porter/internal/models"
  16. "github.com/porter-dev/porter/internal/notifier"
  17. "github.com/porter-dev/porter/internal/oauth"
  18. "gorm.io/gorm"
  19. )
  20. type InviteCreateHandler struct {
  21. handlers.PorterHandlerReadWriter
  22. }
  23. func NewInviteCreateHandler(
  24. config *config.Config,
  25. decoderValidator shared.RequestDecoderValidator,
  26. writer shared.ResultWriter,
  27. ) http.Handler {
  28. return &InviteCreateHandler{
  29. PorterHandlerReadWriter: handlers.NewDefaultPorterHandler(config, decoderValidator, writer),
  30. }
  31. }
  32. func (c *InviteCreateHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  33. user, _ := r.Context().Value(types.UserScope).(*models.User)
  34. project, _ := r.Context().Value(types.ProjectScope).(*models.Project)
  35. request := &types.CreateInviteRequest{}
  36. if ok := c.DecodeAndValidate(w, r, request); !ok {
  37. return
  38. }
  39. if len(request.RoleUIDs) == 0 {
  40. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(
  41. fmt.Errorf("roles cannot be empty"), http.StatusPreconditionFailed,
  42. ))
  43. return
  44. } else {
  45. // check for valid project roles
  46. for _, role := range request.RoleUIDs {
  47. _, err := c.Repo().ProjectRole().ReadProjectRole(project.ID, role)
  48. if err != nil && errors.Is(err, gorm.ErrRecordNotFound) {
  49. c.HandleAPIError(w, r, apierrors.NewErrNotFound(fmt.Errorf("role not found in project: %s", role)))
  50. return
  51. }
  52. }
  53. }
  54. // create invite model
  55. invite, err := CreateInviteWithProject(request, project.ID)
  56. if err != nil {
  57. c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  58. return
  59. }
  60. // write to database
  61. invite, err = c.Repo().Invite().CreateInvite(invite)
  62. if err != nil {
  63. c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  64. return
  65. }
  66. if err := c.Config().UserNotifier.SendProjectInviteEmail(
  67. &notifier.SendProjectInviteEmailOpts{
  68. InviteeEmail: request.Email,
  69. URL: fmt.Sprintf("%s/api/projects/%d/invites/%s", c.Config().ServerConf.ServerURL, project.ID, invite.Token),
  70. Project: project.Name,
  71. ProjectOwnerEmail: user.Email,
  72. },
  73. ); err != nil {
  74. c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  75. return
  76. }
  77. res := types.CreateInviteResponse{
  78. Invite: invite.ToInviteType(),
  79. }
  80. c.WriteResult(w, r, res)
  81. }
  82. func CreateInviteWithProject(invite *types.CreateInviteRequest, projectID uint) (*models.Invite, error) {
  83. // generate a token and an expiry time
  84. expiry := time.Now().Add(24 * time.Hour)
  85. return &models.Invite{
  86. Email: invite.Email,
  87. Kind: invite.Kind,
  88. Expiry: &expiry,
  89. ProjectID: projectID,
  90. Token: oauth.CreateRandomState(),
  91. Roles: []byte(strings.Join(invite.RoleUIDs, ",")),
  92. }, nil
  93. }