satellite/console: add endpoint to get invite link
This change adds an endpoint that returns the invite link for invited users. Related to: #5762 Change-Id: I6432dfbe6405222b949fa02020aee2e01ab59c98
This commit is contained in:
parent
ac1ff0e7e2
commit
2caa5052ad
@ -97,6 +97,38 @@ func (p *Projects) InviteUsers(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
// GetInviteLink returns a link to an invitation given project ID and invitee's email.
|
||||
func (p *Projects) GetInviteLink(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
var err error
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
idParam, ok := mux.Vars(r)["id"]
|
||||
if !ok {
|
||||
p.serveJSONError(w, http.StatusBadRequest, errs.New("missing project id route param"))
|
||||
return
|
||||
}
|
||||
id, err := uuid.FromString(idParam)
|
||||
if err != nil {
|
||||
p.serveJSONError(w, http.StatusBadRequest, err)
|
||||
}
|
||||
|
||||
email := r.URL.Query().Get("email")
|
||||
if email == "" {
|
||||
p.serveJSONError(w, http.StatusBadRequest, errs.New("missing email query param"))
|
||||
return
|
||||
}
|
||||
|
||||
link, err := p.service.GetInviteLink(ctx, id, email)
|
||||
if err != nil {
|
||||
p.serveJSONError(w, http.StatusInternalServerError, err)
|
||||
}
|
||||
|
||||
err = json.NewEncoder(w).Encode(link)
|
||||
if err != nil {
|
||||
p.serveJSONError(w, http.StatusInternalServerError, err)
|
||||
}
|
||||
}
|
||||
|
||||
// GetUserInvitations returns the user's pending project member invitations.
|
||||
func (p *Projects) GetUserInvitations(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
@ -263,6 +263,7 @@ func NewServer(logger *zap.Logger, config Config, service *console.Service, oidc
|
||||
projectsRouter := router.PathPrefix("/api/v0/projects").Subrouter()
|
||||
projectsRouter.Handle("/{id}/salt", server.withAuth(http.HandlerFunc(projectsController.GetSalt))).Methods(http.MethodGet)
|
||||
projectsRouter.Handle("/{id}/invite", server.withAuth(http.HandlerFunc(projectsController.InviteUsers))).Methods(http.MethodPost)
|
||||
projectsRouter.Handle("/{id}/invite-link", server.withAuth(http.HandlerFunc(projectsController.GetInviteLink))).Methods(http.MethodGet)
|
||||
projectsRouter.Handle("/invitations", server.withAuth(http.HandlerFunc(projectsController.GetUserInvitations))).Methods(http.MethodGet)
|
||||
projectsRouter.Handle("/invitations/{id}/respond", server.withAuth(http.HandlerFunc(projectsController.RespondToInvitation))).Methods(http.MethodPost)
|
||||
|
||||
|
@ -128,10 +128,10 @@ func TestInvitedRouting(t *testing.T) {
|
||||
loginURL := baseURL + "login"
|
||||
invalidURL := loginURL + "?invite_invalid=true"
|
||||
|
||||
tokenInvalidProj, err := service.CreateInviteToken(ctx1, project.ID, user2.Email, time.Now())
|
||||
tokenInvalidProj, err := service.CreateInviteToken(ctx, project.ID, user2.Email, time.Now())
|
||||
require.NoError(t, err)
|
||||
|
||||
token, err := service.CreateInviteToken(ctx1, project.PublicID, user2.Email, time.Now())
|
||||
token, err := service.CreateInviteToken(ctx, project.PublicID, user2.Email, time.Now())
|
||||
require.NoError(t, err)
|
||||
|
||||
checkInvitedRedirect("Invited - Invalid projectID", invalidURL, tokenInvalidProj)
|
||||
@ -141,7 +141,7 @@ func TestInvitedRouting(t *testing.T) {
|
||||
_, err = service.InviteProjectMembers(ctx1, project.ID, []string{user2.Email})
|
||||
require.NoError(t, err)
|
||||
|
||||
token, err = service.CreateInviteToken(ctx1, project.PublicID, user2.Email, time.Now())
|
||||
token, err = service.CreateInviteToken(ctx, project.PublicID, user2.Email, time.Now())
|
||||
require.NoError(t, err)
|
||||
|
||||
// valid invite should redirect to login page with email.
|
||||
|
@ -3627,7 +3627,7 @@ func (s *Service) InviteProjectMembers(ctx context.Context, projectID uuid.UUID,
|
||||
return err
|
||||
}
|
||||
}
|
||||
token, err := s.CreateInviteToken(ctx, isMember.project.PublicID, invited.Email, invite.CreatedAt.Add(s.config.ProjectInvitationExpiration))
|
||||
token, err := s.CreateInviteToken(ctx, isMember.project.PublicID, invited.Email, invite.CreatedAt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -3698,20 +3698,41 @@ func (s *Service) GetInviteByToken(ctx context.Context, token string) (invite *P
|
||||
return invite, nil
|
||||
}
|
||||
|
||||
// CreateInviteToken creates a token for project invite links.
|
||||
func (s *Service) CreateInviteToken(ctx context.Context, publicProjectID uuid.UUID, email string, inviteDate time.Time) (_ string, err error) {
|
||||
// GetInviteLink returns a link for project invites.
|
||||
func (s *Service) GetInviteLink(ctx context.Context, publicProjectID uuid.UUID, email string) (_ string, err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
||||
user, err := s.getUserAndAuditLog(ctx, "create invite token", zap.String("projectID", publicProjectID.String()), zap.String("email", email))
|
||||
user, err := s.getUserAndAuditLog(ctx, "get invite link", zap.String("projectID", publicProjectID.String()), zap.String("email", email))
|
||||
if err != nil {
|
||||
return "", Error.Wrap(err)
|
||||
}
|
||||
|
||||
_, err = s.isProjectMember(ctx, user.ID, publicProjectID)
|
||||
isMember, err := s.isProjectMember(ctx, user.ID, publicProjectID)
|
||||
if err != nil {
|
||||
return "", Error.Wrap(err)
|
||||
}
|
||||
|
||||
invite, err := s.store.ProjectInvitations().Get(ctx, isMember.project.ID, email)
|
||||
if err != nil {
|
||||
if !errs.Is(err, sql.ErrNoRows) {
|
||||
return "", Error.Wrap(err)
|
||||
}
|
||||
return "", ErrProjectInviteInvalid.New(projInviteInvalidErrMsg)
|
||||
}
|
||||
|
||||
token, err := s.CreateInviteToken(ctx, publicProjectID, email, invite.CreatedAt)
|
||||
if err != nil {
|
||||
return "", Error.Wrap(err)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s/invited?invite=%s", s.satelliteAddress, token), nil
|
||||
}
|
||||
|
||||
// CreateInviteToken creates a token for project invite links.
|
||||
// Internal use only, since it doesn't check if the project is valid or the user is a member of the project.
|
||||
func (s *Service) CreateInviteToken(ctx context.Context, publicProjectID uuid.UUID, email string, inviteDate time.Time) (_ string, err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
||||
linkClaims := consoleauth.Claims{
|
||||
ID: publicProjectID,
|
||||
Email: email,
|
||||
|
@ -2074,19 +2074,10 @@ func TestProjectInvitations(t *testing.T) {
|
||||
|
||||
t.Run("invite tokens", func(t *testing.T) {
|
||||
user, ctx1 := getUserAndCtx(t)
|
||||
_, ctx2 := getUserAndCtx(t)
|
||||
|
||||
project, err := sat.AddProject(ctx1, user.ID, "Test Project")
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = service.CreateInviteToken(ctx2, project.PublicID, email, time.Now())
|
||||
require.Error(t, err)
|
||||
require.True(t, console.ErrNoMembership.Has(err))
|
||||
|
||||
_, err = service.CreateInviteToken(ctx1, testrand.UUID(), email, time.Now())
|
||||
require.Error(t, err)
|
||||
require.ErrorIs(t, err, sql.ErrNoRows)
|
||||
|
||||
someToken, err := service.CreateInviteToken(ctx1, project.PublicID, email, time.Now())
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, someToken)
|
||||
@ -2105,6 +2096,40 @@ func TestProjectInvitations(t *testing.T) {
|
||||
require.True(t, console.ErrTokenExpiration.Has(err))
|
||||
})
|
||||
|
||||
t.Run("invite links", func(t *testing.T) {
|
||||
user, ctx1 := getUserAndCtx(t)
|
||||
user2, ctx2 := getUserAndCtx(t)
|
||||
|
||||
project, err := sat.AddProject(ctx1, user.ID, "Test Project")
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = service.GetInviteLink(ctx2, project.PublicID, user2.Email)
|
||||
require.Error(t, err)
|
||||
require.True(t, console.ErrNoMembership.Has(err))
|
||||
|
||||
// no such project
|
||||
_, err = service.GetInviteLink(ctx1, testrand.UUID(), user2.Email)
|
||||
require.Error(t, err)
|
||||
require.ErrorIs(t, err, sql.ErrNoRows)
|
||||
|
||||
// no invite exists.
|
||||
_, err = service.GetInviteLink(ctx1, project.PublicID, user2.Email)
|
||||
require.Error(t, err)
|
||||
require.True(t, console.ErrProjectInviteInvalid.Has(err))
|
||||
|
||||
invite := addInvite(t, ctx1, project, user2.Email)
|
||||
|
||||
someLink, err := service.GetInviteLink(ctx1, project.PublicID, user2.Email)
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, someLink)
|
||||
|
||||
someToken, err := service.CreateInviteToken(ctx1, project.PublicID, user2.Email, invite.CreatedAt)
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, someToken)
|
||||
|
||||
require.Contains(t, someLink, someToken)
|
||||
})
|
||||
|
||||
t.Run("get invite by invite token", func(t *testing.T) {
|
||||
owner, ctx := getUserAndCtx(t)
|
||||
user, _ := getUserAndCtx(t)
|
||||
|
Loading…
Reference in New Issue
Block a user