@@ -142,6 +142,25 @@ pub struct WorkOSOrganizationDomain {
142142 pub domain : String ,
143143}
144144
145+ #[ derive( Debug , Deserialize , Serialize , Clone ) ]
146+ pub struct WorkOSOrganizationMembershipResponse {
147+ /// always "organization_membership"
148+ pub object : String ,
149+ /// like "om_01E4ZCR3C5A4QZ2Z2JQXGKZJ9E"
150+ pub id : String ,
151+ pub user_id : String ,
152+ pub organization_id : String ,
153+ pub role : WorkOSOrganizationRole ,
154+ pub status : String ,
155+ pub created_at : String ,
156+ pub updated_at : String ,
157+ }
158+
159+ #[ derive( Debug , Deserialize , Serialize , Clone ) ]
160+ pub struct WorkOSOrganizationRole {
161+ pub slug : String ,
162+ }
163+
145164#[ async_trait]
146165pub trait WorkOSClient : Send + Sync {
147166 async fn fetch_identities ( & self , user_id : & str ) -> anyhow:: Result < Vec < WorkOSIdentity > > ;
@@ -167,6 +186,14 @@ pub trait WorkOSClient: Send + Sync {
167186 domain : Option < & str > ,
168187 ) -> anyhow:: Result < WorkOSOrganizationResponse > ;
169188 async fn delete_organization ( & self , organization_id : & str ) -> anyhow:: Result < ( ) > ;
189+
190+ // Organization membership methods
191+ async fn create_membership (
192+ & self ,
193+ user_id : & str ,
194+ organization_id : & str ,
195+ role_slug : & str ,
196+ ) -> anyhow:: Result < WorkOSOrganizationMembershipResponse > ;
170197}
171198
172199// Separate trait for WorkOS Platform API operations (requires different API
@@ -274,6 +301,22 @@ where
274301 async fn delete_organization ( & self , organization_id : & str ) -> anyhow:: Result < ( ) > {
275302 delete_workos_organization ( & self . api_key , organization_id, & * self . http_client ) . await
276303 }
304+
305+ async fn create_membership (
306+ & self ,
307+ user_id : & str ,
308+ organization_id : & str ,
309+ role_slug : & str ,
310+ ) -> anyhow:: Result < WorkOSOrganizationMembershipResponse > {
311+ create_workos_membership (
312+ & self . api_key ,
313+ user_id,
314+ organization_id,
315+ role_slug,
316+ & * self . http_client ,
317+ )
318+ . await
319+ }
277320}
278321
279322pub struct MockWorkOSClient ;
@@ -396,6 +439,26 @@ impl WorkOSClient for MockWorkOSClient {
396439 async fn delete_organization ( & self , _organization_id : & str ) -> anyhow:: Result < ( ) > {
397440 Ok ( ( ) )
398441 }
442+
443+ async fn create_membership (
444+ & self ,
445+ user_id : & str ,
446+ organization_id : & str ,
447+ role_slug : & str ,
448+ ) -> anyhow:: Result < WorkOSOrganizationMembershipResponse > {
449+ Ok ( WorkOSOrganizationMembershipResponse {
450+ object : "organization_membership" . to_string ( ) ,
451+ id : "om_mock123" . to_string ( ) ,
452+ user_id : user_id. to_string ( ) ,
453+ organization_id : organization_id. to_string ( ) ,
454+ role : WorkOSOrganizationRole {
455+ slug : role_slug. to_string ( ) ,
456+ } ,
457+ status : "active" . to_string ( ) ,
458+ created_at : "2024-01-01T00:00:00.000Z" . to_string ( ) ,
459+ updated_at : "2024-01-01T00:00:00.000Z" . to_string ( ) ,
460+ } )
461+ }
399462}
400463
401464// Separate implementation for WorkOS Platform API
@@ -1153,6 +1216,72 @@ where
11531216 Ok ( ( ) )
11541217}
11551218
1219+ pub async fn create_workos_membership < F , E > (
1220+ api_key : & str ,
1221+ user_id : & str ,
1222+ organization_id : & str ,
1223+ role_slug : & str ,
1224+ http_client : & ( impl Fn ( HttpRequest ) -> F + ' static + ?Sized ) ,
1225+ ) -> anyhow:: Result < WorkOSOrganizationMembershipResponse >
1226+ where
1227+ F : Future < Output = Result < HttpResponse , E > > ,
1228+ E : std:: error:: Error + ' static + Send + Sync ,
1229+ {
1230+ #[ derive( Serialize ) ]
1231+ struct CreateMembershipRequest {
1232+ user_id : String ,
1233+ organization_id : String ,
1234+ role_slug : String ,
1235+ }
1236+
1237+ let request_body = CreateMembershipRequest {
1238+ user_id : user_id. to_string ( ) ,
1239+ organization_id : organization_id. to_string ( ) ,
1240+ role_slug : role_slug. to_string ( ) ,
1241+ } ;
1242+
1243+ let url = "https://api.workos.com/user_management/organization_memberships" ;
1244+
1245+ let request = http:: Request :: builder ( )
1246+ . uri ( url)
1247+ . method ( http:: Method :: POST )
1248+ . header ( http:: header:: AUTHORIZATION , format ! ( "Bearer {api_key}" ) )
1249+ . header ( http:: header:: CONTENT_TYPE , APPLICATION_JSON )
1250+ . header ( http:: header:: ACCEPT , APPLICATION_JSON )
1251+ . body ( serde_json:: to_vec ( & request_body) ?) ?;
1252+
1253+ let response = timeout ( WORKOS_API_TIMEOUT , http_client ( request) )
1254+ . await
1255+ . map_err ( |_| {
1256+ anyhow:: anyhow!(
1257+ "WorkOS API call timed out after {}s" ,
1258+ WORKOS_API_TIMEOUT . as_secs( )
1259+ )
1260+ } ) ?
1261+ . map_err ( |e| anyhow:: anyhow!( "Could not create WorkOS membership: {}" , e) ) ?;
1262+
1263+ if !response. status ( ) . is_success ( ) {
1264+ let status = response. status ( ) ;
1265+ let response_body = response. into_body ( ) ;
1266+ anyhow:: bail!( format_workos_error(
1267+ "create membership" ,
1268+ status,
1269+ & response_body
1270+ ) ) ;
1271+ }
1272+
1273+ let response_body = response. into_body ( ) ;
1274+ let membership: WorkOSOrganizationMembershipResponse = serde_json:: from_slice ( & response_body)
1275+ . with_context ( || {
1276+ format ! (
1277+ "Invalid WorkOS membership response: {}" ,
1278+ String :: from_utf8_lossy( & response_body)
1279+ )
1280+ } ) ?;
1281+
1282+ Ok ( membership)
1283+ }
1284+
11561285#[ cfg( test) ]
11571286mod tests {
11581287 use super :: WorkOSIdentity ;
0 commit comments