From cb7c35e7e8b303e06dd867f6100bb0ded712be96 Mon Sep 17 00:00:00 2001 From: vivekkhimani Date: Thu, 12 Mar 2026 00:51:30 -0700 Subject: [PATCH 1/4] vault events --- src/workos/types/events/__init__.py | 1 + src/workos/types/events/event.py | 54 +++++++++ src/workos/types/events/event_model.py | 16 +++ src/workos/types/events/event_type.py | 9 ++ src/workos/types/events/vault_payload.py | 57 ++++++++++ tests/test_events.py | 135 +++++++++++++++++++++++ 6 files changed, 272 insertions(+) create mode 100644 src/workos/types/events/vault_payload.py diff --git a/src/workos/types/events/__init__.py b/src/workos/types/events/__init__.py index 004d6077..f403d9a1 100644 --- a/src/workos/types/events/__init__.py +++ b/src/workos/types/events/__init__.py @@ -13,3 +13,4 @@ from .organization_domain_verification_failed_payload import * from .previous_attributes import * from .session_payload import * +from .vault_payload import * diff --git a/src/workos/types/events/event.py b/src/workos/types/events/event.py index 028e08f3..35f8512e 100644 --- a/src/workos/types/events/event.py +++ b/src/workos/types/events/event.py @@ -53,6 +53,15 @@ SessionCreatedPayload, SessionRevokedPayload, ) +from workos.types.events.vault_payload import ( + VaultDataCreatedPayload, + VaultDataDeletedPayload, + VaultDataReadPayload, + VaultDekDecryptedPayload, + VaultDekReadPayload, + VaultKekCreatedPayload, + VaultNamesListedPayload, +) from workos.types.organizations.organization_common import OrganizationCommon from workos.types.organization_domains import OrganizationDomain from workos.types.roles.role import EventRole @@ -372,6 +381,42 @@ class UserUpdatedEvent(EventModel[User]): event: Literal["user.updated"] +class VaultDataCreatedEvent(EventModel[VaultDataCreatedPayload]): + event: Literal["vault.data.created"] + + +class VaultDataDeletedEvent(EventModel[VaultDataDeletedPayload]): + event: Literal["vault.data.deleted"] + + +class VaultDataReadEvent(EventModel[VaultDataReadPayload]): + event: Literal["vault.data.read"] + + +class VaultDataUpdatedEvent(EventModel[VaultDataCreatedPayload]): + event: Literal["vault.data.updated"] + + +class VaultDekDecryptedEvent(EventModel[VaultDekDecryptedPayload]): + event: Literal["vault.dek.decrypted"] + + +class VaultDekReadEvent(EventModel[VaultDekReadPayload]): + event: Literal["vault.dek.read"] + + +class VaultKekCreatedEvent(EventModel[VaultKekCreatedPayload]): + event: Literal["vault.kek.created"] + + +class VaultMetadataReadEvent(EventModel[VaultDataDeletedPayload]): + event: Literal["vault.metadata.read"] + + +class VaultNamesListedEvent(EventModel[VaultNamesListedPayload]): + event: Literal["vault.names.listed"] + + Event = Annotated[ Union[ ApiKeyCreatedEvent, @@ -443,6 +488,15 @@ class UserUpdatedEvent(EventModel[User]): UserCreatedEvent, UserDeletedEvent, UserUpdatedEvent, + VaultDataCreatedEvent, + VaultDataDeletedEvent, + VaultDataReadEvent, + VaultDataUpdatedEvent, + VaultDekDecryptedEvent, + VaultDekReadEvent, + VaultKekCreatedEvent, + VaultMetadataReadEvent, + VaultNamesListedEvent, ], Field(..., discriminator="event"), ] diff --git a/src/workos/types/events/event_model.py b/src/workos/types/events/event_model.py index 2d4fb4dc..43301891 100644 --- a/src/workos/types/events/event_model.py +++ b/src/workos/types/events/event_model.py @@ -50,6 +50,15 @@ SessionCreatedPayload, SessionRevokedPayload, ) +from workos.types.events.vault_payload import ( + VaultDataCreatedPayload, + VaultDataDeletedPayload, + VaultDataReadPayload, + VaultDekDecryptedPayload, + VaultDekReadPayload, + VaultKekCreatedPayload, + VaultNamesListedPayload, +) from workos.types.organizations.organization_common import OrganizationCommon from workos.types.organization_domains import OrganizationDomain from workos.types.authorization.organization_role import OrganizationRoleEvent @@ -110,6 +119,13 @@ SessionCreatedPayload, SessionRevokedPayload, User, + VaultDataCreatedPayload, + VaultDataDeletedPayload, + VaultDataReadPayload, + VaultDekDecryptedPayload, + VaultDekReadPayload, + VaultKekCreatedPayload, + VaultNamesListedPayload, ) diff --git a/src/workos/types/events/event_type.py b/src/workos/types/events/event_type.py index 6ccc5d57..b657158f 100644 --- a/src/workos/types/events/event_type.py +++ b/src/workos/types/events/event_type.py @@ -74,6 +74,15 @@ "user.created", "user.deleted", "user.updated", + "vault.data.created", + "vault.data.deleted", + "vault.data.read", + "vault.data.updated", + "vault.dek.decrypted", + "vault.dek.read", + "vault.kek.created", + "vault.metadata.read", + "vault.names.listed", ] EventTypeDiscriminator = TypeVar("EventTypeDiscriminator", bound=EventType) diff --git a/src/workos/types/events/vault_payload.py b/src/workos/types/events/vault_payload.py new file mode 100644 index 00000000..abf8ffd4 --- /dev/null +++ b/src/workos/types/events/vault_payload.py @@ -0,0 +1,57 @@ +from typing import List, Optional + +from workos.types.vault.key import KeyContext +from workos.types.workos_model import WorkOSModel + + +class VaultNamesListedPayload(WorkOSModel): + actor_id: str + actor_source: str + actor_name: Optional[str] = None + + +class VaultDataDeletedPayload(WorkOSModel): + actor_id: str + actor_source: str + actor_name: Optional[str] = None + kv_name: str + + +class VaultDekDecryptedPayload(WorkOSModel): + actor_id: str + actor_source: str + actor_name: Optional[str] = None + key_id: str + + +class VaultDataReadPayload(WorkOSModel): + actor_id: str + actor_source: str + actor_name: Optional[str] = None + kv_name: str + key_id: str + + +class VaultDataCreatedPayload(WorkOSModel): + actor_id: str + actor_source: str + actor_name: Optional[str] = None + kv_name: str + key_id: str + key_context: KeyContext + + +class VaultDekReadPayload(WorkOSModel): + actor_id: str + actor_source: str + actor_name: Optional[str] = None + key_ids: List[str] + key_context: KeyContext + + +class VaultKekCreatedPayload(WorkOSModel): + actor_id: str + actor_source: str + actor_name: Optional[str] = None + key_name: str + key_id: str diff --git a/tests/test_events.py b/tests/test_events.py index 9a830557..d4cf0352 100644 --- a/tests/test_events.py +++ b/tests/test_events.py @@ -5,6 +5,11 @@ from tests.utils.syncify import syncify from workos.events import AsyncEvents, Events, EventsListResource from workos.types.events import OrganizationMembershipCreatedEvent +from workos.types.events.event import ( + VaultDataCreatedEvent, + VaultDekReadEvent, + VaultNamesListedEvent, +) @pytest.mark.sync_and_async(Events, AsyncEvents) @@ -86,3 +91,133 @@ def test_list_events_organization_membership_missing_custom_attributes( event = events.data[0] assert isinstance(event, OrganizationMembershipCreatedEvent) assert event.data.custom_attributes == {} + + def test_list_events_vault_data_created( + self, + module_instance: Union[Events, AsyncEvents], + capture_and_mock_http_client_request, + ): + mock_response = { + "object": "list", + "data": [ + { + "object": "event", + "id": "event_vault_01", + "event": "vault.data.created", + "data": { + "actor_id": "user_01234", + "actor_source": "dashboard", + "actor_name": "Test User", + "kv_name": "my-secret", + "key_id": "key_01234", + "key_context": {"env": "production"}, + }, + "created_at": "2024-01-01T00:00:00.000Z", + } + ], + "list_metadata": { + "after": None, + }, + } + + capture_and_mock_http_client_request( + http_client=module_instance._http_client, + status_code=200, + response_dict=mock_response, + ) + + events: EventsListResource = syncify( + module_instance.list_events(events=["vault.data.created"]) + ) + + event = events.data[0] + assert isinstance(event, VaultDataCreatedEvent) + assert event.data.actor_id == "user_01234" + assert event.data.actor_source == "dashboard" + assert event.data.actor_name == "Test User" + assert event.data.kv_name == "my-secret" + assert event.data.key_id == "key_01234" + assert event.data.key_context.root == {"env": "production"} + + def test_list_events_vault_dek_read( + self, + module_instance: Union[Events, AsyncEvents], + capture_and_mock_http_client_request, + ): + mock_response = { + "object": "list", + "data": [ + { + "object": "event", + "id": "event_vault_02", + "event": "vault.dek.read", + "data": { + "actor_id": "user_01234", + "actor_source": "api", + "key_ids": ["key_01", "key_02"], + "key_context": {"tenant": "acme"}, + }, + "created_at": "2024-01-01T00:00:00.000Z", + } + ], + "list_metadata": { + "after": None, + }, + } + + capture_and_mock_http_client_request( + http_client=module_instance._http_client, + status_code=200, + response_dict=mock_response, + ) + + events: EventsListResource = syncify( + module_instance.list_events(events=["vault.dek.read"]) + ) + + event = events.data[0] + assert isinstance(event, VaultDekReadEvent) + assert event.data.key_ids == ["key_01", "key_02"] + assert event.data.key_context.root == {"tenant": "acme"} + assert event.data.actor_name is None + + def test_list_events_vault_names_listed( + self, + module_instance: Union[Events, AsyncEvents], + capture_and_mock_http_client_request, + ): + mock_response = { + "object": "list", + "data": [ + { + "object": "event", + "id": "event_vault_03", + "event": "vault.names.listed", + "data": { + "actor_id": "user_01234", + "actor_source": "api", + "actor_name": "Service Account", + }, + "created_at": "2024-01-01T00:00:00.000Z", + } + ], + "list_metadata": { + "after": None, + }, + } + + capture_and_mock_http_client_request( + http_client=module_instance._http_client, + status_code=200, + response_dict=mock_response, + ) + + events: EventsListResource = syncify( + module_instance.list_events(events=["vault.names.listed"]) + ) + + event = events.data[0] + assert isinstance(event, VaultNamesListedEvent) + assert event.data.actor_id == "user_01234" + assert event.data.actor_source == "api" + assert event.data.actor_name == "Service Account" From f79783dfb487397501f4805a1d4ceea37ad2840f Mon Sep 17 00:00:00 2001 From: vivekkhimani Date: Thu, 12 Mar 2026 09:39:38 -0700 Subject: [PATCH 2/4] fixes --- src/workos/types/events/vault_payload.py | 18 +++++++++--------- tests/test_events.py | 4 +++- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/workos/types/events/vault_payload.py b/src/workos/types/events/vault_payload.py index abf8ffd4..dd052d21 100644 --- a/src/workos/types/events/vault_payload.py +++ b/src/workos/types/events/vault_payload.py @@ -7,27 +7,27 @@ class VaultNamesListedPayload(WorkOSModel): actor_id: str actor_source: str - actor_name: Optional[str] = None + actor_name: str class VaultDataDeletedPayload(WorkOSModel): actor_id: str actor_source: str - actor_name: Optional[str] = None + actor_name: str kv_name: str class VaultDekDecryptedPayload(WorkOSModel): actor_id: str actor_source: str - actor_name: Optional[str] = None + actor_name: str key_id: str class VaultDataReadPayload(WorkOSModel): actor_id: str actor_source: str - actor_name: Optional[str] = None + actor_name: str kv_name: str key_id: str @@ -35,23 +35,23 @@ class VaultDataReadPayload(WorkOSModel): class VaultDataCreatedPayload(WorkOSModel): actor_id: str actor_source: str - actor_name: Optional[str] = None + actor_name: str kv_name: str key_id: str - key_context: KeyContext + key_context: Optional[KeyContext] = None class VaultDekReadPayload(WorkOSModel): actor_id: str actor_source: str - actor_name: Optional[str] = None + actor_name: str key_ids: List[str] - key_context: KeyContext + key_context: Optional[KeyContext] = None class VaultKekCreatedPayload(WorkOSModel): actor_id: str actor_source: str - actor_name: Optional[str] = None + actor_name: str key_name: str key_id: str diff --git a/tests/test_events.py b/tests/test_events.py index d4cf0352..c153bd10 100644 --- a/tests/test_events.py +++ b/tests/test_events.py @@ -154,6 +154,7 @@ def test_list_events_vault_dek_read( "data": { "actor_id": "user_01234", "actor_source": "api", + "actor_name": "API Client", "key_ids": ["key_01", "key_02"], "key_context": {"tenant": "acme"}, }, @@ -178,8 +179,9 @@ def test_list_events_vault_dek_read( event = events.data[0] assert isinstance(event, VaultDekReadEvent) assert event.data.key_ids == ["key_01", "key_02"] + assert event.data.key_context is not None assert event.data.key_context.root == {"tenant": "acme"} - assert event.data.actor_name is None + assert event.data.actor_name == "API Client" def test_list_events_vault_names_listed( self, From da8c1e814735be352f5c0bdc5f64abc2b3d94332 Mon Sep 17 00:00:00 2001 From: vivekkhimani Date: Thu, 12 Mar 2026 10:53:10 -0700 Subject: [PATCH 3/4] fixes --- src/workos/types/organizations/organization.py | 1 - src/workos/types/organizations/organization_common.py | 3 ++- src/workos/types/roles/role.py | 2 ++ 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/workos/types/organizations/organization.py b/src/workos/types/organizations/organization.py index d7ba0aa4..e6c35922 100644 --- a/src/workos/types/organizations/organization.py +++ b/src/workos/types/organizations/organization.py @@ -9,5 +9,4 @@ class Organization(OrganizationCommon): allow_profiles_outside_organization: bool domains: Sequence[OrganizationDomain] stripe_customer_id: Optional[str] = None - external_id: Optional[str] = None metadata: Metadata = field(default_factory=dict) diff --git a/src/workos/types/organizations/organization_common.py b/src/workos/types/organizations/organization_common.py index 10a4c5c2..b3ab61e2 100644 --- a/src/workos/types/organizations/organization_common.py +++ b/src/workos/types/organizations/organization_common.py @@ -1,4 +1,4 @@ -from typing import Literal, Sequence +from typing import Literal, Optional, Sequence from workos.types.workos_model import WorkOSModel from workos.types.organization_domains import OrganizationDomain @@ -7,6 +7,7 @@ class OrganizationCommon(WorkOSModel): id: str object: Literal["organization"] name: str + external_id: Optional[str] = None domains: Sequence[OrganizationDomain] created_at: str updated_at: str diff --git a/src/workos/types/roles/role.py b/src/workos/types/roles/role.py index 7e299bd6..d65b9b62 100644 --- a/src/workos/types/roles/role.py +++ b/src/workos/types/roles/role.py @@ -11,6 +11,8 @@ class RoleCommon(WorkOSModel): class EventRole(RoleCommon): permissions: Optional[Sequence[str]] = None + created_at: Optional[str] = None + updated_at: Optional[str] = None class Role(RoleCommon): From 6ab4c6e8c81fd4b06e6ce617f392a9322243927e Mon Sep 17 00:00:00 2001 From: vivekkhimani Date: Thu, 12 Mar 2026 15:20:13 -0700 Subject: [PATCH 4/4] greptile comments --- src/workos/types/events/event.py | 6 +- src/workos/types/events/vault_payload.py | 6 + tests/test_events.py | 242 +++++++++++++++++++++++ 3 files changed, 252 insertions(+), 2 deletions(-) diff --git a/src/workos/types/events/event.py b/src/workos/types/events/event.py index 35f8512e..8b72c967 100644 --- a/src/workos/types/events/event.py +++ b/src/workos/types/events/event.py @@ -57,9 +57,11 @@ VaultDataCreatedPayload, VaultDataDeletedPayload, VaultDataReadPayload, + VaultDataUpdatedPayload, VaultDekDecryptedPayload, VaultDekReadPayload, VaultKekCreatedPayload, + VaultMetadataReadPayload, VaultNamesListedPayload, ) from workos.types.organizations.organization_common import OrganizationCommon @@ -393,7 +395,7 @@ class VaultDataReadEvent(EventModel[VaultDataReadPayload]): event: Literal["vault.data.read"] -class VaultDataUpdatedEvent(EventModel[VaultDataCreatedPayload]): +class VaultDataUpdatedEvent(EventModel[VaultDataUpdatedPayload]): event: Literal["vault.data.updated"] @@ -409,7 +411,7 @@ class VaultKekCreatedEvent(EventModel[VaultKekCreatedPayload]): event: Literal["vault.kek.created"] -class VaultMetadataReadEvent(EventModel[VaultDataDeletedPayload]): +class VaultMetadataReadEvent(EventModel[VaultMetadataReadPayload]): event: Literal["vault.metadata.read"] diff --git a/src/workos/types/events/vault_payload.py b/src/workos/types/events/vault_payload.py index dd052d21..79e81bd4 100644 --- a/src/workos/types/events/vault_payload.py +++ b/src/workos/types/events/vault_payload.py @@ -55,3 +55,9 @@ class VaultKekCreatedPayload(WorkOSModel): actor_name: str key_name: str key_id: str + + +# Type aliases for events that reuse another event's payload shape. +# These give the .data field a semantically correct name for consumers. +VaultDataUpdatedPayload = VaultDataCreatedPayload +VaultMetadataReadPayload = VaultDataDeletedPayload diff --git a/tests/test_events.py b/tests/test_events.py index c153bd10..42432634 100644 --- a/tests/test_events.py +++ b/tests/test_events.py @@ -7,7 +7,13 @@ from workos.types.events import OrganizationMembershipCreatedEvent from workos.types.events.event import ( VaultDataCreatedEvent, + VaultDataDeletedEvent, + VaultDataReadEvent, + VaultDataUpdatedEvent, + VaultDekDecryptedEvent, VaultDekReadEvent, + VaultKekCreatedEvent, + VaultMetadataReadEvent, VaultNamesListedEvent, ) @@ -223,3 +229,239 @@ def test_list_events_vault_names_listed( assert event.data.actor_id == "user_01234" assert event.data.actor_source == "api" assert event.data.actor_name == "Service Account" + + def test_list_events_vault_data_read( + self, + module_instance: Union[Events, AsyncEvents], + capture_and_mock_http_client_request, + ): + mock_response = { + "object": "list", + "data": [ + { + "object": "event", + "id": "event_vault_09", + "event": "vault.data.read", + "data": { + "actor_id": "user_01234", + "actor_source": "api", + "actor_name": "Read Service", + "kv_name": "db-password", + "key_id": "key_55", + }, + "created_at": "2024-01-01T00:00:00.000Z", + } + ], + "list_metadata": {"after": None}, + } + + capture_and_mock_http_client_request( + http_client=module_instance._http_client, + status_code=200, + response_dict=mock_response, + ) + + events: EventsListResource = syncify( + module_instance.list_events(events=["vault.data.read"]) + ) + + event = events.data[0] + assert isinstance(event, VaultDataReadEvent) + assert event.data.kv_name == "db-password" + assert event.data.key_id == "key_55" + + def test_list_events_vault_dek_decrypted( + self, + module_instance: Union[Events, AsyncEvents], + capture_and_mock_http_client_request, + ): + mock_response = { + "object": "list", + "data": [ + { + "object": "event", + "id": "event_vault_04", + "event": "vault.dek.decrypted", + "data": { + "actor_id": "user_01234", + "actor_source": "api", + "actor_name": "Decryption Service", + "key_id": "key_99", + }, + "created_at": "2024-01-01T00:00:00.000Z", + } + ], + "list_metadata": {"after": None}, + } + + capture_and_mock_http_client_request( + http_client=module_instance._http_client, + status_code=200, + response_dict=mock_response, + ) + + events: EventsListResource = syncify( + module_instance.list_events(events=["vault.dek.decrypted"]) + ) + + event = events.data[0] + assert isinstance(event, VaultDekDecryptedEvent) + assert event.data.key_id == "key_99" + assert event.data.actor_name == "Decryption Service" + + def test_list_events_vault_kek_created( + self, + module_instance: Union[Events, AsyncEvents], + capture_and_mock_http_client_request, + ): + mock_response = { + "object": "list", + "data": [ + { + "object": "event", + "id": "event_vault_05", + "event": "vault.kek.created", + "data": { + "actor_id": "user_01234", + "actor_source": "dashboard", + "actor_name": "Admin", + "key_name": "production-kek", + "key_id": "kek_01", + }, + "created_at": "2024-01-01T00:00:00.000Z", + } + ], + "list_metadata": {"after": None}, + } + + capture_and_mock_http_client_request( + http_client=module_instance._http_client, + status_code=200, + response_dict=mock_response, + ) + + events: EventsListResource = syncify( + module_instance.list_events(events=["vault.kek.created"]) + ) + + event = events.data[0] + assert isinstance(event, VaultKekCreatedEvent) + assert event.data.key_name == "production-kek" + assert event.data.key_id == "kek_01" + + def test_list_events_vault_data_deleted( + self, + module_instance: Union[Events, AsyncEvents], + capture_and_mock_http_client_request, + ): + mock_response = { + "object": "list", + "data": [ + { + "object": "event", + "id": "event_vault_06", + "event": "vault.data.deleted", + "data": { + "actor_id": "user_01234", + "actor_source": "api", + "actor_name": "Cleanup Job", + "kv_name": "old-secret", + }, + "created_at": "2024-01-01T00:00:00.000Z", + } + ], + "list_metadata": {"after": None}, + } + + capture_and_mock_http_client_request( + http_client=module_instance._http_client, + status_code=200, + response_dict=mock_response, + ) + + events: EventsListResource = syncify( + module_instance.list_events(events=["vault.data.deleted"]) + ) + + event = events.data[0] + assert isinstance(event, VaultDataDeletedEvent) + assert event.data.kv_name == "old-secret" + + def test_list_events_vault_data_updated( + self, + module_instance: Union[Events, AsyncEvents], + capture_and_mock_http_client_request, + ): + mock_response = { + "object": "list", + "data": [ + { + "object": "event", + "id": "event_vault_07", + "event": "vault.data.updated", + "data": { + "actor_id": "user_01234", + "actor_source": "api", + "actor_name": "Rotation Job", + "kv_name": "api-key", + "key_id": "key_02", + "key_context": {"env": "staging"}, + }, + "created_at": "2024-01-01T00:00:00.000Z", + } + ], + "list_metadata": {"after": None}, + } + + capture_and_mock_http_client_request( + http_client=module_instance._http_client, + status_code=200, + response_dict=mock_response, + ) + + events: EventsListResource = syncify( + module_instance.list_events(events=["vault.data.updated"]) + ) + + event = events.data[0] + assert isinstance(event, VaultDataUpdatedEvent) + assert event.data.kv_name == "api-key" + assert event.data.key_id == "key_02" + + def test_list_events_vault_metadata_read( + self, + module_instance: Union[Events, AsyncEvents], + capture_and_mock_http_client_request, + ): + mock_response = { + "object": "list", + "data": [ + { + "object": "event", + "id": "event_vault_08", + "event": "vault.metadata.read", + "data": { + "actor_id": "user_01234", + "actor_source": "api", + "actor_name": "Audit Service", + "kv_name": "config-store", + }, + "created_at": "2024-01-01T00:00:00.000Z", + } + ], + "list_metadata": {"after": None}, + } + + capture_and_mock_http_client_request( + http_client=module_instance._http_client, + status_code=200, + response_dict=mock_response, + ) + + events: EventsListResource = syncify( + module_instance.list_events(events=["vault.metadata.read"]) + ) + + event = events.data[0] + assert isinstance(event, VaultMetadataReadEvent) + assert event.data.kv_name == "config-store"