Anukul Sangwan 4 лет назад
Родитель
Сommit
3bdbda74ea

+ 122 - 0
api/server/handlers/invite/accept.go

@@ -0,0 +1,122 @@
+package invite
+
+import (
+	"fmt"
+	"net/http"
+	"net/url"
+	"strconv"
+
+	"github.com/go-chi/chi"
+
+	"github.com/porter-dev/porter/api/server/handlers"
+	"github.com/porter-dev/porter/api/server/shared"
+	"github.com/porter-dev/porter/api/server/shared/apierrors"
+	"github.com/porter-dev/porter/api/server/shared/config"
+	"github.com/porter-dev/porter/api/types"
+	"github.com/porter-dev/porter/internal/models"
+)
+
+type InviteAcceptHandler struct {
+	handlers.PorterHandlerReader
+}
+
+func NewInviteAcceptHandler(
+	config *config.Config,
+	decoderValidator shared.RequestDecoderValidator,
+) *InviteAcceptHandler {
+	return &InviteAcceptHandler{
+		PorterHandlerReader: handlers.NewDefaultPorterHandler(config, decoderValidator, nil),
+	}
+}
+
+func (c *InviteAcceptHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	request := &types.AcceptInviteRequest{}
+
+	if ok := c.DecodeAndValidate(w, r, request); !ok {
+		return
+	}
+
+	session, err := c.Config().Store.Get(r, c.Config().ServerConf.CookieName)
+
+	if err != nil {
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+	}
+
+	userID, _ := session.Values["user_id"].(uint)
+
+	user, err := c.Repo().User().ReadUser(userID)
+
+	if err != nil {
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+		return
+	}
+
+	projectID, err := strconv.ParseUint(chi.URLParam(r, "project_id"), 0, 64)
+
+	if err != nil || projectID == 0 {
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+		return
+	}
+
+	invite, err := c.Repo().Invite().ReadInviteByToken(request.Token)
+
+	if err != nil || invite.ProjectID != uint(projectID) {
+		vals := url.Values{}
+		vals.Add("error", "Invalid invite token")
+		http.Redirect(w, r, fmt.Sprintf("/dashboard?%s", vals.Encode()), 302)
+
+		return
+	}
+
+	// check that the invite has not expired and has not been accepted
+	if invite.IsExpired() || invite.IsAccepted() {
+		vals := url.Values{}
+		vals.Add("error", "Invite has expired")
+		http.Redirect(w, r, fmt.Sprintf("/dashboard?%s", vals.Encode()), 302)
+
+		return
+	}
+
+	// check that the invite email matches the user's email
+	if user.Email != invite.Email {
+		vals := url.Values{}
+		vals.Add("error", "Wrong email for invite")
+		http.Redirect(w, r, fmt.Sprintf("/dashboard?%s", vals.Encode()), 302)
+
+		return
+	}
+
+	kind := invite.Kind
+
+	if kind == "" {
+		kind = models.RoleDeveloper
+	}
+
+	project, err := c.Repo().Project().ReadProject(uint(projectID))
+
+	if err != nil {
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+		return
+	}
+
+	if _, err = c.Repo().Project().CreateProjectRole(project, &models.Role{
+		Role: types.Role{
+			UserID:    userID,
+			ProjectID: project.ID,
+			Kind:      types.RoleKind(kind),
+		},
+	}); err != nil {
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+		return
+	}
+
+	// update the invite
+	invite.UserID = userID
+
+	if _, err = c.Repo().Invite().UpdateInvite(invite); err != nil {
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+		return
+	}
+
+	http.Redirect(w, r, "/dashboard", 302)
+}

+ 1 - 1
api/server/handlers/invite/create.go

@@ -60,7 +60,7 @@ func (c *CreateInviteHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
 	if err := c.Config().UserNotifier.SendProjectInviteEmail(
 		&notifier.SendProjectInviteEmailOpts{
 			InviteeEmail:      request.Email,
-			URL:               fmt.Sprintf("%s/api/projects/%d/invites/%s", c.Config().ServerConf.ServerURL, project.ID, invite.Token),
+			URL:               fmt.Sprintf("%s/api/projects/%d/invites/accept?token=%s", c.Config().ServerConf.ServerURL, project.ID, invite.Token),
 			Project:           project.Name,
 			ProjectOwnerEmail: user.Email,
 		},

+ 24 - 0
api/server/router/project.go

@@ -467,5 +467,29 @@ func getProjectRoutes(
 		Router:   r,
 	})
 
+	// GET /api/projects/{project_id}/invites/accept -> invite.NewInviteAcceptHandler
+	acceptInviteEndpoint := factory.NewAPIEndpoint(
+		&types.APIRequestMetadata{
+			Verb:   types.APIVerbGet,
+			Method: types.HTTPVerbGet,
+			Path: &types.Path{
+				Parent:       basePath,
+				RelativePath: relPath + "/invites/accept",
+			},
+			Scopes: []types.PermissionScope{},
+		},
+	)
+
+	acceptInviteHandler := invite.NewInviteAcceptHandler(
+		config,
+		factory.GetDecoderValidator(),
+	)
+
+	routes = append(routes, &Route{
+		Endpoint: acceptInviteEndpoint,
+		Handler:  acceptInviteHandler,
+		Router:   r,
+	})
+
 	return routes, newPath
 }

+ 4 - 0
api/types/invite.go

@@ -25,3 +25,7 @@ type ListInvitesResponse []*Invite
 type UpdateInviteRoleRequest struct {
 	Kind string `json:"kind,required"`
 }
+
+type AcceptInviteRequest struct {
+	Token string `schema:"token,required"`
+}

+ 2 - 2
dashboard/src/main/home/project-settings/InviteList.tsx

@@ -209,7 +209,7 @@ const InvitePage: React.FunctionComponent<Props> = ({}) => {
         Header: "Role",
         accessor: "kind",
         Cell: ({ row }) => {
-          return <Role>{row.values.kind || "Admin"}</Role>;
+          return <Role>{row.values.kind || "Developer"}</Role>;
         },
       },
       {
@@ -310,7 +310,7 @@ const InvitePage: React.FunctionComponent<Props> = ({}) => {
     const buildInviteLink = (token: string) => `
       ${isHTTPS ? "https://" : ""}${window.location.host}/api/projects/${
       currentProject.id
-    }/invites/${token}
+    }/invites/accept?token=${token}
     `;
 
     const mappedInviteList = inviteList.map(