Skip to content

Commit d44716d

Browse files
authored
Add support for resending invites (#476)
1 parent 06c2538 commit d44716d

File tree

4 files changed

+206
-0
lines changed

4 files changed

+206
-0
lines changed

pkg/usermanagement/client.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -578,6 +578,10 @@ type RevokeInvitationOpts struct {
578578
Invitation string
579579
}
580580

581+
type ResendInvitationOpts struct {
582+
Invitation string
583+
}
584+
581585
type RevokeSessionOpts struct {
582586
SessionID string `json:"session_id"`
583587
}
@@ -2257,6 +2261,35 @@ func (c *Client) RevokeInvitation(ctx context.Context, opts RevokeInvitationOpts
22572261
return body, err
22582262
}
22592263

2264+
func (c *Client) ResendInvitation(ctx context.Context, opts ResendInvitationOpts) (Invitation, error) {
2265+
endpoint := fmt.Sprintf("%s/user_management/invitations/%s/resend", c.Endpoint, opts.Invitation)
2266+
2267+
req, err := http.NewRequest(http.MethodPost, endpoint, nil)
2268+
if err != nil {
2269+
return Invitation{}, err
2270+
}
2271+
req = req.WithContext(ctx)
2272+
req.Header.Set("User-Agent", "workos-go/"+workos.Version)
2273+
req.Header.Set("Authorization", "Bearer "+c.APIKey)
2274+
req.Header.Set("Content-Type", "application/json")
2275+
2276+
res, err := c.HTTPClient.Do(req)
2277+
if err != nil {
2278+
return Invitation{}, err
2279+
}
2280+
defer res.Body.Close()
2281+
2282+
if err = workos_errors.TryGetHTTPError(res); err != nil {
2283+
return Invitation{}, err
2284+
}
2285+
2286+
var body Invitation
2287+
dec := json.NewDecoder(res.Body)
2288+
err = dec.Decode(&body)
2289+
2290+
return body, err
2291+
}
2292+
22602293
func (c *Client) GetJWKSURL(clientID string) (*url.URL, error) {
22612294
if clientID == "" {
22622295
return nil, errors.New("clientID must not be blank")

pkg/usermanagement/client_test.go

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3494,6 +3494,90 @@ func TestRevokeInvitation(t *testing.T) {
34943494
}
34953495
}
34963496

3497+
func TestResendInvitation(t *testing.T) {
3498+
tests := []struct {
3499+
scenario string
3500+
client *Client
3501+
options ResendInvitationOpts
3502+
expected Invitation
3503+
err bool
3504+
}{
3505+
{
3506+
scenario: "Request without API Key returns an error",
3507+
client: NewClient(""),
3508+
err: true,
3509+
},
3510+
{
3511+
scenario: "Request returns Invitation",
3512+
client: NewClient("test"),
3513+
options: ResendInvitationOpts{
3514+
Invitation: "invitation_01E4ZCR3C56J083X43JQXF3JK5",
3515+
},
3516+
expected: Invitation{
3517+
ID: "invitation_01E4ZCR3C56J083X43JQXF3JK5",
3518+
3519+
State: Pending,
3520+
Token: "Z1uX3RbwcIl5fIGJJJCXXisdI",
3521+
AcceptInvitationUrl: "https://your-app.com/invite?invitation_token=Z1uX3RbwcIl5fIGJJJCXXisdI",
3522+
ExpiresAt: "2021-06-25T19:07:33.155Z",
3523+
CreatedAt: "2021-06-25T19:07:33.155Z",
3524+
UpdatedAt: "2021-06-25T19:07:33.155Z",
3525+
},
3526+
},
3527+
{
3528+
scenario: "Request returns 404 error for non-existent invitation",
3529+
client: NewClient("test"),
3530+
options: ResendInvitationOpts{
3531+
Invitation: "invitation_not_found",
3532+
},
3533+
err: true,
3534+
},
3535+
{
3536+
scenario: "Request returns 400 error for expired invitation",
3537+
client: NewClient("test"),
3538+
options: ResendInvitationOpts{
3539+
Invitation: "invitation_expired",
3540+
},
3541+
err: true,
3542+
},
3543+
{
3544+
scenario: "Request returns 400 error for revoked invitation",
3545+
client: NewClient("test"),
3546+
options: ResendInvitationOpts{
3547+
Invitation: "invitation_revoked",
3548+
},
3549+
err: true,
3550+
},
3551+
{
3552+
scenario: "Request returns 400 error for accepted invitation",
3553+
client: NewClient("test"),
3554+
options: ResendInvitationOpts{
3555+
Invitation: "invitation_accepted",
3556+
},
3557+
err: true,
3558+
},
3559+
}
3560+
3561+
for _, test := range tests {
3562+
t.Run(test.scenario, func(t *testing.T) {
3563+
server := httptest.NewServer(http.HandlerFunc(ResendInvitationTestHandler))
3564+
defer server.Close()
3565+
3566+
client := test.client
3567+
client.Endpoint = server.URL
3568+
client.HTTPClient = server.Client()
3569+
3570+
invitation, err := client.ResendInvitation(context.Background(), test.options)
3571+
if test.err {
3572+
require.Error(t, err)
3573+
return
3574+
}
3575+
require.NoError(t, err)
3576+
require.Equal(t, test.expected, invitation)
3577+
})
3578+
}
3579+
}
3580+
34973581
func TestGetLogoutURL(t *testing.T) {
34983582
tests := []struct {
34993583
scenario string
@@ -3538,6 +3622,60 @@ func TestGetLogoutURL(t *testing.T) {
35383622
}
35393623
}
35403624

3625+
func ResendInvitationTestHandler(w http.ResponseWriter, r *http.Request) {
3626+
auth := r.Header.Get("Authorization")
3627+
if auth != "Bearer test" {
3628+
http.Error(w, "bad auth", http.StatusUnauthorized)
3629+
return
3630+
}
3631+
3632+
var body []byte
3633+
var err error
3634+
3635+
switch r.URL.Path {
3636+
case "/user_management/invitations/invitation_01E4ZCR3C56J083X43JQXF3JK5/resend":
3637+
body, err = json.Marshal(
3638+
Invitation{
3639+
ID: "invitation_01E4ZCR3C56J083X43JQXF3JK5",
3640+
3641+
State: Pending,
3642+
Token: "Z1uX3RbwcIl5fIGJJJCXXisdI",
3643+
AcceptInvitationUrl: "https://your-app.com/invite?invitation_token=Z1uX3RbwcIl5fIGJJJCXXisdI",
3644+
ExpiresAt: "2021-06-25T19:07:33.155Z",
3645+
CreatedAt: "2021-06-25T19:07:33.155Z",
3646+
UpdatedAt: "2021-06-25T19:07:33.155Z",
3647+
})
3648+
case "/user_management/invitations/invitation_not_found/resend":
3649+
w.Header().Set("Content-Type", "application/json")
3650+
w.WriteHeader(http.StatusNotFound)
3651+
w.Write([]byte(`{"message": "Invitation not found"}`))
3652+
return
3653+
case "/user_management/invitations/invitation_expired/resend":
3654+
w.Header().Set("Content-Type", "application/json")
3655+
w.WriteHeader(http.StatusBadRequest)
3656+
w.Write([]byte(`{"code": "invite_expired", "message": "Invite has expired."}`))
3657+
return
3658+
case "/user_management/invitations/invitation_revoked/resend":
3659+
w.Header().Set("Content-Type", "application/json")
3660+
w.WriteHeader(http.StatusBadRequest)
3661+
w.Write([]byte(`{"code": "invite_revoked", "message": "Invite has been revoked."}`))
3662+
return
3663+
case "/user_management/invitations/invitation_accepted/resend":
3664+
w.Header().Set("Content-Type", "application/json")
3665+
w.WriteHeader(http.StatusBadRequest)
3666+
w.Write([]byte(`{"code": "invite_accepted", "message": "Invite has already been accepted."}`))
3667+
return
3668+
}
3669+
3670+
if err != nil {
3671+
w.WriteHeader(http.StatusInternalServerError)
3672+
return
3673+
}
3674+
3675+
w.WriteHeader(http.StatusOK)
3676+
w.Write(body)
3677+
}
3678+
35413679
func RevokeInvitationTestHandler(w http.ResponseWriter, r *http.Request) {
35423680
auth := r.Header.Get("Authorization")
35433681
if auth != "Bearer test" {

pkg/usermanagement/usermanagement.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,13 @@ func RevokeInvitation(
340340
return DefaultClient.RevokeInvitation(ctx, opts)
341341
}
342342

343+
func ResendInvitation(
344+
ctx context.Context,
345+
opts ResendInvitationOpts,
346+
) (Invitation, error) {
347+
return DefaultClient.ResendInvitation(ctx, opts)
348+
}
349+
343350
func GetJWKSURL(clientID string) (*url.URL, error) {
344351
return DefaultClient.GetJWKSURL(clientID)
345352
}

pkg/usermanagement/usermanagement_test.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1051,6 +1051,34 @@ func TestUsersRevokeInvitation(t *testing.T) {
10511051
require.Equal(t, expectedResponse, revokeRes)
10521052
}
10531053

1054+
func TestUsersResendInvitation(t *testing.T) {
1055+
server := httptest.NewServer(http.HandlerFunc(ResendInvitationTestHandler))
1056+
1057+
defer server.Close()
1058+
1059+
DefaultClient = mockClient(server)
1060+
1061+
SetAPIKey("test")
1062+
1063+
expectedResponse := Invitation{
1064+
ID: "invitation_01E4ZCR3C56J083X43JQXF3JK5",
1065+
1066+
State: Pending,
1067+
Token: "Z1uX3RbwcIl5fIGJJJCXXisdI",
1068+
AcceptInvitationUrl: "https://your-app.com/invite?invitation_token=Z1uX3RbwcIl5fIGJJJCXXisdI",
1069+
ExpiresAt: "2021-06-25T19:07:33.155Z",
1070+
CreatedAt: "2021-06-25T19:07:33.155Z",
1071+
UpdatedAt: "2021-06-25T19:07:33.155Z",
1072+
}
1073+
1074+
resendRes, err := ResendInvitation(context.Background(), ResendInvitationOpts{
1075+
Invitation: "invitation_01E4ZCR3C56J083X43JQXF3JK5",
1076+
})
1077+
1078+
require.NoError(t, err)
1079+
require.Equal(t, expectedResponse, resendRes)
1080+
}
1081+
10541082
func TestUserManagementGetJWKSURL(t *testing.T) {
10551083
client := NewClient("test")
10561084

0 commit comments

Comments
 (0)