Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 56 additions & 1 deletion pkg/connector/invitation.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@ package connector

import (
"context"
"errors"
"fmt"
"net/http"
"strconv"
"strings"

v2 "github.com/conductorone/baton-sdk/pb/c1/connector/v2"
"github.com/conductorone/baton-sdk/pkg/annotations"
Expand Down Expand Up @@ -140,6 +143,14 @@ func (i *invitationResourceType) CreateAccount(
Email: params.email,
})
if err != nil {
if isAlreadyOrgMemberError(err, resp) {
return &v2.CreateAccountResponse_AlreadyExistsResult{}, nil, nil, nil
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you need to return the resource as part of the response so c1 caches this value?

}
if isAlreadyInvitedError(err, resp) {
return &v2.CreateAccountResponse_ActionRequiredResult{
Message: "GitHub org invite already pending. User must accept the existing invitation.",
}, nil, nil, nil
}
return nil, nil, nil, wrapGitHubError(err, resp, "github-connector: failed to create org invitation")
}

Expand All @@ -155,8 +166,9 @@ func (i *invitationResourceType) CreateAccount(
if err != nil {
return nil, nil, nil, fmt.Errorf("github-connectorv2: cannot create user resource: %w", err)
}
return &v2.CreateAccountResponse_SuccessResult{
return &v2.CreateAccountResponse_ActionRequiredResult{
Resource: r,
Message: "GitHub org invite sent. User must accept the invitation before team membership can be granted.",
}, nil, annotations, nil
}

Expand Down Expand Up @@ -224,6 +236,49 @@ func getCreateUserParams(accountInfo *v2.AccountInfo) (*createUserParams, error)
}, nil
}

// isAlreadyOrgMemberError returns true if the GitHub API error indicates
// the user is already an organization member.
func isAlreadyOrgMemberError(err error, resp *github.Response) bool {
if resp == nil || resp.StatusCode != http.StatusUnprocessableEntity {
return false
}
var ghErr *github.ErrorResponse
if errors.As(err, &ghErr) {
msg := strings.ToLower(ghErr.Message)
if strings.Contains(msg, "already a member") {
return true
}
for _, e := range ghErr.Errors {
if strings.Contains(strings.ToLower(e.Message), "already a member") {
return true
}
}
}
return false
}

// isAlreadyInvitedError returns true if the GitHub API error indicates
// the user already has a pending invitation.
func isAlreadyInvitedError(err error, resp *github.Response) bool {
if resp == nil || resp.StatusCode != http.StatusUnprocessableEntity {
return false
}
var ghErr *github.ErrorResponse
if errors.As(err, &ghErr) {
msg := strings.ToLower(ghErr.Message)
if strings.Contains(msg, "already invited") || strings.Contains(msg, "already been invited") {
return true
}
for _, e := range ghErr.Errors {
lower := strings.ToLower(e.Message)
if strings.Contains(lower, "already invited") || strings.Contains(lower, "already been invited") {
return true
}
}
}
return false
}

type invitationBuilderParams struct {
client *github.Client
orgCache *orgNameCache
Expand Down
Loading