workos 5.29.0__tar.gz → 5.31.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (161) hide show
  1. {workos-5.29.0 → workos-5.31.0}/PKG-INFO +1 -1
  2. {workos-5.29.0 → workos-5.31.0}/tests/test_session.py +7 -0
  3. {workos-5.29.0 → workos-5.31.0}/tests/test_user_management.py +31 -0
  4. workos-5.31.0/tests/test_user_management_list_sessions.py +73 -0
  5. workos-5.31.0/tests/test_user_management_revoke_session.py +47 -0
  6. {workos-5.29.0 → workos-5.31.0}/workos/__about__.py +1 -1
  7. {workos-5.29.0 → workos-5.31.0}/workos/session.py +4 -0
  8. {workos-5.29.0 → workos-5.31.0}/workos/types/list_resource.py +2 -0
  9. {workos-5.29.0 → workos-5.31.0}/workos/types/user_management/__init__.py +1 -0
  10. {workos-5.29.0 → workos-5.31.0}/workos/types/user_management/authenticate_with_common.py +1 -0
  11. {workos-5.29.0 → workos-5.31.0}/workos/types/user_management/list_filters.py +4 -0
  12. {workos-5.29.0 → workos-5.31.0}/workos/types/user_management/session.py +32 -0
  13. {workos-5.29.0 → workos-5.31.0}/workos/user_management.py +125 -0
  14. {workos-5.29.0 → workos-5.31.0}/workos.egg-info/PKG-INFO +1 -1
  15. {workos-5.29.0 → workos-5.31.0}/workos.egg-info/SOURCES.txt +2 -0
  16. {workos-5.29.0 → workos-5.31.0}/LICENSE +0 -0
  17. {workos-5.29.0 → workos-5.31.0}/README.md +0 -0
  18. {workos-5.29.0 → workos-5.31.0}/setup.cfg +0 -0
  19. {workos-5.29.0 → workos-5.31.0}/setup.py +0 -0
  20. {workos-5.29.0 → workos-5.31.0}/tests/test_async_http_client.py +0 -0
  21. {workos-5.29.0 → workos-5.31.0}/tests/test_audit_logs.py +0 -0
  22. {workos-5.29.0 → workos-5.31.0}/tests/test_client.py +0 -0
  23. {workos-5.29.0 → workos-5.31.0}/tests/test_directory_sync.py +0 -0
  24. {workos-5.29.0 → workos-5.31.0}/tests/test_events.py +0 -0
  25. {workos-5.29.0 → workos-5.31.0}/tests/test_fga.py +0 -0
  26. {workos-5.29.0 → workos-5.31.0}/tests/test_mfa.py +0 -0
  27. {workos-5.29.0 → workos-5.31.0}/tests/test_organization_domains.py +0 -0
  28. {workos-5.29.0 → workos-5.31.0}/tests/test_organizations.py +0 -0
  29. {workos-5.29.0 → workos-5.31.0}/tests/test_passwordless.py +0 -0
  30. {workos-5.29.0 → workos-5.31.0}/tests/test_portal.py +0 -0
  31. {workos-5.29.0 → workos-5.31.0}/tests/test_sso.py +0 -0
  32. {workos-5.29.0 → workos-5.31.0}/tests/test_sync_http_client.py +0 -0
  33. {workos-5.29.0 → workos-5.31.0}/tests/test_vault.py +0 -0
  34. {workos-5.29.0 → workos-5.31.0}/tests/test_webhooks.py +0 -0
  35. {workos-5.29.0 → workos-5.31.0}/tests/test_widgets.py +0 -0
  36. {workos-5.29.0 → workos-5.31.0}/workos/__init__.py +0 -0
  37. {workos-5.29.0 → workos-5.31.0}/workos/_base_client.py +0 -0
  38. {workos-5.29.0 → workos-5.31.0}/workos/_client_configuration.py +0 -0
  39. {workos-5.29.0 → workos-5.31.0}/workos/async_client.py +0 -0
  40. {workos-5.29.0 → workos-5.31.0}/workos/audit_logs.py +0 -0
  41. {workos-5.29.0 → workos-5.31.0}/workos/client.py +0 -0
  42. {workos-5.29.0 → workos-5.31.0}/workos/directory_sync.py +0 -0
  43. {workos-5.29.0 → workos-5.31.0}/workos/events.py +0 -0
  44. {workos-5.29.0 → workos-5.31.0}/workos/exceptions.py +0 -0
  45. {workos-5.29.0 → workos-5.31.0}/workos/fga.py +0 -0
  46. {workos-5.29.0 → workos-5.31.0}/workos/mfa.py +0 -0
  47. {workos-5.29.0 → workos-5.31.0}/workos/organization_domains.py +0 -0
  48. {workos-5.29.0 → workos-5.31.0}/workos/organizations.py +0 -0
  49. {workos-5.29.0 → workos-5.31.0}/workos/passwordless.py +0 -0
  50. {workos-5.29.0 → workos-5.31.0}/workos/portal.py +0 -0
  51. {workos-5.29.0 → workos-5.31.0}/workos/py.typed +0 -0
  52. {workos-5.29.0 → workos-5.31.0}/workos/sso.py +0 -0
  53. {workos-5.29.0 → workos-5.31.0}/workos/types/__init__.py +0 -0
  54. {workos-5.29.0 → workos-5.31.0}/workos/types/audit_logs/__init__.py +0 -0
  55. {workos-5.29.0 → workos-5.31.0}/workos/types/audit_logs/audit_log_event.py +0 -0
  56. {workos-5.29.0 → workos-5.31.0}/workos/types/audit_logs/audit_log_event_actor.py +0 -0
  57. {workos-5.29.0 → workos-5.31.0}/workos/types/audit_logs/audit_log_event_context.py +0 -0
  58. {workos-5.29.0 → workos-5.31.0}/workos/types/audit_logs/audit_log_event_target.py +0 -0
  59. {workos-5.29.0 → workos-5.31.0}/workos/types/audit_logs/audit_log_export.py +0 -0
  60. {workos-5.29.0 → workos-5.31.0}/workos/types/audit_logs/audit_log_metadata.py +0 -0
  61. {workos-5.29.0 → workos-5.31.0}/workos/types/directory_sync/__init__.py +0 -0
  62. {workos-5.29.0 → workos-5.31.0}/workos/types/directory_sync/directory.py +0 -0
  63. {workos-5.29.0 → workos-5.31.0}/workos/types/directory_sync/directory_group.py +0 -0
  64. {workos-5.29.0 → workos-5.31.0}/workos/types/directory_sync/directory_state.py +0 -0
  65. {workos-5.29.0 → workos-5.31.0}/workos/types/directory_sync/directory_type.py +0 -0
  66. {workos-5.29.0 → workos-5.31.0}/workos/types/directory_sync/directory_user.py +0 -0
  67. {workos-5.29.0 → workos-5.31.0}/workos/types/directory_sync/list_filters.py +0 -0
  68. {workos-5.29.0 → workos-5.31.0}/workos/types/events/__init__.py +0 -0
  69. {workos-5.29.0 → workos-5.31.0}/workos/types/events/authentication_payload.py +0 -0
  70. {workos-5.29.0 → workos-5.31.0}/workos/types/events/connection_payload_with_legacy_fields.py +0 -0
  71. {workos-5.29.0 → workos-5.31.0}/workos/types/events/directory_group_membership_payload.py +0 -0
  72. {workos-5.29.0 → workos-5.31.0}/workos/types/events/directory_group_with_previous_attributes.py +0 -0
  73. {workos-5.29.0 → workos-5.31.0}/workos/types/events/directory_payload.py +0 -0
  74. {workos-5.29.0 → workos-5.31.0}/workos/types/events/directory_payload_with_legacy_fields.py +0 -0
  75. {workos-5.29.0 → workos-5.31.0}/workos/types/events/directory_user_with_previous_attributes.py +0 -0
  76. {workos-5.29.0 → workos-5.31.0}/workos/types/events/event.py +0 -0
  77. {workos-5.29.0 → workos-5.31.0}/workos/types/events/event_model.py +0 -0
  78. {workos-5.29.0 → workos-5.31.0}/workos/types/events/event_type.py +0 -0
  79. {workos-5.29.0 → workos-5.31.0}/workos/types/events/list_filters.py +0 -0
  80. {workos-5.29.0 → workos-5.31.0}/workos/types/events/organization_domain_verification_failed_payload.py +0 -0
  81. {workos-5.29.0 → workos-5.31.0}/workos/types/events/previous_attributes.py +0 -0
  82. {workos-5.29.0 → workos-5.31.0}/workos/types/events/session_created_payload.py +0 -0
  83. {workos-5.29.0 → workos-5.31.0}/workos/types/feature_flags/__init__.py +0 -0
  84. {workos-5.29.0 → workos-5.31.0}/workos/types/feature_flags/feature_flag.py +0 -0
  85. {workos-5.29.0 → workos-5.31.0}/workos/types/feature_flags/list_filters.py +0 -0
  86. {workos-5.29.0 → workos-5.31.0}/workos/types/fga/__init__.py +0 -0
  87. {workos-5.29.0 → workos-5.31.0}/workos/types/fga/authorization_resource_types.py +0 -0
  88. {workos-5.29.0 → workos-5.31.0}/workos/types/fga/authorization_resources.py +0 -0
  89. {workos-5.29.0 → workos-5.31.0}/workos/types/fga/check.py +0 -0
  90. {workos-5.29.0 → workos-5.31.0}/workos/types/fga/list_filters.py +0 -0
  91. {workos-5.29.0 → workos-5.31.0}/workos/types/fga/warnings.py +0 -0
  92. {workos-5.29.0 → workos-5.31.0}/workos/types/fga/warrant.py +0 -0
  93. {workos-5.29.0 → workos-5.31.0}/workos/types/metadata.py +0 -0
  94. {workos-5.29.0 → workos-5.31.0}/workos/types/mfa/__init__.py +0 -0
  95. {workos-5.29.0 → workos-5.31.0}/workos/types/mfa/authentication_challenge.py +0 -0
  96. {workos-5.29.0 → workos-5.31.0}/workos/types/mfa/authentication_challenge_verification_response.py +0 -0
  97. {workos-5.29.0 → workos-5.31.0}/workos/types/mfa/authentication_factor.py +0 -0
  98. {workos-5.29.0 → workos-5.31.0}/workos/types/mfa/authentication_factor_totp_and_challenge_response.py +0 -0
  99. {workos-5.29.0 → workos-5.31.0}/workos/types/mfa/enroll_authentication_factor_type.py +0 -0
  100. {workos-5.29.0 → workos-5.31.0}/workos/types/organization_domains/__init__.py +0 -0
  101. {workos-5.29.0 → workos-5.31.0}/workos/types/organization_domains/organization_domain.py +0 -0
  102. {workos-5.29.0 → workos-5.31.0}/workos/types/organizations/__init__.py +0 -0
  103. {workos-5.29.0 → workos-5.31.0}/workos/types/organizations/domain_data_input.py +0 -0
  104. {workos-5.29.0 → workos-5.31.0}/workos/types/organizations/list_filters.py +0 -0
  105. {workos-5.29.0 → workos-5.31.0}/workos/types/organizations/organization.py +0 -0
  106. {workos-5.29.0 → workos-5.31.0}/workos/types/organizations/organization_common.py +0 -0
  107. {workos-5.29.0 → workos-5.31.0}/workos/types/passwordless/__init__.py +0 -0
  108. {workos-5.29.0 → workos-5.31.0}/workos/types/passwordless/passwordless_session.py +0 -0
  109. {workos-5.29.0 → workos-5.31.0}/workos/types/passwordless/passwordless_session_type.py +0 -0
  110. {workos-5.29.0 → workos-5.31.0}/workos/types/portal/__init__.py +0 -0
  111. {workos-5.29.0 → workos-5.31.0}/workos/types/portal/portal_link.py +0 -0
  112. {workos-5.29.0 → workos-5.31.0}/workos/types/portal/portal_link_intent.py +0 -0
  113. {workos-5.29.0 → workos-5.31.0}/workos/types/portal/portal_link_intent_options.py +0 -0
  114. {workos-5.29.0 → workos-5.31.0}/workos/types/roles/__init__.py +0 -0
  115. {workos-5.29.0 → workos-5.31.0}/workos/types/roles/role.py +0 -0
  116. {workos-5.29.0 → workos-5.31.0}/workos/types/sso/__init__.py +0 -0
  117. {workos-5.29.0 → workos-5.31.0}/workos/types/sso/connection.py +0 -0
  118. {workos-5.29.0 → workos-5.31.0}/workos/types/sso/connection_domain.py +0 -0
  119. {workos-5.29.0 → workos-5.31.0}/workos/types/sso/profile.py +0 -0
  120. {workos-5.29.0 → workos-5.31.0}/workos/types/sso/sso_provider_type.py +0 -0
  121. {workos-5.29.0 → workos-5.31.0}/workos/types/user_management/authentication_response.py +0 -0
  122. {workos-5.29.0 → workos-5.31.0}/workos/types/user_management/email_verification.py +0 -0
  123. {workos-5.29.0 → workos-5.31.0}/workos/types/user_management/impersonator.py +0 -0
  124. {workos-5.29.0 → workos-5.31.0}/workos/types/user_management/invitation.py +0 -0
  125. {workos-5.29.0 → workos-5.31.0}/workos/types/user_management/magic_auth.py +0 -0
  126. {workos-5.29.0 → workos-5.31.0}/workos/types/user_management/oauth_tokens.py +0 -0
  127. {workos-5.29.0 → workos-5.31.0}/workos/types/user_management/organization_membership.py +0 -0
  128. {workos-5.29.0 → workos-5.31.0}/workos/types/user_management/password_hash_type.py +0 -0
  129. {workos-5.29.0 → workos-5.31.0}/workos/types/user_management/password_reset.py +0 -0
  130. {workos-5.29.0 → workos-5.31.0}/workos/types/user_management/screen_hint.py +0 -0
  131. {workos-5.29.0 → workos-5.31.0}/workos/types/user_management/user.py +0 -0
  132. {workos-5.29.0 → workos-5.31.0}/workos/types/user_management/user_management_provider_type.py +0 -0
  133. {workos-5.29.0 → workos-5.31.0}/workos/types/vault/__init__.py +0 -0
  134. {workos-5.29.0 → workos-5.31.0}/workos/types/vault/key.py +0 -0
  135. {workos-5.29.0 → workos-5.31.0}/workos/types/vault/object.py +0 -0
  136. {workos-5.29.0 → workos-5.31.0}/workos/types/webhooks/__init__.py +0 -0
  137. {workos-5.29.0 → workos-5.31.0}/workos/types/webhooks/webhook.py +0 -0
  138. {workos-5.29.0 → workos-5.31.0}/workos/types/webhooks/webhook_model.py +0 -0
  139. {workos-5.29.0 → workos-5.31.0}/workos/types/webhooks/webhook_payload.py +0 -0
  140. {workos-5.29.0 → workos-5.31.0}/workos/types/widgets/__init__.py +0 -0
  141. {workos-5.29.0 → workos-5.31.0}/workos/types/widgets/widget_scope.py +0 -0
  142. {workos-5.29.0 → workos-5.31.0}/workos/types/widgets/widget_token_response.py +0 -0
  143. {workos-5.29.0 → workos-5.31.0}/workos/types/workos_model.py +0 -0
  144. {workos-5.29.0 → workos-5.31.0}/workos/typing/__init__.py +0 -0
  145. {workos-5.29.0 → workos-5.31.0}/workos/typing/literals.py +0 -0
  146. {workos-5.29.0 → workos-5.31.0}/workos/typing/sync_or_async.py +0 -0
  147. {workos-5.29.0 → workos-5.31.0}/workos/typing/untyped_literal.py +0 -0
  148. {workos-5.29.0 → workos-5.31.0}/workos/typing/webhooks.py +0 -0
  149. {workos-5.29.0 → workos-5.31.0}/workos/utils/__init__.py +0 -0
  150. {workos-5.29.0 → workos-5.31.0}/workos/utils/_base_http_client.py +0 -0
  151. {workos-5.29.0 → workos-5.31.0}/workos/utils/crypto_provider.py +0 -0
  152. {workos-5.29.0 → workos-5.31.0}/workos/utils/http_client.py +0 -0
  153. {workos-5.29.0 → workos-5.31.0}/workos/utils/pagination_order.py +0 -0
  154. {workos-5.29.0 → workos-5.31.0}/workos/utils/request_helper.py +0 -0
  155. {workos-5.29.0 → workos-5.31.0}/workos/vault.py +0 -0
  156. {workos-5.29.0 → workos-5.31.0}/workos/webhooks.py +0 -0
  157. {workos-5.29.0 → workos-5.31.0}/workos/widgets.py +0 -0
  158. {workos-5.29.0 → workos-5.31.0}/workos.egg-info/dependency_links.txt +0 -0
  159. {workos-5.29.0 → workos-5.31.0}/workos.egg-info/not-zip-safe +0 -0
  160. {workos-5.29.0 → workos-5.31.0}/workos.egg-info/requires.txt +0 -0
  161. {workos-5.29.0 → workos-5.31.0}/workos.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: workos
