Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Identity functionality to User Management #343

Merged
merged 2 commits into from
Jun 3, 2024
Merged
Show file tree
Hide file tree
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
62 changes: 61 additions & 1 deletion pkg/usermanagement/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ type Invitation struct {
AcceptedAt string `json:"accepted_at,omitempty"`
RevokedAt string `json:"revoked_at,omitempty"`
Token string `json:"token"`
AcceptInvitationUrl string `json:"accept_invitation_url`
AcceptInvitationUrl string `json:"accept_invitation_url"`
OrganizationID string `json:"organization_id,omitempty"`
InviterUserID string `json:"inviter_user_id,omitempty"`
ExpiresAt string `json:"expires_at"`
Expand Down Expand Up @@ -165,6 +165,16 @@ type User struct {
ProfilePictureURL string `json:"profile_picture_url"`
}

// Represents User identities obtained from external identity providers.
type Identity struct {
// The unique ID of the user in the external identity provider.
IdpID string `json:"idp_id"`
// The type of the identity.
Type string `json:"type"`
// The type of OAuth provider for the identity.
Provider string `json:"provider"`
}

// GetUserOpts contains the options to pass in order to get a user profile.
type GetUserOpts struct {
// User unique identifier
Expand Down Expand Up @@ -530,6 +540,14 @@ type RevokeSessionOpts struct {
SessionID string `json:"session_id"`
}

type ListIdentitiesResult struct {
Identities []Identity `json:"identities"`
}

type ListIdentitiesOpts struct {
ID string `json:"id"`
}

func NewClient(apiKey string) *Client {
return &Client{
APIKey: apiKey,
Expand Down Expand Up @@ -745,6 +763,48 @@ func (c *Client) DeleteUser(ctx context.Context, opts DeleteUserOpts) error {
return workos_errors.TryGetHTTPError(res)
}

func (c *Client) ListIdentities(ctx context.Context, opts ListIdentitiesOpts) (ListIdentitiesResult, error) {
endpoint := fmt.Sprintf(
"%s/user_management/users/%s/identities",
c.Endpoint,
opts.ID,
)

data, err := c.JSONEncode(opts)
if err != nil {
return ListIdentitiesResult{}, err
}

req, err := http.NewRequest(
http.MethodGet,
endpoint,
bytes.NewBuffer(data),
)
if err != nil {
return ListIdentitiesResult{}, err
}
req = req.WithContext(ctx)
req.Header.Set("User-Agent", "workos-go/"+workos.Version)
req.Header.Set("Authorization", "Bearer "+c.APIKey)
req.Header.Set("Content-Type", "application/json")

res, err := c.HTTPClient.Do(req)
if err != nil {
return ListIdentitiesResult{}, err
}
defer res.Body.Close()

if err = workos_errors.TryGetHTTPError(res); err != nil {
return ListIdentitiesResult{}, err
}

var body ListIdentitiesResult
dec := json.NewDecoder(res.Body)
err = dec.Decode(&body)

return body, err
}

// GetAuthorizationURLOpts contains the options to pass in order to generate
// an authorization url.
type GetAuthorizationURLOpts struct {
Expand Down
86 changes: 86 additions & 0 deletions pkg/usermanagement/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -509,6 +509,92 @@ func deleteUserTestHandler(w http.ResponseWriter, r *http.Request) {
w.Write(body)
}

func TestListIdentities(t *testing.T) {
tests := []struct {
scenario string
client *Client
options ListIdentitiesOpts
expected ListIdentitiesResult
err bool
}{
{
scenario: "Request without API Key returns an error",
client: NewClient(""),
err: true,
},
{
scenario: "Request returns identities",
client: NewClient("test"),
options: ListIdentitiesOpts{
ID: "user_01E3JC5F5Z1YJNPGVYWV9SX6GH",
},
expected: ListIdentitiesResult{
Identities: []Identity{
{
IdpID: "13966412",
Type: "OAuth",
Provider: "GitHubOAuth",
},
},
},
err: false,
},
}

for _, test := range tests {
t.Run(test.scenario, func(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(listIdentitiesTestHandler))
defer server.Close()

client := test.client
client.Endpoint = server.URL
client.HTTPClient = server.Client()

identities, err := client.ListIdentities(context.Background(), test.options)
if test.err {
require.Error(t, err)
return
}
require.NoError(t, err)
require.Equal(t, test.expected, identities)
})
}
}

func listIdentitiesTestHandler(w http.ResponseWriter, r *http.Request) {
auth := r.Header.Get("Authorization")
if auth != "Bearer test" {
http.Error(w, "bad auth", http.StatusUnauthorized)
return
}

if r.Method != http.MethodGet {
http.Error(w, "bad method", http.StatusBadRequest)
}

if userAgent := r.Header.Get("User-Agent"); !strings.Contains(userAgent, "workos-go/") {
w.WriteHeader(http.StatusBadRequest)
return
}

body, err := json.Marshal(ListIdentitiesResult{
Identities: []Identity{
{
IdpID: "13966412",
Type: "OAuth",
Provider: "GitHubOAuth",
},
},
})
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}

w.WriteHeader(http.StatusOK)
w.Write(body)
}

func TestClientAuthorizeURL(t *testing.T) {
tests := []struct {
scenario string
Expand Down
Loading