Skip to content
This repository was archived by the owner on May 13, 2026. It is now read-only.

Commit 7740c06

Browse files
feat(user-management): add invitation (#68)
* feat(user-management): add invitation * refactor: cleanup --------- Co-authored-by: Daniëlle Huisman <danielle@huisman.me>
1 parent 30bb616 commit 7740c06

15 files changed

Lines changed: 1057 additions & 12 deletions

src/user_management/operations.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
mod accept_invitation;
12
mod authenticate_with_code;
23
mod authenticate_with_email_verification;
34
mod authenticate_with_magic_auth;
@@ -9,8 +10,10 @@ mod create_password_reset;
910
mod create_user;
1011
mod delete_user;
1112
mod enroll_auth_factor;
13+
mod find_invitation_by_token;
1214
mod get_authorization_url;
1315
mod get_email_verification;
16+
mod get_invitation;
1417
mod get_jwks;
1518
mod get_jwks_url;
1619
mod get_logout_url;
@@ -20,10 +23,14 @@ mod get_user;
2023
mod get_user_by_external_id;
2124
mod get_user_identities;
2225
mod list_auth_factors;
26+
mod list_invitations;
2327
mod list_users;
2428
mod reset_password;
29+
mod revoke_invitation;
30+
mod send_invitation;
2531
mod update_user;
2632

33+
pub use accept_invitation::*;
2734
pub use authenticate_with_code::*;
2835
pub use authenticate_with_email_verification::*;
2936
pub use authenticate_with_magic_auth::*;
@@ -35,8 +42,10 @@ pub use create_password_reset::*;
3542
pub use create_user::*;
3643
pub use delete_user::*;
3744
pub use enroll_auth_factor::*;
45+
pub use find_invitation_by_token::*;
3846
pub use get_authorization_url::*;
3947
pub use get_email_verification::*;
48+
pub use get_invitation::*;
4049
pub use get_jwks::*;
4150
pub use get_jwks_url::*;
4251
pub use get_logout_url::*;
@@ -46,6 +55,9 @@ pub use get_user::*;
4655
pub use get_user_by_external_id::*;
4756
pub use get_user_identities::*;
4857
pub use list_auth_factors::*;
58+
pub use list_invitations::*;
4959
pub use list_users::*;
5060
pub use reset_password::*;
61+
pub use revoke_invitation::*;
62+
pub use send_invitation::*;
5163
pub use update_user::*;
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
use async_trait::async_trait;
2+
use thiserror::Error;
3+
4+
use crate::user_management::{Invitation, InvitationId, UserManagement};
5+
use crate::{ResponseExt, WorkOsError, WorkOsResult};
6+
7+
/// An error returned from [`AcceptInvitation`].
8+
#[derive(Debug, Error)]
9+
pub enum AcceptInvitationError {}
10+
11+
impl From<AcceptInvitationError> for WorkOsError<AcceptInvitationError> {
12+
fn from(err: AcceptInvitationError) -> Self {
13+
Self::Operation(err)
14+
}
15+
}
16+
17+
/// [WorkOS Docs: Accept an invitation](https://workos.com/docs/reference/user-management/invitation/accept)
18+
#[async_trait]
19+
pub trait AcceptInvitation {
20+
/// Accepts an invitation and, if linked to an organization, activates the user's membership in that organization.
21+
///
22+
/// [WorkOS Docs: Accept an invitation](https://workos.com/docs/reference/user-management/invitation/accept)
23+
///
24+
/// # Examples
25+
///
26+
/// ```
27+
/// # use workos_sdk::WorkOsResult;
28+
/// # use workos_sdk::user_management::*;
29+
/// use workos_sdk::{ApiKey, WorkOs};
30+
///
31+
/// # async fn run() -> WorkOsResult<(), AcceptInvitationError> {
32+
/// let workos = WorkOs::new(&ApiKey::from("sk_example_123456789"));
33+
///
34+
/// let invitation = workos
35+
/// .user_management()
36+
/// .accept_invitation(&InvitationId::from("invitation_01E4ZCR3C56J083X43JQXF3JK5"))
37+
/// .await?;
38+
/// # Ok(())
39+
/// # }
40+
/// ```
41+
async fn accept_invitation(
42+
&self,
43+
invitation_id: &InvitationId,
44+
) -> WorkOsResult<Invitation, AcceptInvitationError>;
45+
}
46+
47+
#[async_trait]
48+
impl AcceptInvitation for UserManagement<'_> {
49+
async fn accept_invitation(
50+
&self,
51+
invitation_id: &InvitationId,
52+
) -> WorkOsResult<Invitation, AcceptInvitationError> {
53+
let url = self.workos.base_url().join(&format!(
54+
"/user_management/invitations/{invitation_id}/accept"
55+
))?;
56+
let user = self
57+
.workos
58+
.client()
59+
.post(url)
60+
.bearer_auth(self.workos.key())
61+
.send()
62+
.await?
63+
.handle_unauthorized_or_generic_error()?
64+
.json::<Invitation>()
65+
.await?;
66+
67+
Ok(user)
68+
}
69+
}
70+
71+
#[cfg(test)]
72+
mod test {
73+
use serde_json::json;
74+
use tokio;
75+
76+
use crate::user_management::InvitationId;
77+
use crate::{ApiKey, WorkOs};
78+
79+
use super::*;
80+
81+
#[tokio::test]
82+
async fn it_calls_the_accept_invitation_endpoint() {
83+
let mut server = mockito::Server::new_async().await;
84+
85+
let workos = WorkOs::builder(&ApiKey::from("sk_example_123456789"))
86+
.base_url(&server.url())
87+
.unwrap()
88+
.build();
89+
90+
server
91+
.mock("POST", "/user_management/invitations/invitation_01E4ZCR3C56J083X43JQXF3JK5/accept")
92+
.match_header("Authorization", "Bearer sk_example_123456789")
93+
.with_status(200)
94+
.with_body(
95+
json!({
96+
"object": "invitation",
97+
"id": "invitation_01E4ZCR3C56J083X43JQXF3JK5",
98+
"email": "marcelina.davis@example.com",
99+
"state": "accepted",
100+
"accepted_at": "2021-06-27T19:07:33.155Z",
101+
"revoked_at": null,
102+
"expires_at": "2021-07-01T19:07:33.155Z",
103+
"token": "Z1uX3RbwcIl5fIGJJJCXXisdI",
104+
"accept_invitation_url": "https://your-app.com/invite?invitation_token=Z1uX3RbwcIl5fIGJJJCXXisdI",
105+
"organization_id": "org_01E4ZCR3C56J083X43JQXF3JK5",
106+
"inviter_user_id": "user_01HYGBX8ZGD19949T3BM4FW1C3",
107+
"accepted_user_id": "user_01JBJDMMV04RSWPG30MQE8ADFV",
108+
"created_at": "2021-06-25T19:07:33.155Z",
109+
"updated_at": "2021-06-25T19:07:33.155Z"
110+
})
111+
.to_string(),
112+
)
113+
.create_async()
114+
.await;
115+
116+
let invitation = workos
117+
.user_management()
118+
.accept_invitation(&InvitationId::from("invitation_01E4ZCR3C56J083X43JQXF3JK5"))
119+
.await
120+
.unwrap();
121+
122+
assert_eq!(
123+
invitation.id,
124+
InvitationId::from("invitation_01E4ZCR3C56J083X43JQXF3JK5")
125+
);
126+
assert!(invitation.accepted_at.is_some());
127+
}
128+
}