3
- Version: 5.29.0
3
+ Version: 5.31.0
4
4
  Summary: WorkOS Python Client
5
5
  Home-page: https://github.com/workos-inc/workos-python
6
6
  Author: WorkOS
@@ -51,6 +51,7 @@ class SessionFixtures:
51
51
  "roles": ["admin"],
52
52
  "permissions": ["read"],
53
53
  "entitlements": ["feature_1"],
54
+ "feature_flags": ["flag1", "flag2"],
54
55
  "exp": int(current_datetime.timestamp()) + 3600,
55
56
  "iat": int(current_datetime.timestamp()),
56
57
  }
@@ -244,6 +245,7 @@ class TestSessionBase(SessionFixtures):
244
245
  "roles": ["admin"],
245
246
  "permissions": ["read"],
246
247
  "entitlements": ["feature_1"],
248
+ "feature_flags": ["flag1", "flag2"],
247
249
  }
248
250
 
249
251
  with patch.object(Session, "unseal_data", return_value=mock_session), patch(
@@ -263,6 +265,7 @@ class TestSessionBase(SessionFixtures):
263
265
  assert response.roles == ["admin"]
264
266
  assert response.permissions == ["read"]
265
267
  assert response.entitlements == ["feature_1"]
268
+ assert response.feature_flags == ["flag1", "flag2"]
266
269
  assert response.user.id == session_constants["USER_ID"]
267
270
  assert response.impersonator is None
268
271
 
@@ -312,6 +315,7 @@ class TestSessionBase(SessionFixtures):
312
315
  "roles": ["admin", "member"],
313
316
  "permissions": ["read", "write"],
314
317
  "entitlements": ["feature_1"],
318
+ "feature_flags": ["flag1", "flag2"],
315
319
  }
