diff --git a/src/user_management/operations.rs b/src/user_management/operations.rs index 0d64e33..4366375 100644 --- a/src/user_management/operations.rs +++ b/src/user_management/operations.rs @@ -31,10 +31,12 @@ mod get_user_identities; mod list_auth_factors; mod list_invitations; mod list_organization_memberships; +mod list_sessions; mod list_users; mod reactivate_organization_membership; mod reset_password; mod revoke_invitation; +mod revoke_session; mod send_invitation; mod update_organization_membership; mod update_user; @@ -72,10 +74,12 @@ pub use get_user_identities::*; pub use list_auth_factors::*; pub use list_invitations::*; pub use list_organization_memberships::*; +pub use list_sessions::*; pub use list_users::*; pub use reactivate_organization_membership::*; pub use reset_password::*; pub use revoke_invitation::*; +pub use revoke_session::*; pub use send_invitation::*; pub use update_organization_membership::*; pub use update_user::*; diff --git a/src/user_management/operations/list_sessions.rs b/src/user_management/operations/list_sessions.rs new file mode 100644 index 0000000..0018a04 --- /dev/null +++ b/src/user_management/operations/list_sessions.rs @@ -0,0 +1,162 @@ +use async_trait::async_trait; +use serde::Serialize; +use thiserror::Error; + +use crate::user_management::{Session, UserId, UserManagement}; +use crate::{PaginatedList, PaginationParams, ResponseExt, WorkOsError, WorkOsResult}; + +/// The parameters for the [`ListSessions`] function. +#[derive(Debug, Serialize)] +pub struct ListSessionsParams<'a> { + /// The ID of the user. + #[serde(skip_serializing)] + pub user_id: &'a UserId, + + /// The pagination parameters to use when listing sessions. + #[serde(flatten)] + pub pagination: PaginationParams<'a>, +} + +/// An error returned from [`ListSessions`]. +#[derive(Debug, Error)] +pub enum ListSessionsError {} + +impl From for WorkOsError { + fn from(err: ListSessionsError) -> Self { + Self::Operation(err) + } +} + +/// [WorkOS Docs: List sessions](https://workos.com/docs/reference/user-management/session/list) +#[async_trait] +pub trait ListSessions { + /// Get a list of all active sessions for a specific user. + /// + /// [WorkOS Docs: List sessions](https://workos.com/docs/reference/user-management/session/list) + /// + /// # Examples + /// + /// ``` + /// # use workos::WorkOsResult; + /// # use workos::user_management::*; + /// use workos::{ApiKey, WorkOs}; + /// use workos::organizations::OrganizationId; + /// + /// # async fn run() -> WorkOsResult<(), ListSessionsError> { + /// let workos = WorkOs::new(&ApiKey::from("sk_example_123456789")); + /// + /// let sessions = workos + /// .user_management() + /// .list_sessions(&ListSessionsParams { + /// user_id: &UserId::from("user_01E4ZCR3C56J083X43JQXF3JK5"), + /// pagination: Default::default(), + /// }) + /// .await?; + /// # Ok(()) + /// # } + /// ``` + async fn list_sessions( + &self, + params: &ListSessionsParams, + ) -> WorkOsResult, ListSessionsError>; +} + +#[async_trait] +impl ListSessions for UserManagement<'_> { + async fn list_sessions( + &self, + params: &ListSessionsParams, + ) -> WorkOsResult, ListSessionsError> { + let url = self.workos.base_url().join(&format!( + "/user_management/users/{}/sessions", + params.user_id + ))?; + + let sessions = self + .workos + .client() + .get(url) + .query(¶ms) + .bearer_auth(self.workos.key()) + .send() + .await? + .handle_unauthorized_or_generic_error() + .await? + .json::>() + .await?; + + Ok(sessions) + } +} + +#[cfg(test)] +mod test { + use mockito::Matcher; + use serde_json::json; + use tokio; + + use crate::{ApiKey, WorkOs, user_management::SessionId}; + + use super::*; + + #[tokio::test] + async fn it_calls_the_get_list_sessions_endpoint() { + let mut server = mockito::Server::new_async().await; + + let workos = WorkOs::builder(&ApiKey::from("sk_example_123456789")) + .base_url(&server.url()) + .unwrap() + .build(); + + server + .mock("GET", "/user_management/users/user_01E4ZCR3C56J083X43JQXF3JK5/sessions") + .match_query(Matcher::UrlEncoded("order".to_string(), "desc".to_string())) + .match_header("Authorization", "Bearer sk_example_123456789") + .with_status(200) + .with_body( + json!({ + "data": [ + { + "object": "session", + "id": "session_01E4ZCR3C56J083X43JQXF3JK5", + "user_id": "user_01E4ZCR3C56J083X43JQXF3JK5", + "organization_id": "org_01E4ZCR3C56J083X43JQXF3JK5", + "status": "active", + "auth_method": "password", + "ip_address": "192.168.1.1", + "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36", + "expires_at": "2025-07-23T15:00:00.000Z", + "ended_at": null, + "created_at": "2025-07-23T14:00:00.000Z", + "updated_at": "2025-07-23T14:00:00.000Z" + } + ], + "list_metadata": { + "before": "session_01E4ZCR3C56J083X43JQXF3JK5", + "after": "session_01EJBGJT2PC6638TN5Y380M40Z" + } + }) + .to_string(), + ) + .create_async() + .await; + + let paginated_list = workos + .user_management() + .list_sessions(&ListSessionsParams { + user_id: &UserId::from("user_01E4ZCR3C56J083X43JQXF3JK5"), + pagination: Default::default(), + }) + .await + .unwrap(); + + assert_eq!( + paginated_list + .data + .into_iter() + .next() + .map(|session| session.id), + Some(SessionId::from("session_01E4ZCR3C56J083X43JQXF3JK5")) + ) + } +} diff --git a/src/user_management/operations/revoke_session.rs b/src/user_management/operations/revoke_session.rs new file mode 100644 index 0000000..7773e03 --- /dev/null +++ b/src/user_management/operations/revoke_session.rs @@ -0,0 +1,118 @@ +use async_trait::async_trait; +use serde::Serialize; +use thiserror::Error; + +use crate::user_management::{SessionId, UserManagement}; +use crate::{ResponseExt, WorkOsError, WorkOsResult}; + +/// The parameters for [`RevokeSession`]. +#[derive(Debug, Serialize)] +pub struct RevokeSessionParams<'a> { + /// The ID of the session. + pub session_id: &'a SessionId, +} + +/// An error returned from [`RevokeSession`]. +#[derive(Debug, Error)] +pub enum RevokeSessionError {} + +impl From for WorkOsError { + fn from(err: RevokeSessionError) -> Self { + Self::Operation(err) + } +} + +/// [WorkOS Docs: Revoke session](https://workos.com/docs/reference/user-management/session/revoke) +#[async_trait] +pub trait RevokeSession { + /// Revoke a session. + /// + /// [WorkOS Docs: Revoke session](https://workos.com/docs/reference/user-management/session/revoke) + /// + /// # Examples + /// + /// ``` + /// # use workos::WorkOsResult; + /// # use workos::user_management::*; + /// use workos::{ApiKey, Metadata, WorkOs}; + /// + /// # async fn run() -> WorkOsResult<(), RevokeSessionError> { + /// let workos = WorkOs::new(&ApiKey::from("sk_example_123456789")); + /// + /// workos + /// .user_management() + /// .revoke_session(&RevokeSessionParams { + /// session_id: &SessionId::from("session_01E4ZCR3C56J083X43JQXF3JK5"), + /// }) + /// .await?; + /// # Ok(()) + /// # } + /// ``` + async fn revoke_session( + &self, + params: &RevokeSessionParams<'_>, + ) -> WorkOsResult<(), RevokeSessionError>; +} + +#[async_trait] +impl RevokeSession for UserManagement<'_> { + async fn revoke_session( + &self, + params: &RevokeSessionParams<'_>, + ) -> WorkOsResult<(), RevokeSessionError> { + let url = self + .workos + .base_url() + .join("/user_management/sessions/revoke")?; + + self.workos + .client() + .post(url) + .bearer_auth(self.workos.key()) + .json(¶ms) + .send() + .await? + .handle_unauthorized_or_generic_error() + .await?; + + Ok(()) + } +} + +#[cfg(test)] +mod test { + use matches::assert_matches; + + use tokio; + + use crate::{ApiKey, WorkOs}; + + use super::*; + + #[tokio::test] + async fn it_calls_the_revoke_session_endpoint() { + let mut server = mockito::Server::new_async().await; + + let workos = WorkOs::builder(&ApiKey::from("sk_example_123456789")) + .base_url(&server.url()) + .unwrap() + .build(); + + server + .mock("POST", "/user_management/sessions/revoke") + .match_header("Authorization", "Bearer sk_example_123456789") + .match_body(r#"{"session_id":"session_01E4ZCR3C56J083X43JQXF3JK5"}"#) + .with_status(202) + .create_async() + .await; + + let result = workos + .user_management() + .revoke_session(&RevokeSessionParams { + session_id: &SessionId::from("session_01E4ZCR3C56J083X43JQXF3JK5"), + }) + .await; + + assert_matches!(result, Ok(())); + } +}