src/user_management/operations/create_magic_auth.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,6 @@ pub trait CreateMagicAuth {
3535
/// # Examples
3636
///
3737
/// ```
38-
/// use std::collections::HashSet;
39-
///
4038
/// # use workos_sdk::WorkOsResult;
4139
/// # use workos_sdk::user_management::*;
4240
/// use workos_sdk::{ApiKey, WorkOs};

src/user_management/operations/create_password_reset.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,6 @@ pub trait CreatePasswordReset {
7373
/// # Examples
7474
///
7575
/// ```
76-
/// use std::collections::HashSet;
77-
///
7876
/// # use workos_sdk::WorkOsResult;
7977
/// # use workos_sdk::user_management::*;
8078
/// use workos_sdk::{ApiKey, WorkOs};

src/user_management/operations/create_user.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,6 @@ pub trait CreateUser {
5151
/// # Examples
5252
///
5353
/// ```
54-
/// use std::collections::HashSet;
55-
///
5654
/// # use workos_sdk::WorkOsResult;
5755
/// # use workos_sdk::user_management::*;
5856
/// use workos_sdk::{ApiKey, WorkOs};

src/user_management/operations/enroll_auth_factor.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,8 +107,6 @@ pub trait EnrollAuthFactor {
107107
/// # Examples
108108
///
109109
/// ```
110-
/// use std::collections::HashSet;
111-
///
112110
/// # use workos_sdk::WorkOsResult;
113111
/// # use workos_sdk::user_management::*;
114112
/// use workos_sdk::{ApiKey, WorkOs};
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
use async_trait::async_trait;
2+
use thiserror::Error;
3+
4+
use crate::user_management::{Invitation, InvitationToken, UserManagement};
5+
use crate::{ResponseExt, WorkOsError, WorkOsResult};
6+
7+
/// An error returned from [`FindInvitationByToken`].
8+
#[derive(Debug, Error)]
9+
pub enum FindInvitationByTokenError {}
10+
11+
impl From<FindInvitationByTokenError> for WorkOsError<FindInvitationByTokenError> {
12+
fn from(err: FindInvitationByTokenError) -> Self {
13+
Self::Operation(err)
14+
}
15+
}
16+
17+
/// [WorkOS Docs: Find an invitation by token](https://workos.com/docs/reference/user-management/invitation/find-by-token)
18+
#[async_trait]
19+
pub trait FindInvitationByToken {
20+
/// Retrieve an existing invitation using the token.
21+
///
22+
/// [WorkOS Docs: Find an invitation by token](https://workos.com/docs/reference/user-management/invitation/find-by-token)
23+
///
24+
/// # Examples
25+
///
26+
/// ```
27+
/// # use workos_sdk::WorkOsResult;
28+
/// # use workos_sdk::user_management::*;
29+
/// use workos_sdk::{ApiKey, WorkOs};
30+
///
31+
/// # async fn run() -> WorkOsResult<(), FindInvitationByTokenError> {
32+
/// let workos = WorkOs::new(&ApiKey::from("sk_example_123456789"));
33+
///
34+
/// let invitation = workos
35+
/// .user_management()
36+
/// .find_invitation_by_token(&InvitationToken::from("Z1uX3RbwcIl5fIGJJJCXXisdI"))
37+
/// .await?;
38+
/// # Ok(())
39+
/// # }
40+
/// ```
41+
async fn find_invitation_by_token(
42+
&self,
43+
token: &InvitationToken,
44+
) -> WorkOsResult<Invitation, FindInvitationByTokenError>;
45+
}
46+
47+
#[async_trait]
48+
impl FindInvitationByToken for UserManagement<'_> {
49+
async fn find_invitation_by_token(
50+
&self,
51+
token: &InvitationToken,
52+
) -> WorkOsResult<Invitation, FindInvitationByTokenError> {
53+
let url = self
54+
.workos
55+
.base_url()
56+
.join(&format!("/user_management/invitations/by_token/{token}"))?;
57+
let organization = self
58+
.workos
59+
.client()
60+
.get(url)
61+
.bearer_auth(self.workos.key())
62+
.send()
63+
.await?
64+
.handle_unauthorized_or_generic_error()?
65+
.json::<Invitation>()
66+
.await?;
67+
68+
Ok(organization)
69+
}
70+
}
71+
72+
#[cfg(test)]
73+
mod test {
74+
use serde_json::json;
75+
use tokio;
76+
77+
use crate::{ApiKey, WorkOs, user_management::InvitationId};
78+
79+
use super::*;
80+
81+
#[tokio::test]
82+
async fn it_calls_the_find_invitation_by_token_endpoint() {
83+
let mut server = mockito::Server::new_async().await;
84+
85+
let workos = WorkOs::builder(&ApiKey::from("sk_example_123456789"))
86+
.base_url(&server.url())
87+
.unwrap()
88+
.build();
89+
90+
server
91+
.mock(
92+
"GET",
93+
"/user_management/invitations/by_token/Z1uX3RbwcIl5fIGJJJCXXisdI",
94+
)
95+
.match_header("Authorization", "Bearer sk_example_123456789")
96+
.with_status(200)
97+
.with_body(
98+
json!({
99+
"object": "invitation",
100+
"id": "invitation_01E4ZCR3C56J083X43JQXF3JK5",
101+
"email": "marcelina.davis@example.com",
102+
"state": "pending",
103+
"accepted_at": null,
104+
"revoked_at": null,
105+
"expires_at": "2021-07-01T19:07:33.155Z",
106+
"token": "Z1uX3RbwcIl5fIGJJJCXXisdI",
107+
"accept_invitation_url": "https://your-app.com/invite?invitation_token=Z1uX3RbwcIl5fIGJJJCXXisdI",
108+
"organization_id": "org_01E4ZCR3C56J083X43JQXF3JK5",
109+
"inviter_user_id": "user_01HYGBX8ZGD19949T3BM4FW1C3",
110+
"created_at": "2021-06-25T19:07:33.155Z",
111+
"updated_at": "2021-06-25T19:07:33.155Z"
112+
})
113+
.to_string(),
114+
)
115+
.create_async()
116+
.await;
117+
118+
let invitation = workos
119+
.user_management()
120+
.find_invitation_by_token(&InvitationToken::from("Z1uX3RbwcIl5fIGJJJCXXisdI"))
121+
.await
122+
.unwrap();
123+
124+
assert_eq!(
125+
invitation.id,
126+
InvitationId::from("invitation_01E4ZCR3C56J083X43JQXF3JK5")
127+
)
128+
}
129+
}

0 commit comments

Comments
 (0)