316
320
 
317
321
  with patch.object(Session, "unseal_data", return_value=mock_session), patch(
@@ -331,6 +335,7 @@ class TestSessionBase(SessionFixtures):
331
335
  assert response.roles == ["admin", "member"]
332
336
  assert response.permissions == ["read", "write"]
333
337
  assert response.entitlements == ["feature_1"]
338
+ assert response.feature_flags == ["flag1", "flag2"]
334
339
  assert response.user.id == session_constants["USER_ID"]
335
340
  assert response.impersonator is None
336
341
 
@@ -410,6 +415,7 @@ class TestSession(SessionFixtures):
410
415
  "roles": ["admin"],
411
416
  "permissions": ["read"],
412
417
  "entitlements": ["feature_1"],
418
+ "feature_flags": ["flag1", "flag2"],
413
419
  },
414
420
  ):
415
421
  response = session.refresh()
@@ -511,6 +517,7 @@ class TestAsyncSession(SessionFixtures):
511
517
  "roles": ["admin"],
512
518
  "permissions": ["read"],
513
519
  "entitlements": ["feature_1"],
520
+ "feature_flags": ["flag1", "flag2"],
514
521
  },
515
522
  ):
516
523
  response = await session.refresh()
@@ -718,6 +718,37 @@ class TestUserManagement(UserManagementFixtures):
718
718
  "grant_type": "authorization_code",
719
719
  }
720
720
 
721
+ def test_authenticate_with_code_with_invitation_token(
722
+ self,
723
+ capture_and_mock_http_client_request,
724
+ mock_auth_response,
725
+ base_authentication_params,
726
+ ):
727
+ params = {
728
+ "code": "test_code",
729
+ "code_verifier": "test_code_verifier",
730
+ "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36",
731
+ "ip_address": "192.0.0.1",
732
+ "invitation_token": "invitation_token_12345",
733
+ }
734
+ request_kwargs = capture_and_mock_http_client_request(
735
+ self.http_client, mock_auth_response, 200
736
+ )
737
+
738
+ response = syncify(self.user_management.authenticate_with_code(**params))
739
+
740
+ assert request_kwargs["url"].endswith("user_management/authenticate")
741
+ assert request_kwargs["method"] == "post"
742
+ assert response.user.id == "user_01H7ZGXFP5C6BBQY6Z7277ZCT0"
743
+ assert response.organization_id == "org_12345"
744
+ assert response.access_token == "access_token_12345"
745
+ assert response.refresh_token == "refresh_token_12345"
746
+ assert request_kwargs["json"] == {
747
+ **params,
748
+ **base_authentication_params,
749
+ "grant_type": "authorization_code",
750
+ }
751
+
721
752
  def test_authenticate_with_magic_auth(
722
753
  self,
723
754
  capture_and_mock_http_client_request,
@@ -0,0 +1,73 @@
1
+ from typing import Union
2
+
3
+ import pytest
4
+
5
+ from tests.utils.list_resource import list_response_of
6
+ from tests.utils.syncify import syncify
7
+ from tests.types.test_auto_pagination_function import TestAutoPaginationFunction
8
+ from workos.user_management import AsyncUserManagement, UserManagement
9
+
10
+
11
+ def _mock_session(id: str):
12
+ now = "2025-07-23T14:00:00.000Z"
13
+ return {
14
+ "object": "session",
15
+ "id": id,
16
+ "user_id": "user_123",
17
+ "organization_id": "org_123",
18
+ "status": "active",
19
+ "auth_method": "password",
20
+ "impersonator": None,
21
+ "ip_address": "192.168.1.1",
22
+ "user_agent": "Mozilla/5.0",
23
+ "expires_at": "2025-07-23T15:00:00.000Z",
24
+ "ended_at": None,
25
+ "created_at": now,
26
+ "updated_at": now,
27
+ }
28
+
29
+
30
+ @pytest.mark.sync_and_async(UserManagement, AsyncUserManagement)
31
+ class TestUserManagementListSessions:
32
+ @pytest.fixture(autouse=True)
33
+ def setup(self, module_instance: Union[UserManagement, AsyncUserManagement]):
34
+ self.http_client = module_instance._http_client
35
+ self.user_management = module_instance
36
+
37
+ def test_list_sessions_query_and_parsing(
38
+ self, capture_and_mock_http_client_request
39
+ ):
40
+ sessions = [_mock_session("session_1"), _mock_session("session_2")]
41
+ response = list_response_of(data=sessions)
42
+ request_kwargs = capture_and_mock_http_client_request(
43
+ self.http_client, response, 200
44
+ )
45
+
46
+ result = syncify(
47
+ self.user_management.list_sessions(
48
+ user_id="user_123", limit=10, before="before_id", order="desc"
49
+ )
50
+ )
51
+
52
+ assert request_kwargs["url"].endswith("user_management/users/user_123/sessions")
53
+ assert request_kwargs["method"] == "get"
54
+ assert request_kwargs["params"]["limit"] == 10
55
+ assert request_kwargs["params"]["before"] == "before_id"
56
+ assert request_kwargs["params"]["order"] == "desc"
57
+ assert "after" not in request_kwargs["params"]
58
+ assert len(result.data) == 2
59
+ assert result.data[0].id == "session_1"
60
+ assert result.list_metadata.before is None
61
+ assert result.list_metadata.after is None
62
+
63
+ def test_list_sessions_auto_pagination(
64
+ self, test_auto_pagination: TestAutoPaginationFunction
65
+ ):
66
+ data = [_mock_session(str(i)) for i in range(40)]
67
+ test_auto_pagination(
68
+ http_client=self.http_client,
69
+ list_function=self.user_management.list_sessions,
70
+ list_function_params={"user_id": "user_123"},
71
+ expected_all_page_data=data,
72
+ url_path_keys=["user_id"],
73
+ )
@@ -0,0 +1,47 @@
1
+ from typing import Union
2
+
3
+ import pytest
4
+
5
+ from tests.utils.syncify import syncify
6
+ from workos.user_management import AsyncUserManagement, UserManagement
7
+
8
+
9
+ def _mock_session(id: str):
10
+ now = "2025-07-23T14:00:00.000Z"
11
+ return {
12
+ "object": "session",
13
+ "id": id,
14
+ "user_id": "user_123",
15
+ "organization_id": "org_123",
16
+ "status": "revoked",
17
+ "auth_method": "password",
18
+ "ip_address": "192.168.1.1",
19
+ "user_agent": "Mozilla/5.0",
20
+ "expires_at": "2025-07-23T15:00:00.000Z",
21
+ "ended_at": now,
22
+ "created_at": now,
23
+ "updated_at": now,
24
+ }
25
+
26
+
27
+ @pytest.mark.sync_and_async(UserManagement, AsyncUserManagement)
28
+ class TestUserManagementRevokeSession:
29
+ @pytest.fixture(autouse=True)
30
+ def setup(self, module_instance: Union[UserManagement, AsyncUserManagement]):
31
+ self.http_client = module_instance._http_client
32
+ self.user_management = module_instance
33
+
34
+ def test_revoke_session(self, capture_and_mock_http_client_request):
35
+ mock = _mock_session("session_abc")
36
+ request_kwargs = capture_and_mock_http_client_request(
37
+ self.http_client, mock, 200
38
+ )
39
+
40
+ response = syncify(
41
+ self.user_management.revoke_session(session_id="session_abc")
42
+ )
43
+
44
+ assert request_kwargs["url"].endswith("user_management/sessions/revoke")
45
+ assert request_kwargs["method"] == "post"
46
+ assert request_kwargs["json"] == {"session_id": "session_abc"}
47
+ assert response.id == "session_abc"
@@ -12,7 +12,7 @@ __package_name__ = "workos"
12
12
 
13
13
  __package_url__ = "https://github.com/workos-inc/workos-python"
14
14
 
15
- __version__ = "5.29.0"
15
+ __version__ = "5.31.0"
16
16
 
17
17
  __author__ = "WorkOS"
18
18
 
@@ -4,6 +4,7 @@ from typing import TYPE_CHECKING, List, Protocol
4
4
  from functools import lru_cache
5
5
  import json
6
6
  from typing import Any, Dict, Optional, Union, cast
7
+
7
8
  import jwt
8
9
  from jwt import PyJWKClient
9
10
  from cryptography.fernet import Fernet
@@ -107,6 +108,7 @@ class SessionModule(Protocol):
107
108
  entitlements=decoded.get("entitlements", None),
108
109
  user=session["user"],
109
110
  impersonator=session.get("impersonator", None),
111
+ feature_flags=decoded.get("feature_flags", None),
110
112
  )
111
113
 
112
114
  def refresh(
@@ -235,6 +237,7 @@ class Session(SessionModule):
235
237
  entitlements=decoded.get("entitlements", None),
236
238
  user=auth_response.user,
237
239
  impersonator=auth_response.impersonator,
240
+ feature_flags=decoded.get("feature_flags", None),
238
241
  )
239
242
  except Exception as e:
240
243
  return RefreshWithSessionCookieErrorResponse(
@@ -326,6 +329,7 @@ class AsyncSession(SessionModule):
326
329
  entitlements=decoded.get("entitlements", None),
327
330
  user=auth_response.user,
328
331
  impersonator=auth_response.impersonator,
332
+ feature_flags=decoded.get("feature_flags", None),
329
333
  )
330
334
  except Exception as e:
331
335
  return RefreshWithSessionCookieErrorResponse(
@@ -34,6 +34,7 @@ from workos.types.mfa import AuthenticationFactor
34
34
  from workos.types.organizations import Organization
35
35
  from workos.types.sso import ConnectionWithDomains
36
36
  from workos.types.user_management import Invitation, OrganizationMembership, User
37
+ from workos.types.user_management.session import Session as UserManagementSession
37
38
  from workos.types.vault import ObjectDigest
38
39
  from workos.types.workos_model import WorkOSModel
39
40
  from workos.utils.request_helper import DEFAULT_LIST_RESPONSE_LIMIT
@@ -54,6 +55,7 @@ ListableResource = TypeVar(
54
55
  AuthorizationResource,
55
56
  AuthorizationResourceType,
56
57
  User,
58
+ UserManagementSession,
57
59
  ObjectDigest,
58
60
  Warrant,
59
61
  WarrantQueryResult,
@@ -9,3 +9,4 @@ from .password_hash_type import *
9
9
  from .password_reset import *
10
10
  from .user_management_provider_type import *
11
11
  from .user import *
12
+ from .session import *
@@ -19,6 +19,7 @@ class AuthenticateWithCodeParameters(AuthenticateWithBaseParameters):
19
19
  code_verifier: Union[str, None]
20
20
  grant_type: Literal["authorization_code"]
21
21
  session: Union[SessionConfig, None]
22
+ invitation_token: Union[str, None]
22
23
 
23
24
 
24
25
  class AuthenticateWithMagicAuthParameters(AuthenticateWithBaseParameters):
@@ -23,3 +23,7 @@ class OrganizationMembershipsListFilters(ListArgs, total=False):
23
23
 
24
24
  class AuthenticationFactorsListFilters(ListArgs, total=False):
25
25
  user_id: str
26
+
27
+
28
+ class SessionsListFilters(ListArgs, total=False):
29
+ user_id: str
@@ -24,6 +24,7 @@ class AuthenticateWithSessionCookieSuccessResponse(WorkOSModel):
24
24
  user: User
25
25
  impersonator: Optional[Impersonator] = None
26
26
  entitlements: Optional[Sequence[str]] = None
27
+ feature_flags: Optional[Sequence[str]] = None
27
28
 
28
29
 
29
30
  class AuthenticateWithSessionCookieErrorResponse(WorkOSModel):
@@ -45,3 +46,34 @@ class RefreshWithSessionCookieErrorResponse(WorkOSModel):
45
46
  class SessionConfig(TypedDict, total=False):
46
47
  seal_session: bool
47
48
  cookie_password: str
49
+
50
+
51
+ AuthMethodType = Literal[
52
+ "external_auth",
53
+ "impersonation",
54
+ "magic_code",
55
+ "migrated_session",
56
+ "oauth",
57
+ "passkey",
58
+ "password",
59
+ "sso",
60
+ "unknown",
61
+ ]
62
+
63
+
64
+ class Session(WorkOSModel):
65
+ """Representation of a WorkOS User Management Session."""
66
+
67
+ object: Literal["session"]
68
+ id: str
69
+ user_id: str
70
+ organization_id: Optional[str] = None
71
+ status: str
72
+ auth_method: AuthMethodType
73
+ impersonator: Optional[Impersonator] = None
74
+ ip_address: Optional[str] = None
75
+ user_agent: Optional[str] = None
76
+ expires_at: str
77
+ ended_at: Optional[str] = None
78
+ created_at: str
79
+ updated_at: str
@@ -48,6 +48,7 @@ from workos.types.user_management.list_filters import (
48
48
  from workos.types.user_management.password_hash_type import PasswordHashType
49
49
  from workos.types.user_management.screen_hint import ScreenHintType
50
50
  from workos.types.user_management.session import SessionConfig
51
+ from workos.types.user_management.session import Session as UserManagementSession
51
52
  from workos.types.user_management.user_management_provider_type import (
52
53
  UserManagementProviderType,
53
54
  )
@@ -86,6 +87,8 @@ MAGIC_AUTH_DETAIL_PATH = "user_management/magic_auth/{0}"
86
87
  MAGIC_AUTH_PATH = "user_management/magic_auth"
87
88
  USER_SEND_MAGIC_AUTH_PATH = "user_management/magic_auth/send"
88
89
  USER_AUTH_FACTORS_PATH = "user_management/users/{0}/auth_factors"
90
+ USER_SESSIONS_PATH = "user_management/users/{0}/sessions"
91
+ SESSIONS_REVOKE_PATH = "user_management/sessions/revoke"
89
92
  EMAIL_VERIFICATION_DETAIL_PATH = "user_management/email_verification/{0}"
90
93
  INVITATION_PATH = "user_management/invitations"
91
94
  INVITATION_DETAIL_PATH = "user_management/invitations/{0}"
@@ -109,6 +112,12 @@ InvitationsListResource = WorkOSListResource[
109
112
  Invitation, InvitationsListFilters, ListMetadata
110
113
  ]
111
114
 
115
+ from workos.types.user_management.list_filters import SessionsListFilters
116
+
117
+ SessionsListResource = WorkOSListResource[
118
+ UserManagementSession, SessionsListFilters, ListMetadata
119
+ ]
120
+
112
121
 
113
122
  class UserManagementModule(Protocol):
114
123
  """Offers methods for using the WorkOS User Management API."""
@@ -488,6 +497,7 @@ class UserManagementModule(Protocol):
488
497
  code_verifier: Optional[str] = None,
489
498
  ip_address: Optional[str] = None,
490
499
  user_agent: Optional[str] = None,
500
+ invitation_token: Optional[str] = None,
491
501
  ) -> SyncOrAsync[AuthenticationResponse]:
492
502
  """Authenticates an OAuth user or a user that is logging in through SSO.
493
503
 
@@ -498,6 +508,7 @@ class UserManagementModule(Protocol):
498
508
  url as part of the PKCE flow. This parameter is required when the client secret is not present. (Optional)
499
509
  ip_address (str): The IP address of the request from the user who is attempting to authenticate. (Optional)
500
510
  user_agent (str): The user agent of the request from the user who is attempting to authenticate. (Optional)
511
+ invitation_token (str): The token of an Invitation, if required. (Optional)
501
512
 
502
513
  Returns:
503
514
  AuthenticationResponse: Authentication response from WorkOS.
@@ -718,6 +729,20 @@ class UserManagementModule(Protocol):
718
729
  """
719
730
  ...
720
731
 
732
+ def list_sessions(
733
+ self,
734
+ *,
735
+ user_id: str,
736
+ limit: Optional[int] = None,
737
+ before: Optional[str] = None,
738
+ after: Optional[str] = None,
739
+ order: Optional[PaginationOrder] = "desc",
740
+ ) -> SyncOrAsync["SessionsListResource"]: ...
741
+
742
+ def revoke_session(
743
+ self, *, session_id: str
744
+ ) -> SyncOrAsync[UserManagementSession]: ...
745
+
721
746
  def get_magic_auth(self, magic_auth_id: str) -> SyncOrAsync[MagicAuth]:
722
747
  """Get the details of a Magic Auth object.
723
748
 
@@ -1166,6 +1191,7 @@ class UserManagement(UserManagementModule):
1166
1191
  code_verifier: Optional[str] = None,
1167
1192
  ip_address: Optional[str] = None,
1168
1193
  user_agent: Optional[str] = None,
1194
+ invitation_token: Optional[str] = None,
1169
1195
  ) -> AuthKitAuthenticationResponse:
1170
1196
  if (
1171
1197
  session is not None
@@ -1181,6 +1207,7 @@ class UserManagement(UserManagementModule):
1181
1207
  "user_agent": user_agent,
1182
1208
  "code_verifier": code_verifier,
1183
1209
  "session": session,
1210
+ "invitation_token": invitation_token,
1184
1211
  }
1185
1212
 
1186
1213
  return self._authenticate_with(
@@ -1373,6 +1400,54 @@ class UserManagement(UserManagementModule):
1373
1400
 
1374
1401
  return MagicAuth.model_validate(response)
1375
1402
 
1403
+ def list_sessions(
1404
+ self,
1405
+ *,
1406
+ user_id: str,
1407
+ limit: Optional[int] = DEFAULT_LIST_RESPONSE_LIMIT,
1408
+ before: Optional[str] = None,
1409
+ after: Optional[str] = None,
1410
+ order: Optional[PaginationOrder] = "desc",
1411
+ ) -> "SessionsListResource":
1412
+ limit_value: int = limit if limit is not None else DEFAULT_LIST_RESPONSE_LIMIT
1413
+
1414
+ params: ListArgs = {
1415
+ "limit": limit_value,
1416
+ "before": before,
1417
+ "after": after,
1418
+ "order": order,
1419
+ }
1420
+
1421
+ response = self._http_client.request(
1422
+ USER_SESSIONS_PATH.format(user_id),
1423
+ method=REQUEST_METHOD_GET,
1424
+ params=params,
1425
+ )
1426
+
1427
+ list_args: SessionsListFilters = {
1428
+ "limit": limit_value,
1429
+ "before": before,
1430
+ "after": after,
1431
+ "user_id": user_id,
1432
+ }
1433
+ if order is not None:
1434
+ list_args["order"] = order
1435
+
1436
+ return SessionsListResource(
1437
+ list_method=self.list_sessions,
1438
+ list_args=list_args,
1439
+ **ListPage[UserManagementSession](**response).model_dump(),
1440
+ )
1441
+
1442
+ def revoke_session(self, *, session_id: str) -> UserManagementSession:
1443
+ json = {"session_id": session_id}
1444
+
1445
+ response = self._http_client.request(
1446
+ SESSIONS_REVOKE_PATH, method=REQUEST_METHOD_POST, json=json
1447
+ )
1448
+
1449
+ return UserManagementSession.model_validate(response)
1450
+
1376
1451
  def enroll_auth_factor(
1377
1452
  self,
1378
1453
  *,
@@ -1807,6 +1882,7 @@ class AsyncUserManagement(UserManagementModule):
1807
1882
  code_verifier: Optional[str] = None,
1808
1883
  ip_address: Optional[str] = None,
1809
1884
  user_agent: Optional[str] = None,
1885
+ invitation_token: Optional[str] = None,
1810
1886
  ) -> AuthKitAuthenticationResponse:
1811
1887
  if (
1812
1888
  session is not None
@@ -1822,6 +1898,7 @@ class AsyncUserManagement(UserManagementModule):
1822
1898
  "user_agent": user_agent,
1823
1899
  "code_verifier": code_verifier,
1824
1900
  "session": session,
1901
+ "invitation_token": invitation_token,
1825
1902
  }
1826
1903
 
1827
1904
  return await self._authenticate_with(
@@ -2027,6 +2104,54 @@ class AsyncUserManagement(UserManagementModule):
2027
2104
 
2028
2105
  return MagicAuth.model_validate(response)
2029
2106
 
2107
+ async def list_sessions(
2108
+ self,
2109
+ *,
2110
+ user_id: str,
2111
+ limit: Optional[int] = DEFAULT_LIST_RESPONSE_LIMIT,
2112
+ before: Optional[str] = None,
2113
+ after: Optional[str] = None,
2114
+ order: Optional[PaginationOrder] = "desc",
2115
+ ) -> "SessionsListResource":
2116
+ limit_value: int = limit if limit is not None else DEFAULT_LIST_RESPONSE_LIMIT
2117
+
2118
+ params: ListArgs = {
2119
+ "limit": limit_value,
2120
+ "before": before,
2121
+ "after": after,
2122
+ "order": order,
2123
+ }
2124
+
2125
+ response = await self._http_client.request(
2126
+ USER_SESSIONS_PATH.format(user_id),
2127
+ method=REQUEST_METHOD_GET,
2128
+ params=params,
2129
+ )
2130
+
2131
+ list_args: SessionsListFilters = {
2132
+ "limit": limit_value,
2133
+ "before": before,
2134
+ "after": after,
2135
+ "user_id": user_id,
2136
+ }
2137
+ if order is not None:
2138
+ list_args["order"] = order
2139
+
2140
+ return SessionsListResource(
2141
+ list_method=self.list_sessions,
2142
+ list_args=list_args,
2143
+ **ListPage[UserManagementSession](**response).model_dump(),
2144
+ )
2145
+
2146
+ async def revoke_session(self, *, session_id: str) -> UserManagementSession:
2147
+ json = {"session_id": session_id}
2148
+
2149
+ response = await self._http_client.request(
2150
+ SESSIONS_REVOKE_PATH, method=REQUEST_METHOD_POST, json=json
2151
+ )
2152
+
2153
+ return UserManagementSession.model_validate(response)
2154
+
2030
2155
  async def enroll_auth_factor(
2031
2156
  self,
2032
2157
  *,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: workos
3
- Version: 5.29.0
3
+ Version: 5.31.0
4
4
  Summary: WorkOS Python Client
5
5
  Home-page: https://github.com/workos-inc/workos-python
6
6
  Author: WorkOS
@@ -16,6 +16,8 @@ tests/test_session.py
16
16
  tests/test_sso.py
17
17
  tests/test_sync_http_client.py
18
18
  tests/test_user_management.py
19
+ tests/test_user_management_list_sessions.py
20
+ tests/test_user_management_revoke_session.py
19
21
  tests/test_vault.py
20
22
  tests/test_webhooks.py
21
23
  tests/test_widgets.py
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes