From 60988e89b9e0ab944caa9789cae3dea305c2d8b7 Mon Sep 17 00:00:00 2001 From: Taronish Daruwalla Date: Mon, 22 Jan 2024 14:29:02 -0800 Subject: [PATCH 1/6] Autogen update: totp and recovery codes --- .gitignore | 1 + stytch/b2b/api/discovery_organizations.py | 34 ++- stytch/b2b/api/magic_links_email.py | 4 +- stytch/b2b/api/organizations.py | 80 +++++- stytch/b2b/api/organizations_members.py | 70 ++++- stytch/b2b/api/otp_sms.py | 46 +++ stytch/b2b/api/passwords.py | 8 +- stytch/b2b/api/rbac.py | 4 +- stytch/b2b/api/recovery_codes.py | 232 ++++++++++++++++ stytch/b2b/api/sessions.py | 12 +- stytch/b2b/api/sso_saml.py | 8 +- stytch/b2b/api/totps.py | 308 +++++++++++++++++++++ stytch/b2b/client.py | 12 + stytch/b2b/models/organizations.py | 21 +- stytch/b2b/models/organizations_members.py | 22 ++ stytch/b2b/models/passwords_email.py | 2 + stytch/b2b/models/recovery_codes.py | 64 +++++ stytch/b2b/models/sessions.py | 2 +- stytch/b2b/models/totps.py | 70 +++++ stytch/consumer/api/otp_sms.py | 8 + stytch/consumer/models/oauth.py | 2 +- stytch/consumer/models/sessions.py | 18 +- 22 files changed, 984 insertions(+), 44 deletions(-) create mode 100644 stytch/b2b/api/recovery_codes.py create mode 100644 stytch/b2b/api/totps.py create mode 100644 stytch/b2b/models/recovery_codes.py create mode 100644 stytch/b2b/models/totps.py diff --git a/.gitignore b/.gitignore index 6e6d70f4..7ffb9326 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ stytch.egg-info/ .env* .coverage dist/ +.idea/ diff --git a/stytch/b2b/api/discovery_organizations.py b/stytch/b2b/api/discovery_organizations.py index aa02e018..51ba2b78 100644 --- a/stytch/b2b/api/discovery_organizations.py +++ b/stytch/b2b/api/discovery_organizations.py @@ -41,6 +41,8 @@ def create( rbac_email_implicit_role_assignments: Optional[ List[EmailImplicitRoleAssignment] ] = None, + mfa_methods: Optional[str] = None, + allowed_mfa_methods: Optional[List[str]] = None, ) -> CreateResponse: """If an end user does not want to join any already-existing Organization, or has no possible Organizations to join, this endpoint can be used to create a new [Organization](https://stytch.com/docs/b2b/api/organization-object) and [Member](https://stytch.com/docs/b2b/api/member-object). @@ -122,10 +124,19 @@ def create( `OPTIONAL` – The default value. The Organization does not require MFA by default for all Members. Members will be required to complete MFA only if their `mfa_enrolled` status is set to true. - - rbac_email_implicit_role_assignments: (Coming Soon) Implicit role assignments based off of email domains. + - rbac_email_implicit_role_assignments: Implicit role assignments based off of email domains. For each domain-Role pair, all Members whose email addresses have the specified email domain will be granted the associated Role, regardless of their login method. See the [RBAC guide](https://stytch.com/docs/b2b/guides/rbac/role-assignment) for more information about role assignment. + - mfa_methods: The setting that controls which mfa methods can be used by Members of an Organization. The accepted values are: + + `ALL_ALLOWED` – the default setting which allows all authentication methods to be used. + + `RESTRICTED` – only methods that comply with `allowed_auth_methods` can be used for authentication. This setting does not apply to Members with `is_breakglass` set to `true`. + + - allowed_mfa_methods: An array of allowed mfa authentication methods. This list is enforced when `mfa_methods` is set to `RESTRICTED`. + The list's accepted values are: `sms_otp` and `totp`. + """ # noqa headers: Dict[str, str] = {} data: Dict[str, Any] = { @@ -159,6 +170,10 @@ def create( data["rbac_email_implicit_role_assignments"] = [ item.dict() for item in rbac_email_implicit_role_assignments ] + if mfa_methods is not None: + data["mfa_methods"] = mfa_methods + if allowed_mfa_methods is not None: + data["allowed_mfa_methods"] = allowed_mfa_methods url = self.api_base.url_for("/v1/b2b/discovery/organizations/create", data) res = self.sync_client.post(url, data, headers) @@ -183,6 +198,8 @@ async def create_async( rbac_email_implicit_role_assignments: Optional[ List[EmailImplicitRoleAssignment] ] = None, + mfa_methods: Optional[str] = None, + allowed_mfa_methods: Optional[List[str]] = None, ) -> CreateResponse: """If an end user does not want to join any already-existing Organization, or has no possible Organizations to join, this endpoint can be used to create a new [Organization](https://stytch.com/docs/b2b/api/organization-object) and [Member](https://stytch.com/docs/b2b/api/member-object). @@ -264,10 +281,19 @@ async def create_async( `OPTIONAL` – The default value. The Organization does not require MFA by default for all Members. Members will be required to complete MFA only if their `mfa_enrolled` status is set to true. - - rbac_email_implicit_role_assignments: (Coming Soon) Implicit role assignments based off of email domains. + - rbac_email_implicit_role_assignments: Implicit role assignments based off of email domains. For each domain-Role pair, all Members whose email addresses have the specified email domain will be granted the associated Role, regardless of their login method. See the [RBAC guide](https://stytch.com/docs/b2b/guides/rbac/role-assignment) for more information about role assignment. + - mfa_methods: The setting that controls which mfa methods can be used by Members of an Organization. The accepted values are: + + `ALL_ALLOWED` – the default setting which allows all authentication methods to be used. + + `RESTRICTED` – only methods that comply with `allowed_auth_methods` can be used for authentication. This setting does not apply to Members with `is_breakglass` set to `true`. + + - allowed_mfa_methods: An array of allowed mfa authentication methods. This list is enforced when `mfa_methods` is set to `RESTRICTED`. + The list's accepted values are: `sms_otp` and `totp`. + """ # noqa headers: Dict[str, str] = {} data: Dict[str, Any] = { @@ -301,6 +327,10 @@ async def create_async( data["rbac_email_implicit_role_assignments"] = [ item.dict() for item in rbac_email_implicit_role_assignments ] + if mfa_methods is not None: + data["mfa_methods"] = mfa_methods + if allowed_mfa_methods is not None: + data["allowed_mfa_methods"] = allowed_mfa_methods url = self.api_base.url_for("/v1/b2b/discovery/organizations/create", data) res = await self.async_client.post(url, data, headers) diff --git a/stytch/b2b/api/magic_links_email.py b/stytch/b2b/api/magic_links_email.py index 995771bf..ca676b13 100644 --- a/stytch/b2b/api/magic_links_email.py +++ b/stytch/b2b/api/magic_links_email.py @@ -181,7 +181,7 @@ def invite( Request support for additional languages [here](https://docs.google.com/forms/d/e/1FAIpQLScZSpAu_m2AmLXRT3F3kap-s_mcV6UTBitYn6CdyWP0-o7YjQ/viewform?usp=sf_link")! - - roles: (Coming Soon) Roles to explicitly assign to this Member. See the [RBAC guide](https://stytch.com/docs/b2b/guides/rbac/role-assignment) + - roles: Roles to explicitly assign to this Member. See the [RBAC guide](https://stytch.com/docs/b2b/guides/rbac/role-assignment) for more information about role assignment. """ # noqa headers: Dict[str, str] = {} @@ -248,7 +248,7 @@ async def invite_async( Request support for additional languages [here](https://docs.google.com/forms/d/e/1FAIpQLScZSpAu_m2AmLXRT3F3kap-s_mcV6UTBitYn6CdyWP0-o7YjQ/viewform?usp=sf_link")! - - roles: (Coming Soon) Roles to explicitly assign to this Member. See the [RBAC guide](https://stytch.com/docs/b2b/guides/rbac/role-assignment) + - roles: Roles to explicitly assign to this Member. See the [RBAC guide](https://stytch.com/docs/b2b/guides/rbac/role-assignment) for more information about role assignment. """ # noqa headers: Dict[str, str] = {} diff --git a/stytch/b2b/api/organizations.py b/stytch/b2b/api/organizations.py index 767cf525..32b69d21 100644 --- a/stytch/b2b/api/organizations.py +++ b/stytch/b2b/api/organizations.py @@ -53,6 +53,8 @@ def create( rbac_email_implicit_role_assignments: Optional[ List[EmailImplicitRoleAssignment] ] = None, + mfa_methods: Optional[str] = None, + allowed_mfa_methods: Optional[List[str]] = None, ) -> CreateResponse: """Creates an Organization. An `organization_name` and a unique `organization_slug` are required. @@ -106,10 +108,19 @@ def create( `OPTIONAL` – The default value. The Organization does not require MFA by default for all Members. Members will be required to complete MFA only if their `mfa_enrolled` status is set to true. - - rbac_email_implicit_role_assignments: (Coming Soon) Implicit role assignments based off of email domains. + - rbac_email_implicit_role_assignments: Implicit role assignments based off of email domains. For each domain-Role pair, all Members whose email addresses have the specified email domain will be granted the associated Role, regardless of their login method. See the [RBAC guide](https://stytch.com/docs/b2b/guides/rbac/role-assignment) for more information about role assignment. + - mfa_methods: The setting that controls which mfa methods can be used by Members of an Organization. The accepted values are: + + `ALL_ALLOWED` – the default setting which allows all authentication methods to be used. + + `RESTRICTED` – only methods that comply with `allowed_auth_methods` can be used for authentication. This setting does not apply to Members with `is_breakglass` set to `true`. + + - allowed_mfa_methods: An array of allowed mfa authentication methods. This list is enforced when `mfa_methods` is set to `RESTRICTED`. + The list's accepted values are: `sms_otp` and `totp`. + """ # noqa headers: Dict[str, str] = {} data: Dict[str, Any] = { @@ -139,6 +150,10 @@ def create( data["rbac_email_implicit_role_assignments"] = [ item.dict() for item in rbac_email_implicit_role_assignments ] + if mfa_methods is not None: + data["mfa_methods"] = mfa_methods + if allowed_mfa_methods is not None: + data["allowed_mfa_methods"] = allowed_mfa_methods url = self.api_base.url_for("/v1/b2b/organizations", data) res = self.sync_client.post(url, data, headers) @@ -160,6 +175,8 @@ async def create_async( rbac_email_implicit_role_assignments: Optional[ List[EmailImplicitRoleAssignment] ] = None, + mfa_methods: Optional[str] = None, + allowed_mfa_methods: Optional[List[str]] = None, ) -> CreateResponse: """Creates an Organization. An `organization_name` and a unique `organization_slug` are required. @@ -213,10 +230,19 @@ async def create_async( `OPTIONAL` – The default value. The Organization does not require MFA by default for all Members. Members will be required to complete MFA only if their `mfa_enrolled` status is set to true. - - rbac_email_implicit_role_assignments: (Coming Soon) Implicit role assignments based off of email domains. + - rbac_email_implicit_role_assignments: Implicit role assignments based off of email domains. For each domain-Role pair, all Members whose email addresses have the specified email domain will be granted the associated Role, regardless of their login method. See the [RBAC guide](https://stytch.com/docs/b2b/guides/rbac/role-assignment) for more information about role assignment. + - mfa_methods: The setting that controls which mfa methods can be used by Members of an Organization. The accepted values are: + + `ALL_ALLOWED` – the default setting which allows all authentication methods to be used. + + `RESTRICTED` – only methods that comply with `allowed_auth_methods` can be used for authentication. This setting does not apply to Members with `is_breakglass` set to `true`. + + - allowed_mfa_methods: An array of allowed mfa authentication methods. This list is enforced when `mfa_methods` is set to `RESTRICTED`. + The list's accepted values are: `sms_otp` and `totp`. + """ # noqa headers: Dict[str, str] = {} data: Dict[str, Any] = { @@ -246,6 +272,10 @@ async def create_async( data["rbac_email_implicit_role_assignments"] = [ item.dict() for item in rbac_email_implicit_role_assignments ] + if mfa_methods is not None: + data["mfa_methods"] = mfa_methods + if allowed_mfa_methods is not None: + data["allowed_mfa_methods"] = allowed_mfa_methods url = self.api_base.url_for("/v1/b2b/organizations", data) res = await self.async_client.post(url, data, headers) @@ -304,13 +334,15 @@ def update( allowed_auth_methods: Optional[List[str]] = None, mfa_policy: Optional[str] = None, rbac_email_implicit_role_assignments: Optional[List[str]] = None, + mfa_methods: Optional[str] = None, + allowed_mfa_methods: Optional[List[str]] = None, method_options: Optional[UpdateRequestOptions] = None, ) -> UpdateResponse: """Updates an Organization specified by `organization_id`. An Organization must always have at least one auth setting set to either `RESTRICTED` or `ALL_ALLOWED` in order to provision new Members. *See the [Organization authentication settings](https://stytch.com/docs/b2b/api/org-auth-settings) resource to learn more about fields like `email_jit_provisioning`, `email_invites`, `sso_jit_provisioning`, etc., and their behaviors. - (Coming Soon) Our RBAC implementation offers out-of-the-box handling of authorization checks for this endpoint. If you pass in + Our RBAC implementation offers out-of-the-box handling of authorization checks for this endpoint. If you pass in a header containing a `session_token` or a `session_jwt` for an unexpired Member Session, we will check that the Member Session has the necessary permissions. The specific permissions needed depend on which of the optional fields are passed in the request. For example, if the `organization_name` argument is provided, the Member Session must have @@ -398,12 +430,25 @@ def update( If this field is provided and a session header is passed into the request, the Member Session must have permission to perform the `update.settings.mfa-policy` action on the `stytch.organization` Resource. - - rbac_email_implicit_role_assignments: (Coming Soon) Implicit role assignments based off of email domains. + - rbac_email_implicit_role_assignments: Implicit role assignments based off of email domains. For each domain-Role pair, all Members whose email addresses have the specified email domain will be granted the associated Role, regardless of their login method. See the [RBAC guide](https://stytch.com/docs/b2b/guides/rbac/role-assignment) for more information about role assignment. If this field is provided and a session header is passed into the request, the Member Session must have permission to perform the `update.settings.implicit-roles` action on the `stytch.organization` Resource. + - mfa_methods: The setting that controls which mfa methods can be used by Members of an Organization. The accepted values are: + + `ALL_ALLOWED` – the default setting which allows all authentication methods to be used. + + `RESTRICTED` – only methods that comply with `allowed_auth_methods` can be used for authentication. This setting does not apply to Members with `is_breakglass` set to `true`. + + + If this field is provided and a session header is passed into the request, the Member Session must have permission to perform the `update.settings.allowed-auth-methods` action on the `stytch.organization` Resource. + - allowed_mfa_methods: An array of allowed mfa authentication methods. This list is enforced when `mfa_methods` is set to `RESTRICTED`. + The list's accepted values are: `sms_otp` and `totp`. + + + If this field is provided and a session header is passed into the request, the Member Session must have permission to perform the `update.settings.allowed-mfa-methods` action on the `stytch.organization` Resource. """ # noqa headers: Dict[str, str] = {} if method_options is not None: @@ -443,6 +488,10 @@ def update( data[ "rbac_email_implicit_role_assignments" ] = rbac_email_implicit_role_assignments + if mfa_methods is not None: + data["mfa_methods"] = mfa_methods + if allowed_mfa_methods is not None: + data["allowed_mfa_methods"] = allowed_mfa_methods url = self.api_base.url_for("/v1/b2b/organizations/{organization_id}", data) res = self.sync_client.put(url, data, headers) @@ -465,13 +514,15 @@ async def update_async( allowed_auth_methods: Optional[List[str]] = None, mfa_policy: Optional[str] = None, rbac_email_implicit_role_assignments: Optional[List[str]] = None, + mfa_methods: Optional[str] = None, + allowed_mfa_methods: Optional[List[str]] = None, method_options: Optional[UpdateRequestOptions] = None, ) -> UpdateResponse: """Updates an Organization specified by `organization_id`. An Organization must always have at least one auth setting set to either `RESTRICTED` or `ALL_ALLOWED` in order to provision new Members. *See the [Organization authentication settings](https://stytch.com/docs/b2b/api/org-auth-settings) resource to learn more about fields like `email_jit_provisioning`, `email_invites`, `sso_jit_provisioning`, etc., and their behaviors. - (Coming Soon) Our RBAC implementation offers out-of-the-box handling of authorization checks for this endpoint. If you pass in + Our RBAC implementation offers out-of-the-box handling of authorization checks for this endpoint. If you pass in a header containing a `session_token` or a `session_jwt` for an unexpired Member Session, we will check that the Member Session has the necessary permissions. The specific permissions needed depend on which of the optional fields are passed in the request. For example, if the `organization_name` argument is provided, the Member Session must have @@ -559,12 +610,25 @@ async def update_async( If this field is provided and a session header is passed into the request, the Member Session must have permission to perform the `update.settings.mfa-policy` action on the `stytch.organization` Resource. - - rbac_email_implicit_role_assignments: (Coming Soon) Implicit role assignments based off of email domains. + - rbac_email_implicit_role_assignments: Implicit role assignments based off of email domains. For each domain-Role pair, all Members whose email addresses have the specified email domain will be granted the associated Role, regardless of their login method. See the [RBAC guide](https://stytch.com/docs/b2b/guides/rbac/role-assignment) for more information about role assignment. If this field is provided and a session header is passed into the request, the Member Session must have permission to perform the `update.settings.implicit-roles` action on the `stytch.organization` Resource. + - mfa_methods: The setting that controls which mfa methods can be used by Members of an Organization. The accepted values are: + + `ALL_ALLOWED` – the default setting which allows all authentication methods to be used. + + `RESTRICTED` – only methods that comply with `allowed_auth_methods` can be used for authentication. This setting does not apply to Members with `is_breakglass` set to `true`. + + + If this field is provided and a session header is passed into the request, the Member Session must have permission to perform the `update.settings.allowed-auth-methods` action on the `stytch.organization` Resource. + - allowed_mfa_methods: An array of allowed mfa authentication methods. This list is enforced when `mfa_methods` is set to `RESTRICTED`. + The list's accepted values are: `sms_otp` and `totp`. + + + If this field is provided and a session header is passed into the request, the Member Session must have permission to perform the `update.settings.allowed-mfa-methods` action on the `stytch.organization` Resource. """ # noqa headers: Dict[str, str] = {} if method_options is not None: @@ -604,6 +668,10 @@ async def update_async( data[ "rbac_email_implicit_role_assignments" ] = rbac_email_implicit_role_assignments + if mfa_methods is not None: + data["mfa_methods"] = mfa_methods + if allowed_mfa_methods is not None: + data["allowed_mfa_methods"] = allowed_mfa_methods url = self.api_base.url_for("/v1/b2b/organizations/{organization_id}", data) res = await self.async_client.put(url, data, headers) diff --git a/stytch/b2b/api/organizations_members.py b/stytch/b2b/api/organizations_members.py index d8b7782e..91e9dc86 100644 --- a/stytch/b2b/api/organizations_members.py +++ b/stytch/b2b/api/organizations_members.py @@ -23,6 +23,8 @@ ReactivateResponse, SearchRequestOptions, SearchResponse, + TOTPRequestOptions, + TOTPResponse, UpdateRequestOptions, UpdateResponse, ) @@ -50,11 +52,12 @@ def update( mfa_enrolled: Optional[bool] = None, roles: Optional[List[str]] = None, preserve_existing_sessions: Optional[bool] = None, + default_mfa_method: Optional[str] = None, method_options: Optional[UpdateRequestOptions] = None, ) -> UpdateResponse: """Updates a Member specified by `organization_id` and `member_id`. - (Coming Soon) Our RBAC implementation offers out-of-the-box handling of authorization checks for this endpoint. If you pass in + Our RBAC implementation offers out-of-the-box handling of authorization checks for this endpoint. If you pass in a header containing a `session_token` or a `session_jwt` for an unexpired Member Session, we will check that the Member Session has the necessary permissions. The specific permissions needed depend on which of the optional fields are passed in the request. For example, if the `organization_name` argument is provided, the Member Session must have @@ -93,7 +96,7 @@ def update( If this field is provided and a session header is passed into the request, the Member Session must have permission to perform the `update.settings.mfa-enrolled` action on the `stytch.member` Resource. Alternatively, if the Member Session matches the Member associated with the `member_id` passed in the request, the authorization check will also allow a Member Session that has permission to perform the `update.settings.mfa-enrolled` action on the `stytch.self` Resource. - - roles: (Coming Soon) Roles to explicitly assign to this Member. + - roles: Roles to explicitly assign to this Member. Will completely replace any existing explicitly assigned roles. See the [RBAC guide](https://stytch.com/docs/b2b/guides/rbac/role-assignment) for more information about role assignment. @@ -103,9 +106,10 @@ def update( `preserve_existing_sessions` parameter with a value of `true`. If this field is provided and a session header is passed into the request, the Member Session must have permission to perform the `update.settings.roles` action on the `stytch.member` Resource. - - preserve_existing_sessions: (Coming Soon) Whether to preserve existing sessions when explicit Roles that are revoked are also implicitly assigned + - preserve_existing_sessions: Whether to preserve existing sessions when explicit Roles that are revoked are also implicitly assigned by SSO connection or SSO group. Defaults to `false` - that is, existing Member Sessions that contain SSO authentication factors with the affected SSO connection IDs will be revoked. + - default_mfa_method: The Member's default MFA method. This value is used to determine which secondary MFA method to use in the case of multiple methods registered for a Member. The current possible values are `sms_otp` and `totp`. """ # noqa headers: Dict[str, str] = {} if method_options is not None: @@ -130,6 +134,8 @@ def update( data["roles"] = roles if preserve_existing_sessions is not None: data["preserve_existing_sessions"] = preserve_existing_sessions + if default_mfa_method is not None: + data["default_mfa_method"] = default_mfa_method url = self.api_base.url_for( "/v1/b2b/organizations/{organization_id}/members/{member_id}", data @@ -149,11 +155,12 @@ async def update_async( mfa_enrolled: Optional[bool] = None, roles: Optional[List[str]] = None, preserve_existing_sessions: Optional[bool] = None, + default_mfa_method: Optional[str] = None, method_options: Optional[UpdateRequestOptions] = None, ) -> UpdateResponse: """Updates a Member specified by `organization_id` and `member_id`. - (Coming Soon) Our RBAC implementation offers out-of-the-box handling of authorization checks for this endpoint. If you pass in + Our RBAC implementation offers out-of-the-box handling of authorization checks for this endpoint. If you pass in a header containing a `session_token` or a `session_jwt` for an unexpired Member Session, we will check that the Member Session has the necessary permissions. The specific permissions needed depend on which of the optional fields are passed in the request. For example, if the `organization_name` argument is provided, the Member Session must have @@ -192,7 +199,7 @@ async def update_async( If this field is provided and a session header is passed into the request, the Member Session must have permission to perform the `update.settings.mfa-enrolled` action on the `stytch.member` Resource. Alternatively, if the Member Session matches the Member associated with the `member_id` passed in the request, the authorization check will also allow a Member Session that has permission to perform the `update.settings.mfa-enrolled` action on the `stytch.self` Resource. - - roles: (Coming Soon) Roles to explicitly assign to this Member. + - roles: Roles to explicitly assign to this Member. Will completely replace any existing explicitly assigned roles. See the [RBAC guide](https://stytch.com/docs/b2b/guides/rbac/role-assignment) for more information about role assignment. @@ -202,9 +209,10 @@ async def update_async( `preserve_existing_sessions` parameter with a value of `true`. If this field is provided and a session header is passed into the request, the Member Session must have permission to perform the `update.settings.roles` action on the `stytch.member` Resource. - - preserve_existing_sessions: (Coming Soon) Whether to preserve existing sessions when explicit Roles that are revoked are also implicitly assigned + - preserve_existing_sessions: Whether to preserve existing sessions when explicit Roles that are revoked are also implicitly assigned by SSO connection or SSO group. Defaults to `false` - that is, existing Member Sessions that contain SSO authentication factors with the affected SSO connection IDs will be revoked. + - default_mfa_method: The Member's default MFA method. This value is used to determine which secondary MFA method to use in the case of multiple methods registered for a Member. The current possible values are `sms_otp` and `totp`. """ # noqa headers: Dict[str, str] = {} if method_options is not None: @@ -229,6 +237,8 @@ async def update_async( data["roles"] = roles if preserve_existing_sessions is not None: data["preserve_existing_sessions"] = preserve_existing_sessions + if default_mfa_method is not None: + data["default_mfa_method"] = default_mfa_method url = self.api_base.url_for( "/v1/b2b/organizations/{organization_id}/members/{member_id}", data @@ -412,6 +422,46 @@ async def delete_mfa_phone_number_async( res = await self.async_client.delete(url, headers) return DeleteMFAPhoneNumberResponse.from_json(res.response.status, res.json) + def totp( + self, + organization_id: str, + member_id: str, + method_options: Optional[TOTPRequestOptions] = None, + ) -> TOTPResponse: + headers: Dict[str, str] = {} + if method_options is not None: + headers = method_options.add_headers(headers) + data: Dict[str, Any] = { + "organization_id": organization_id, + "member_id": member_id, + } + + url = self.api_base.url_for( + "/v1/b2b/organizations/{organization_id}/members/{member_id}/totp", data + ) + res = self.sync_client.delete(url, headers) + return TOTPResponse.from_json(res.response.status_code, res.json) + + async def totp_async( + self, + organization_id: str, + member_id: str, + method_options: Optional[TOTPRequestOptions] = None, + ) -> TOTPResponse: + headers: Dict[str, str] = {} + if method_options is not None: + headers = method_options.add_headers(headers) + data: Dict[str, Any] = { + "organization_id": organization_id, + "member_id": member_id, + } + + url = self.api_base.url_for( + "/v1/b2b/organizations/{organization_id}/members/{member_id}/totp", data + ) + res = await self.async_client.delete(url, headers) + return TOTPResponse.from_json(res.response.status, res.json) + def search( self, organization_ids: List[str], @@ -424,7 +474,7 @@ def search( *All fuzzy search filters require a minimum of three characters. - (Coming Soon) Our RBAC implementation offers out-of-the-box handling of authorization checks for this endpoint. If you pass in + Our RBAC implementation offers out-of-the-box handling of authorization checks for this endpoint. If you pass in a header containing a `session_token` or a `session_jwt` for an unexpired Member Session, we will check that the Member Session has permission to perform the `search` action on the `stytch.member` Resource. In addition, enforcing RBAC on this endpoint means that you may only search for Members within the calling Member's Organization, so the @@ -471,7 +521,7 @@ async def search_async( *All fuzzy search filters require a minimum of three characters. - (Coming Soon) Our RBAC implementation offers out-of-the-box handling of authorization checks for this endpoint. If you pass in + Our RBAC implementation offers out-of-the-box handling of authorization checks for this endpoint. If you pass in a header containing a `session_token` or a `session_jwt` for an unexpired Member Session, we will check that the Member Session has permission to perform the `search` action on the `stytch.member` Resource. In addition, enforcing RBAC on this endpoint means that you may only search for Members within the calling Member's Organization, so the @@ -628,7 +678,7 @@ def create( - is_breakglass: Identifies the Member as a break glass user - someone who has permissions to authenticate into an Organization by bypassing the Organization's settings. A break glass account is typically used for emergency purposes to gain access outside of normal authentication procedures. Refer to the [Organization object](organization-object) and its `auth_methods` and `allowed_auth_methods` fields for more details. - mfa_phone_number: The Member's phone number. A Member may only have one phone number. - mfa_enrolled: Sets whether the Member is enrolled in MFA. If true, the Member must complete an MFA step whenever they wish to log in to their Organization. If false, the Member only needs to complete an MFA step if the Organization's MFA policy is set to `REQUIRED_FOR_ALL`. - - roles: (Coming Soon) Roles to explicitly assign to this Member. See the [RBAC guide](https://stytch.com/docs/b2b/guides/rbac/role-assignment) + - roles: Roles to explicitly assign to this Member. See the [RBAC guide](https://stytch.com/docs/b2b/guides/rbac/role-assignment) for more information about role assignment. """ # noqa headers: Dict[str, str] = {} @@ -689,7 +739,7 @@ async def create_async( - is_breakglass: Identifies the Member as a break glass user - someone who has permissions to authenticate into an Organization by bypassing the Organization's settings. A break glass account is typically used for emergency purposes to gain access outside of normal authentication procedures. Refer to the [Organization object](organization-object) and its `auth_methods` and `allowed_auth_methods` fields for more details. - mfa_phone_number: The Member's phone number. A Member may only have one phone number. - mfa_enrolled: Sets whether the Member is enrolled in MFA. If true, the Member must complete an MFA step whenever they wish to log in to their Organization. If false, the Member only needs to complete an MFA step if the Organization's MFA policy is set to `REQUIRED_FOR_ALL`. - - roles: (Coming Soon) Roles to explicitly assign to this Member. See the [RBAC guide](https://stytch.com/docs/b2b/guides/rbac/role-assignment) + - roles: Roles to explicitly assign to this Member. See the [RBAC guide](https://stytch.com/docs/b2b/guides/rbac/role-assignment) for more information about role assignment. """ # noqa headers: Dict[str, str] = {} diff --git a/stytch/b2b/api/otp_sms.py b/stytch/b2b/api/otp_sms.py index 587066a6..8be99752 100644 --- a/stytch/b2b/api/otp_sms.py +++ b/stytch/b2b/api/otp_sms.py @@ -31,6 +31,9 @@ def send( member_id: str, mfa_phone_number: Optional[str] = None, locale: Optional[Union[SendRequestLocale, str]] = None, + intermediate_session_token: Optional[str] = None, + session_token: Optional[str] = None, + session_jwt: Optional[str] = None, ) -> SendResponse: """Send a One-Time Passcode (OTP) to a Member's phone number. @@ -43,9 +46,13 @@ def send( If a Member has a phone number and is enrolled in MFA, then after a successful primary authentication event (e.g. [email magic link](https://stytch.com/docs/b2b/api/authenticate-magic-link) or [SSO](https://stytch.com/docs/b2b/api/sso-authenticate) login is complete), an SMS OTP will automatically be sent to their phone number. In that case, this endpoint should only be used for subsequent authentication events, such as prompting a Member for an OTP again after a period of inactivity. + Passing an intermediate session token, session token, or session JWT is not required, but if passed must match the Member ID passed. + ### Cost to send SMS OTP Before configuring SMS or WhatsApp OTPs, please review how Stytch [bills the costs of international OTPs](https://stytch.com/pricing) and understand how to protect your app against [toll fraud](https://stytch.com/docs/guides/passcodes/toll-fraud/overview). + Even when international SMS is enabled, we do not support sending SMS to countries on our [Unsupported countries list](https://stytch.com/docs/guides/passcodes/unsupported-countries). + __Note:__ SMS to phone numbers outside of the US and Canada is disabled by default for customers who did not use SMS prior to October 2023. If you're interested in sending international SMS, please reach out to [support@stytch.com](mailto:support@stytch.com?subject=Enable%20international%20SMS). Fields: @@ -58,6 +65,12 @@ def send( Request support for additional languages [here](https://docs.google.com/forms/d/e/1FAIpQLScZSpAu_m2AmLXRT3F3kap-s_mcV6UTBitYn6CdyWP0-o7YjQ/viewform?usp=sf_link")! + - intermediate_session_token: The Intermediate Session Token. This token does not necessarily belong to a specific instance of a Member, but represents a bag of factors that may be converted to a member session. + The token can be used with the [OTP SMS Authenticate endpoint](https://stytch.com/docs/b2b/api/authenticate-otp-sms) to complete an MFA flow; + the [Exchange Intermediate Session endpoint](https://stytch.com/docs/b2b/api/exchange-intermediate-session) to join a specific Organization that allows the factors represented by the intermediate session token; + or the [Create Organization via Discovery endpoint](https://stytch.com/docs/b2b/api/create-organization-via-discovery) to create a new Organization and Member. + - session_token: A secret token for a given Stytch Session. + - session_jwt: The JSON Web Token (JWT) for a given Stytch Session. """ # noqa headers: Dict[str, str] = {} data: Dict[str, Any] = { @@ -68,6 +81,12 @@ def send( data["mfa_phone_number"] = mfa_phone_number if locale is not None: data["locale"] = locale + if intermediate_session_token is not None: + data["intermediate_session_token"] = intermediate_session_token + if session_token is not None: + data["session_token"] = session_token + if session_jwt is not None: + data["session_jwt"] = session_jwt url = self.api_base.url_for("/v1/b2b/otps/sms/send", data) res = self.sync_client.post(url, data, headers) @@ -79,6 +98,9 @@ async def send_async( member_id: str, mfa_phone_number: Optional[str] = None, locale: Optional[SendRequestLocale] = None, + intermediate_session_token: Optional[str] = None, + session_token: Optional[str] = None, + session_jwt: Optional[str] = None, ) -> SendResponse: """Send a One-Time Passcode (OTP) to a Member's phone number. @@ -91,9 +113,13 @@ async def send_async( If a Member has a phone number and is enrolled in MFA, then after a successful primary authentication event (e.g. [email magic link](https://stytch.com/docs/b2b/api/authenticate-magic-link) or [SSO](https://stytch.com/docs/b2b/api/sso-authenticate) login is complete), an SMS OTP will automatically be sent to their phone number. In that case, this endpoint should only be used for subsequent authentication events, such as prompting a Member for an OTP again after a period of inactivity. + Passing an intermediate session token, session token, or session JWT is not required, but if passed must match the Member ID passed. + ### Cost to send SMS OTP Before configuring SMS or WhatsApp OTPs, please review how Stytch [bills the costs of international OTPs](https://stytch.com/pricing) and understand how to protect your app against [toll fraud](https://stytch.com/docs/guides/passcodes/toll-fraud/overview). + Even when international SMS is enabled, we do not support sending SMS to countries on our [Unsupported countries list](https://stytch.com/docs/guides/passcodes/unsupported-countries). + __Note:__ SMS to phone numbers outside of the US and Canada is disabled by default for customers who did not use SMS prior to October 2023. If you're interested in sending international SMS, please reach out to [support@stytch.com](mailto:support@stytch.com?subject=Enable%20international%20SMS). Fields: @@ -106,6 +132,12 @@ async def send_async( Request support for additional languages [here](https://docs.google.com/forms/d/e/1FAIpQLScZSpAu_m2AmLXRT3F3kap-s_mcV6UTBitYn6CdyWP0-o7YjQ/viewform?usp=sf_link")! + - intermediate_session_token: The Intermediate Session Token. This token does not necessarily belong to a specific instance of a Member, but represents a bag of factors that may be converted to a member session. + The token can be used with the [OTP SMS Authenticate endpoint](https://stytch.com/docs/b2b/api/authenticate-otp-sms) to complete an MFA flow; + the [Exchange Intermediate Session endpoint](https://stytch.com/docs/b2b/api/exchange-intermediate-session) to join a specific Organization that allows the factors represented by the intermediate session token; + or the [Create Organization via Discovery endpoint](https://stytch.com/docs/b2b/api/create-organization-via-discovery) to create a new Organization and Member. + - session_token: A secret token for a given Stytch Session. + - session_jwt: The JSON Web Token (JWT) for a given Stytch Session. """ # noqa headers: Dict[str, str] = {} data: Dict[str, Any] = { @@ -116,6 +148,12 @@ async def send_async( data["mfa_phone_number"] = mfa_phone_number if locale is not None: data["locale"] = locale + if intermediate_session_token is not None: + data["intermediate_session_token"] = intermediate_session_token + if session_token is not None: + data["session_token"] = session_token + if session_jwt is not None: + data["session_jwt"] = session_jwt url = self.api_base.url_for("/v1/b2b/otps/sms/send", data) res = await self.async_client.post(url, data, headers) @@ -132,6 +170,7 @@ def authenticate( session_duration_minutes: Optional[int] = None, session_custom_claims: Optional[Dict[str, Any]] = None, set_mfa_enrollment: Optional[str] = None, + set_default_mfa: Optional[bool] = None, ) -> AuthenticateResponse: """SMS OTPs may not be used as a primary authentication mechanism. They can be used to complete an MFA requirement, or they can be used as a step-up factor to be added to an existing session. @@ -180,6 +219,7 @@ def authenticate( `unenroll` – sets the Member's `mfa_enrolled` boolean to `false`. The Member will no longer be required to complete MFA steps when logging in to the Organization. + - set_default_mfa: (no documentation yet) """ # noqa headers: Dict[str, str] = {} data: Dict[str, Any] = { @@ -199,6 +239,8 @@ def authenticate( data["session_custom_claims"] = session_custom_claims if set_mfa_enrollment is not None: data["set_mfa_enrollment"] = set_mfa_enrollment + if set_default_mfa is not None: + data["set_default_mfa"] = set_default_mfa url = self.api_base.url_for("/v1/b2b/otps/sms/authenticate", data) res = self.sync_client.post(url, data, headers) @@ -215,6 +257,7 @@ async def authenticate_async( session_duration_minutes: Optional[int] = None, session_custom_claims: Optional[Dict[str, Any]] = None, set_mfa_enrollment: Optional[str] = None, + set_default_mfa: Optional[bool] = None, ) -> AuthenticateResponse: """SMS OTPs may not be used as a primary authentication mechanism. They can be used to complete an MFA requirement, or they can be used as a step-up factor to be added to an existing session. @@ -263,6 +306,7 @@ async def authenticate_async( `unenroll` – sets the Member's `mfa_enrolled` boolean to `false`. The Member will no longer be required to complete MFA steps when logging in to the Organization. + - set_default_mfa: (no documentation yet) """ # noqa headers: Dict[str, str] = {} data: Dict[str, Any] = { @@ -282,6 +326,8 @@ async def authenticate_async( data["session_custom_claims"] = session_custom_claims if set_mfa_enrollment is not None: data["set_mfa_enrollment"] = set_mfa_enrollment + if set_default_mfa is not None: + data["set_default_mfa"] = set_default_mfa url = self.api_base.url_for("/v1/b2b/otps/sms/authenticate", data) res = await self.async_client.post(url, data, headers) diff --git a/stytch/b2b/api/passwords.py b/stytch/b2b/api/passwords.py index 1266898a..be8e7d1b 100644 --- a/stytch/b2b/api/passwords.py +++ b/stytch/b2b/api/passwords.py @@ -148,7 +148,7 @@ def migrate( - untrusted_metadata: An arbitrary JSON object of application-specific data. These fields can be edited directly by the frontend SDK, and should not be used to store critical information. See the [Metadata resource](https://stytch.com/docs/b2b/api/metadata) for complete field behavior details. - - roles: (Coming Soon) Roles to explicitly assign to this Member. + - roles: Roles to explicitly assign to this Member. Will completely replace any existing explicitly assigned roles. See the [RBAC guide](https://stytch.com/docs/b2b/guides/rbac/role-assignment) for more information about role assignment. @@ -156,7 +156,7 @@ def migrate( or an SSO group, we will by default revoke any existing sessions for the Member that contain any SSO authentication factors with the affected connection ID. You can preserve these sessions by passing in the `preserve_existing_sessions` parameter with a value of `true`. - - preserve_existing_sessions: (Coming Soon) Whether to preserve existing sessions when explicit Roles that are revoked are also implicitly assigned + - preserve_existing_sessions: Whether to preserve existing sessions when explicit Roles that are revoked are also implicitly assigned by SSO connection or SSO group. Defaults to `false` - that is, existing Member Sessions that contain SSO authentication factors with the affected SSO connection IDs will be revoked. """ # noqa @@ -226,7 +226,7 @@ async def migrate_async( - untrusted_metadata: An arbitrary JSON object of application-specific data. These fields can be edited directly by the frontend SDK, and should not be used to store critical information. See the [Metadata resource](https://stytch.com/docs/b2b/api/metadata) for complete field behavior details. - - roles: (Coming Soon) Roles to explicitly assign to this Member. + - roles: Roles to explicitly assign to this Member. Will completely replace any existing explicitly assigned roles. See the [RBAC guide](https://stytch.com/docs/b2b/guides/rbac/role-assignment) for more information about role assignment. @@ -234,7 +234,7 @@ async def migrate_async( or an SSO group, we will by default revoke any existing sessions for the Member that contain any SSO authentication factors with the affected connection ID. You can preserve these sessions by passing in the `preserve_existing_sessions` parameter with a value of `true`. - - preserve_existing_sessions: (Coming Soon) Whether to preserve existing sessions when explicit Roles that are revoked are also implicitly assigned + - preserve_existing_sessions: Whether to preserve existing sessions when explicit Roles that are revoked are also implicitly assigned by SSO connection or SSO group. Defaults to `false` - that is, existing Member Sessions that contain SSO authentication factors with the affected SSO connection IDs will be revoked. """ # noqa diff --git a/stytch/b2b/api/rbac.py b/stytch/b2b/api/rbac.py index 0d189792..8fb5fb16 100644 --- a/stytch/b2b/api/rbac.py +++ b/stytch/b2b/api/rbac.py @@ -31,7 +31,7 @@ def policy( Resources and Roles can be created and managed within the [Dashboard](/dashboard). Additionally, [Role assignment](https://stytch.com/docs/b2b/guides/rbac/role-assignment) can be programmatically managed through certain Stytch API endpoints. - Check out the [RBAC overview](https://stytch.com/docs/b2b/guides/rbac/overview) to learn more about Stytch's RBAC permissioning model or [contact us](https://share.hsforms.com/1qkU__-1CT1--lnqRDxphXgd4bkb) to request early access. + Check out the [RBAC overview](https://stytch.com/docs/b2b/guides/rbac/overview) to learn more about Stytch's RBAC permissioning model. Fields: """ # noqa @@ -52,7 +52,7 @@ async def policy_async( Resources and Roles can be created and managed within the [Dashboard](/dashboard). Additionally, [Role assignment](https://stytch.com/docs/b2b/guides/rbac/role-assignment) can be programmatically managed through certain Stytch API endpoints. - Check out the [RBAC overview](https://stytch.com/docs/b2b/guides/rbac/overview) to learn more about Stytch's RBAC permissioning model or [contact us](https://share.hsforms.com/1qkU__-1CT1--lnqRDxphXgd4bkb) to request early access. + Check out the [RBAC overview](https://stytch.com/docs/b2b/guides/rbac/overview) to learn more about Stytch's RBAC permissioning model. Fields: """ # noqa diff --git a/stytch/b2b/api/recovery_codes.py b/stytch/b2b/api/recovery_codes.py new file mode 100644 index 00000000..56349908 --- /dev/null +++ b/stytch/b2b/api/recovery_codes.py @@ -0,0 +1,232 @@ +# !!! +# WARNING: This file is autogenerated +# Only modify code within MANUAL() sections +# or your changes may be overwritten later! +# !!! + +from __future__ import annotations + +from typing import Any, Dict, Optional + +from stytch.b2b.models.recovery_codes import ( + B2BGetResponse, + RecoverResponse, + RotateResponse, +) +from stytch.core.api_base import ApiBase +from stytch.core.http.client import AsyncClient, SyncClient + + +class RecoveryCodes: + def __init__( + self, api_base: ApiBase, sync_client: SyncClient, async_client: AsyncClient + ) -> None: + self.api_base = api_base + self.sync_client = sync_client + self.async_client = async_client + + def recover( + self, + organization_id: str, + member_id: str, + recovery_code: str, + intermediate_session_token: Optional[str] = None, + session_token: Optional[str] = None, + session_jwt: Optional[str] = None, + session_duration_minutes: Optional[int] = None, + session_custom_claims: Optional[Dict[str, Any]] = None, + ) -> RecoverResponse: + """Allows a Member to complete an MFA flow by consuming a recovery code. This consumes the recovery code and returns a session token that can be used to authenticate the Member. + + Fields: + - organization_id: Globally unique UUID that identifies a specific Organization. The `organization_id` is critical to perform operations on an Organization, so be sure to preserve this value. + - member_id: Globally unique UUID that identifies a specific Member. The `member_id` is critical to perform operations on a Member, so be sure to preserve this value. + - recovery_code: The recovery code generated by a secondary MFA method. This code is used to authenticate in place of the secondary MFA method if that method as a backup. + - intermediate_session_token: The Intermediate Session Token. This token does not necessarily belong to a specific instance of a Member, but represents a bag of factors that may be converted to a member session. + The token can be used with the [OTP SMS Authenticate endpoint](https://stytch.com/docs/b2b/api/authenticate-otp-sms) to complete an MFA flow; + the [Exchange Intermediate Session endpoint](https://stytch.com/docs/b2b/api/exchange-intermediate-session) to join a specific Organization that allows the factors represented by the intermediate session token; + or the [Create Organization via Discovery endpoint](https://stytch.com/docs/b2b/api/create-organization-via-discovery) to create a new Organization and Member. + - session_token: A secret token for a given Stytch Session. + - session_jwt: The JSON Web Token (JWT) for a given Stytch Session. + - session_duration_minutes: Set the session lifetime to be this many minutes from now. This will start a new session if one doesn't already exist, + returning both an opaque `session_token` and `session_jwt` for this session. Remember that the `session_jwt` will have a fixed lifetime of + five minutes regardless of the underlying session duration, and will need to be refreshed over time. + + This value must be a minimum of 5 and a maximum of 527040 minutes (366 days). + + If a `session_token` or `session_jwt` is provided then a successful authentication will continue to extend the session this many minutes. + + If the `session_duration_minutes` parameter is not specified, a Stytch session will be created with a 60 minute duration. If you don't want + to use the Stytch session product, you can ignore the session fields in the response. + - session_custom_claims: Add a custom claims map to the Session being authenticated. Claims are only created if a Session is initialized by providing a value in + `session_duration_minutes`. Claims will be included on the Session object and in the JWT. To update a key in an existing Session, supply a new value. To + delete a key, supply a null value. Custom claims made with reserved claims (`iss`, `sub`, `aud`, `exp`, `nbf`, `iat`, `jti`) will be ignored. + Total custom claims size cannot exceed four kilobytes. + """ # noqa + headers: Dict[str, str] = {} + data: Dict[str, Any] = { + "organization_id": organization_id, + "member_id": member_id, + "recovery_code": recovery_code, + } + if intermediate_session_token is not None: + data["intermediate_session_token"] = intermediate_session_token + if session_token is not None: + data["session_token"] = session_token + if session_jwt is not None: + data["session_jwt"] = session_jwt + if session_duration_minutes is not None: + data["session_duration_minutes"] = session_duration_minutes + if session_custom_claims is not None: + data["session_custom_claims"] = session_custom_claims + + url = self.api_base.url_for("/v1/b2b/recovery_codes/recover", data) + res = self.sync_client.post(url, data, headers) + return RecoverResponse.from_json(res.response.status_code, res.json) + + async def recover_async( + self, + organization_id: str, + member_id: str, + recovery_code: str, + intermediate_session_token: Optional[str] = None, + session_token: Optional[str] = None, + session_jwt: Optional[str] = None, + session_duration_minutes: Optional[int] = None, + session_custom_claims: Optional[Dict[str, Any]] = None, + ) -> RecoverResponse: + """Allows a Member to complete an MFA flow by consuming a recovery code. This consumes the recovery code and returns a session token that can be used to authenticate the Member. + + Fields: + - organization_id: Globally unique UUID that identifies a specific Organization. The `organization_id` is critical to perform operations on an Organization, so be sure to preserve this value. + - member_id: Globally unique UUID that identifies a specific Member. The `member_id` is critical to perform operations on a Member, so be sure to preserve this value. + - recovery_code: The recovery code generated by a secondary MFA method. This code is used to authenticate in place of the secondary MFA method if that method as a backup. + - intermediate_session_token: The Intermediate Session Token. This token does not necessarily belong to a specific instance of a Member, but represents a bag of factors that may be converted to a member session. + The token can be used with the [OTP SMS Authenticate endpoint](https://stytch.com/docs/b2b/api/authenticate-otp-sms) to complete an MFA flow; + the [Exchange Intermediate Session endpoint](https://stytch.com/docs/b2b/api/exchange-intermediate-session) to join a specific Organization that allows the factors represented by the intermediate session token; + or the [Create Organization via Discovery endpoint](https://stytch.com/docs/b2b/api/create-organization-via-discovery) to create a new Organization and Member. + - session_token: A secret token for a given Stytch Session. + - session_jwt: The JSON Web Token (JWT) for a given Stytch Session. + - session_duration_minutes: Set the session lifetime to be this many minutes from now. This will start a new session if one doesn't already exist, + returning both an opaque `session_token` and `session_jwt` for this session. Remember that the `session_jwt` will have a fixed lifetime of + five minutes regardless of the underlying session duration, and will need to be refreshed over time. + + This value must be a minimum of 5 and a maximum of 527040 minutes (366 days). + + If a `session_token` or `session_jwt` is provided then a successful authentication will continue to extend the session this many minutes. + + If the `session_duration_minutes` parameter is not specified, a Stytch session will be created with a 60 minute duration. If you don't want + to use the Stytch session product, you can ignore the session fields in the response. + - session_custom_claims: Add a custom claims map to the Session being authenticated. Claims are only created if a Session is initialized by providing a value in + `session_duration_minutes`. Claims will be included on the Session object and in the JWT. To update a key in an existing Session, supply a new value. To + delete a key, supply a null value. Custom claims made with reserved claims (`iss`, `sub`, `aud`, `exp`, `nbf`, `iat`, `jti`) will be ignored. + Total custom claims size cannot exceed four kilobytes. + """ # noqa + headers: Dict[str, str] = {} + data: Dict[str, Any] = { + "organization_id": organization_id, + "member_id": member_id, + "recovery_code": recovery_code, + } + if intermediate_session_token is not None: + data["intermediate_session_token"] = intermediate_session_token + if session_token is not None: + data["session_token"] = session_token + if session_jwt is not None: + data["session_jwt"] = session_jwt + if session_duration_minutes is not None: + data["session_duration_minutes"] = session_duration_minutes + if session_custom_claims is not None: + data["session_custom_claims"] = session_custom_claims + + url = self.api_base.url_for("/v1/b2b/recovery_codes/recover", data) + res = await self.async_client.post(url, data, headers) + return RecoverResponse.from_json(res.response.status, res.json) + + def b2_b_get( + self, + organization_id: str, + member_id: str, + ) -> B2BGetResponse: + """Returns a Member's full set of active recovery codes. + + Fields: + - organization_id: Globally unique UUID that identifies a specific Organization. The `organization_id` is critical to perform operations on an Organization, so be sure to preserve this value. + - member_id: Globally unique UUID that identifies a specific Member. The `member_id` is critical to perform operations on a Member, so be sure to preserve this value. + """ # noqa + headers: Dict[str, str] = {} + data: Dict[str, Any] = { + "organization_id": organization_id, + "member_id": member_id, + } + + url = self.api_base.url_for( + "/v1/b2b/recovery_codes/{organization_id}/{member_id}", data + ) + res = self.sync_client.get(url, data, headers) + return B2BGetResponse.from_json(res.response.status_code, res.json) + + async def b2_b_get_async( + self, + organization_id: str, + member_id: str, + ) -> B2BGetResponse: + """Returns a Member's full set of active recovery codes. + + Fields: + - organization_id: Globally unique UUID that identifies a specific Organization. The `organization_id` is critical to perform operations on an Organization, so be sure to preserve this value. + - member_id: Globally unique UUID that identifies a specific Member. The `member_id` is critical to perform operations on a Member, so be sure to preserve this value. + """ # noqa + headers: Dict[str, str] = {} + data: Dict[str, Any] = { + "organization_id": organization_id, + "member_id": member_id, + } + + url = self.api_base.url_for( + "/v1/b2b/recovery_codes/{organization_id}/{member_id}", data + ) + res = await self.async_client.get(url, data, headers) + return B2BGetResponse.from_json(res.response.status, res.json) + + def rotate( + self, + organization_id: str, + member_id: str, + ) -> RotateResponse: + """Rotate a Member's recovery codes. This invalidates all existing recovery codes and generates a new set of recovery codes. + + Fields: + - organization_id: Globally unique UUID that identifies a specific Organization. The `organization_id` is critical to perform operations on an Organization, so be sure to preserve this value. + - member_id: Globally unique UUID that identifies a specific Member. The `member_id` is critical to perform operations on a Member, so be sure to preserve this value. + """ # noqa + headers: Dict[str, str] = {} + data: Dict[str, Any] = { + "organization_id": organization_id, + "member_id": member_id, + } + + url = self.api_base.url_for("/v1/b2b/recovery_codes/rotate", data) + res = self.sync_client.post(url, data, headers) + return RotateResponse.from_json(res.response.status_code, res.json) + + async def rotate_async( + self, + organization_id: str, + member_id: str, + ) -> RotateResponse: + """Rotate a Member's recovery codes. This invalidates all existing recovery codes and generates a new set of recovery codes. + + Fields: + - organization_id: Globally unique UUID that identifies a specific Organization. The `organization_id` is critical to perform operations on an Organization, so be sure to preserve this value. + - member_id: Globally unique UUID that identifies a specific Member. The `member_id` is critical to perform operations on a Member, so be sure to preserve this value. + """ # noqa + headers: Dict[str, str] = {} + data: Dict[str, Any] = { + "organization_id": organization_id, + "member_id": member_id, + } + + url = self.api_base.url_for("/v1/b2b/recovery_codes/rotate", data) + res = await self.async_client.post(url, data, headers) + return RotateResponse.from_json(res.response.status, res.json) diff --git a/stytch/b2b/api/sessions.py b/stytch/b2b/api/sessions.py index 49f1079c..07113469 100644 --- a/stytch/b2b/api/sessions.py +++ b/stytch/b2b/api/sessions.py @@ -98,7 +98,7 @@ def authenticate( You may provide a JWT that needs to be refreshed and is expired according to its `exp` claim. A new JWT will be returned if both the signature and the underlying Session are still valid. - If an `authorization_check` object is passed in, this method will also check if the Member is authorized to perform the given action on the given Resource in the specified Organization. A Member is authorized if their Member Session contains a Role, assigned [explicitly or implicitly](https://github.com/docs/b2b/guides/rbac/role-assignment), with adequate permissions. + If an `authorization_check` object is passed in, this method will also check if the Member is authorized to perform the given action on the given Resource in the specified Organization. A Member is authorized if their Member Session contains a Role, assigned [explicitly or implicitly](https://stytch.com/docs/b2b/guides/rbac/role-assignment), with adequate permissions. In addition, the `organization_id` passed in the authorization check must match the Member's Organization. If the Member is not authorized to perform the specified action on the specified Resource, or if the @@ -122,10 +122,10 @@ def authenticate( `session_duration_minutes`. Claims will be included on the Session object and in the JWT. To update a key in an existing Session, supply a new value. To delete a key, supply a null value. Custom claims made with reserved claims (`iss`, `sub`, `aud`, `exp`, `nbf`, `iat`, `jti`) will be ignored. Total custom claims size cannot exceed four kilobytes. - - authorization_check: (Coming Soon) If an `authorization_check` object is passed in, this endpoint will also check if the Member is + - authorization_check: If an `authorization_check` object is passed in, this endpoint will also check if the Member is authorized to perform the given action on the given Resource in the specified Organization. A Member is authorized if their Member Session contains a Role, assigned - [explicitly or implicitly](https://github.com/docs/b2b/guides/rbac/role-assignment), with adequate permissions. + [explicitly or implicitly](https://stytch.com/docs/b2b/guides/rbac/role-assignment), with adequate permissions. In addition, the `organization_id` passed in the authorization check must match the Member's Organization. The Roles on the Member Session may differ from the Roles you see on the Member object - Roles that are implicitly @@ -165,7 +165,7 @@ async def authenticate_async( You may provide a JWT that needs to be refreshed and is expired according to its `exp` claim. A new JWT will be returned if both the signature and the underlying Session are still valid. - If an `authorization_check` object is passed in, this method will also check if the Member is authorized to perform the given action on the given Resource in the specified Organization. A Member is authorized if their Member Session contains a Role, assigned [explicitly or implicitly](https://github.com/docs/b2b/guides/rbac/role-assignment), with adequate permissions. + If an `authorization_check` object is passed in, this method will also check if the Member is authorized to perform the given action on the given Resource in the specified Organization. A Member is authorized if their Member Session contains a Role, assigned [explicitly or implicitly](https://stytch.com/docs/b2b/guides/rbac/role-assignment), with adequate permissions. In addition, the `organization_id` passed in the authorization check must match the Member's Organization. If the Member is not authorized to perform the specified action on the specified Resource, or if the @@ -189,10 +189,10 @@ async def authenticate_async( `session_duration_minutes`. Claims will be included on the Session object and in the JWT. To update a key in an existing Session, supply a new value. To delete a key, supply a null value. Custom claims made with reserved claims (`iss`, `sub`, `aud`, `exp`, `nbf`, `iat`, `jti`) will be ignored. Total custom claims size cannot exceed four kilobytes. - - authorization_check: (Coming Soon) If an `authorization_check` object is passed in, this endpoint will also check if the Member is + - authorization_check: If an `authorization_check` object is passed in, this endpoint will also check if the Member is authorized to perform the given action on the given Resource in the specified Organization. A Member is authorized if their Member Session contains a Role, assigned - [explicitly or implicitly](https://github.com/docs/b2b/guides/rbac/role-assignment), with adequate permissions. + [explicitly or implicitly](https://stytch.com/docs/b2b/guides/rbac/role-assignment), with adequate permissions. In addition, the `organization_id` passed in the authorization check must match the Member's Organization. The Roles on the Member Session may differ from the Roles you see on the Member object - Roles that are implicitly diff --git a/stytch/b2b/api/sso_saml.py b/stytch/b2b/api/sso_saml.py index c6118ad3..7eb556c1 100644 --- a/stytch/b2b/api/sso_saml.py +++ b/stytch/b2b/api/sso_saml.py @@ -111,8 +111,8 @@ def update_connection( - attribute_mapping: An object that represents the attributes used to identify a Member. This object will map the IdP-defined User attributes to Stytch-specific values. Required attributes: `email` and one of `full_name` or `first_name` and `last_name`. - x509_certificate: A certificate that Stytch will use to verify the sign-in assertion sent by the IdP, in [PEM](https://en.wikipedia.org/wiki/Privacy-Enhanced_Mail) format. See our [X509 guide](https://stytch.com/docs/b2b/api/saml-certificates) for more info. - idp_sso_url: The URL for which assertions for login requests will be sent. This will be provided by the IdP. - - saml_connection_implicit_role_assignments: (Coming Soon) All Members who log in with this SAML connection will implicitly receive the specified Roles. See the [RBAC guide](https://stytch.com/docs/b2b/guides/rbac/role-assignment) for more information about role assignment. - - saml_group_implicit_role_assignments: (Coming Soon) Defines the names of the SAML groups + - saml_connection_implicit_role_assignments: All Members who log in with this SAML connection will implicitly receive the specified Roles. See the [RBAC guide](https://stytch.com/docs/b2b/guides/rbac/role-assignment) for more information about role assignment. + - saml_group_implicit_role_assignments: Defines the names of the SAML groups that grant specific role assignments. For each group-Role pair, if a Member logs in with this SAML connection and belongs to the specified SAML group, they will be granted the associated Role. See the [RBAC guide](https://stytch.com/docs/b2b/guides/rbac/role-assignment) for more information about role assignment. @@ -185,8 +185,8 @@ async def update_connection_async( - attribute_mapping: An object that represents the attributes used to identify a Member. This object will map the IdP-defined User attributes to Stytch-specific values. Required attributes: `email` and one of `full_name` or `first_name` and `last_name`. - x509_certificate: A certificate that Stytch will use to verify the sign-in assertion sent by the IdP, in [PEM](https://en.wikipedia.org/wiki/Privacy-Enhanced_Mail) format. See our [X509 guide](https://stytch.com/docs/b2b/api/saml-certificates) for more info. - idp_sso_url: The URL for which assertions for login requests will be sent. This will be provided by the IdP. - - saml_connection_implicit_role_assignments: (Coming Soon) All Members who log in with this SAML connection will implicitly receive the specified Roles. See the [RBAC guide](https://stytch.com/docs/b2b/guides/rbac/role-assignment) for more information about role assignment. - - saml_group_implicit_role_assignments: (Coming Soon) Defines the names of the SAML groups + - saml_connection_implicit_role_assignments: All Members who log in with this SAML connection will implicitly receive the specified Roles. See the [RBAC guide](https://stytch.com/docs/b2b/guides/rbac/role-assignment) for more information about role assignment. + - saml_group_implicit_role_assignments: Defines the names of the SAML groups that grant specific role assignments. For each group-Role pair, if a Member logs in with this SAML connection and belongs to the specified SAML group, they will be granted the associated Role. See the [RBAC guide](https://stytch.com/docs/b2b/guides/rbac/role-assignment) for more information about role assignment. diff --git a/stytch/b2b/api/totps.py b/stytch/b2b/api/totps.py new file mode 100644 index 00000000..2407b673 --- /dev/null +++ b/stytch/b2b/api/totps.py @@ -0,0 +1,308 @@ +# !!! +# WARNING: This file is autogenerated +# Only modify code within MANUAL() sections +# or your changes may be overwritten later! +# !!! + +from __future__ import annotations + +from typing import Any, Dict, List, Optional + +from stytch.b2b.models.totps import ( + AuthenticateResponse, + CreateResponse, + MigrateResponse, +) +from stytch.core.api_base import ApiBase +from stytch.core.http.client import AsyncClient, SyncClient + + +class TOTPs: + def __init__( + self, api_base: ApiBase, sync_client: SyncClient, async_client: AsyncClient + ) -> None: + self.api_base = api_base + self.sync_client = sync_client + self.async_client = async_client + + def create( + self, + organization_id: str, + member_id: str, + expiration_minutes: Optional[int] = None, + intermediate_session_token: Optional[str] = None, + session_token: Optional[str] = None, + session_jwt: Optional[str] = None, + ) -> CreateResponse: + """Create a new TOTP instance for a Member. The Member can use the authenticator application of their choice to scan the QR code or enter the secret. + + Passing an intermediate session token, session token, or session JWT is not required, but if passed must match the Member ID passed. + + Fields: + - organization_id: Globally unique UUID that identifies a specific Organization. The `organization_id` is critical to perform operations on an Organization, so be sure to preserve this value. + - member_id: Globally unique UUID that identifies a specific Member. The `member_id` is critical to perform operations on a Member, so be sure to preserve this value. + - expiration_minutes: The expiration for the TOTP registration. If the newly created TOTP registration is not authenticated within this time frame the member will have to restart the registration flow. Defaults to 60 (1 hour) with a minimum of 5 and a maximum of 1440. + - intermediate_session_token: The Intermediate Session Token. This token does not necessarily belong to a specific instance of a Member, but represents a bag of factors that may be converted to a member session. + The token can be used with the [OTP SMS Authenticate endpoint](https://stytch.com/docs/b2b/api/authenticate-otp-sms) to complete an MFA flow; + the [Exchange Intermediate Session endpoint](https://stytch.com/docs/b2b/api/exchange-intermediate-session) to join a specific Organization that allows the factors represented by the intermediate session token; + or the [Create Organization via Discovery endpoint](https://stytch.com/docs/b2b/api/create-organization-via-discovery) to create a new Organization and Member. + - session_token: A secret token for a given Stytch Session. + - session_jwt: The JSON Web Token (JWT) for a given Stytch Session. + """ # noqa + headers: Dict[str, str] = {} + data: Dict[str, Any] = { + "organization_id": organization_id, + "member_id": member_id, + } + if expiration_minutes is not None: + data["expiration_minutes"] = expiration_minutes + if intermediate_session_token is not None: + data["intermediate_session_token"] = intermediate_session_token + if session_token is not None: + data["session_token"] = session_token + if session_jwt is not None: + data["session_jwt"] = session_jwt + + url = self.api_base.url_for("/v1/b2b/totp", data) + res = self.sync_client.post(url, data, headers) + return CreateResponse.from_json(res.response.status_code, res.json) + + async def create_async( + self, + organization_id: str, + member_id: str, + expiration_minutes: Optional[int] = None, + intermediate_session_token: Optional[str] = None, + session_token: Optional[str] = None, + session_jwt: Optional[str] = None, + ) -> CreateResponse: + """Create a new TOTP instance for a Member. The Member can use the authenticator application of their choice to scan the QR code or enter the secret. + + Passing an intermediate session token, session token, or session JWT is not required, but if passed must match the Member ID passed. + + Fields: + - organization_id: Globally unique UUID that identifies a specific Organization. The `organization_id` is critical to perform operations on an Organization, so be sure to preserve this value. + - member_id: Globally unique UUID that identifies a specific Member. The `member_id` is critical to perform operations on a Member, so be sure to preserve this value. + - expiration_minutes: The expiration for the TOTP registration. If the newly created TOTP registration is not authenticated within this time frame the member will have to restart the registration flow. Defaults to 60 (1 hour) with a minimum of 5 and a maximum of 1440. + - intermediate_session_token: The Intermediate Session Token. This token does not necessarily belong to a specific instance of a Member, but represents a bag of factors that may be converted to a member session. + The token can be used with the [OTP SMS Authenticate endpoint](https://stytch.com/docs/b2b/api/authenticate-otp-sms) to complete an MFA flow; + the [Exchange Intermediate Session endpoint](https://stytch.com/docs/b2b/api/exchange-intermediate-session) to join a specific Organization that allows the factors represented by the intermediate session token; + or the [Create Organization via Discovery endpoint](https://stytch.com/docs/b2b/api/create-organization-via-discovery) to create a new Organization and Member. + - session_token: A secret token for a given Stytch Session. + - session_jwt: The JSON Web Token (JWT) for a given Stytch Session. + """ # noqa + headers: Dict[str, str] = {} + data: Dict[str, Any] = { + "organization_id": organization_id, + "member_id": member_id, + } + if expiration_minutes is not None: + data["expiration_minutes"] = expiration_minutes + if intermediate_session_token is not None: + data["intermediate_session_token"] = intermediate_session_token + if session_token is not None: + data["session_token"] = session_token + if session_jwt is not None: + data["session_jwt"] = session_jwt + + url = self.api_base.url_for("/v1/b2b/totp", data) + res = await self.async_client.post(url, data, headers) + return CreateResponse.from_json(res.response.status, res.json) + + def authenticate( + self, + organization_id: str, + member_id: str, + code: str, + intermediate_session_token: Optional[str] = None, + session_token: Optional[str] = None, + session_jwt: Optional[str] = None, + session_duration_minutes: Optional[int] = None, + session_custom_claims: Optional[Dict[str, Any]] = None, + set_mfa_enrollment: Optional[str] = None, + set_default_mfa: Optional[bool] = None, + ) -> AuthenticateResponse: + """Authenticate a Member provided TOTP. + + Fields: + - organization_id: Globally unique UUID that identifies a specific Organization. The `organization_id` is critical to perform operations on an Organization, so be sure to preserve this value. + - member_id: Globally unique UUID that identifies a specific Member. The `member_id` is critical to perform operations on a Member, so be sure to preserve this value. + - code: The code to authenticate. + - intermediate_session_token: The Intermediate Session Token. This token does not necessarily belong to a specific instance of a Member, but represents a bag of factors that may be converted to a member session. + The token can be used with the [OTP SMS Authenticate endpoint](https://stytch.com/docs/b2b/api/authenticate-otp-sms) to complete an MFA flow; + the [Exchange Intermediate Session endpoint](https://stytch.com/docs/b2b/api/exchange-intermediate-session) to join a specific Organization that allows the factors represented by the intermediate session token; + or the [Create Organization via Discovery endpoint](https://stytch.com/docs/b2b/api/create-organization-via-discovery) to create a new Organization and Member. + - session_token: A secret token for a given Stytch Session. + - session_jwt: The JSON Web Token (JWT) for a given Stytch Session. + - session_duration_minutes: Set the session lifetime to be this many minutes from now. This will start a new session if one doesn't already exist, + returning both an opaque `session_token` and `session_jwt` for this session. Remember that the `session_jwt` will have a fixed lifetime of + five minutes regardless of the underlying session duration, and will need to be refreshed over time. + + This value must be a minimum of 5 and a maximum of 527040 minutes (366 days). + + If a `session_token` or `session_jwt` is provided then a successful authentication will continue to extend the session this many minutes. + + If the `session_duration_minutes` parameter is not specified, a Stytch session will be created with a 60 minute duration. If you don't want + to use the Stytch session product, you can ignore the session fields in the response. + - session_custom_claims: Add a custom claims map to the Session being authenticated. Claims are only created if a Session is initialized by providing a value in + `session_duration_minutes`. Claims will be included on the Session object and in the JWT. To update a key in an existing Session, supply a new value. To + delete a key, supply a null value. Custom claims made with reserved claims (`iss`, `sub`, `aud`, `exp`, `nbf`, `iat`, `jti`) will be ignored. + Total custom claims size cannot exceed four kilobytes. + - set_mfa_enrollment: Optionally sets the Member’s MFA enrollment status upon a successful authentication. If the Organization’s MFA policy is `REQUIRED_FOR_ALL`, this field will be ignored. If this field is not passed in, the Member’s `mfa_enrolled` boolean will not be affected. The options are: + + `enroll` – sets the Member's `mfa_enrolled` boolean to `true`. The Member will be required to complete an MFA step upon subsequent logins to the Organization. + + `unenroll` – sets the Member's `mfa_enrolled` boolean to `false`. The Member will no longer be required to complete MFA steps when logging in to the Organization. + + - set_default_mfa: If passed will set the authenticated method to the default MFA method. Completing an MFA authentication flow for the first time for a Member will implicitly set the method to the default MFA method. This option can be used to update the default MFA method if multiple are being used. + """ # noqa + headers: Dict[str, str] = {} + data: Dict[str, Any] = { + "organization_id": organization_id, + "member_id": member_id, + "code": code, + } + if intermediate_session_token is not None: + data["intermediate_session_token"] = intermediate_session_token + if session_token is not None: + data["session_token"] = session_token + if session_jwt is not None: + data["session_jwt"] = session_jwt + if session_duration_minutes is not None: + data["session_duration_minutes"] = session_duration_minutes + if session_custom_claims is not None: + data["session_custom_claims"] = session_custom_claims + if set_mfa_enrollment is not None: + data["set_mfa_enrollment"] = set_mfa_enrollment + if set_default_mfa is not None: + data["set_default_mfa"] = set_default_mfa + + url = self.api_base.url_for("/v1/b2b/totp/authenticate", data) + res = self.sync_client.post(url, data, headers) + return AuthenticateResponse.from_json(res.response.status_code, res.json) + + async def authenticate_async( + self, + organization_id: str, + member_id: str, + code: str, + intermediate_session_token: Optional[str] = None, + session_token: Optional[str] = None, + session_jwt: Optional[str] = None, + session_duration_minutes: Optional[int] = None, + session_custom_claims: Optional[Dict[str, Any]] = None, + set_mfa_enrollment: Optional[str] = None, + set_default_mfa: Optional[bool] = None, + ) -> AuthenticateResponse: + """Authenticate a Member provided TOTP. + + Fields: + - organization_id: Globally unique UUID that identifies a specific Organization. The `organization_id` is critical to perform operations on an Organization, so be sure to preserve this value. + - member_id: Globally unique UUID that identifies a specific Member. The `member_id` is critical to perform operations on a Member, so be sure to preserve this value. + - code: The code to authenticate. + - intermediate_session_token: The Intermediate Session Token. This token does not necessarily belong to a specific instance of a Member, but represents a bag of factors that may be converted to a member session. + The token can be used with the [OTP SMS Authenticate endpoint](https://stytch.com/docs/b2b/api/authenticate-otp-sms) to complete an MFA flow; + the [Exchange Intermediate Session endpoint](https://stytch.com/docs/b2b/api/exchange-intermediate-session) to join a specific Organization that allows the factors represented by the intermediate session token; + or the [Create Organization via Discovery endpoint](https://stytch.com/docs/b2b/api/create-organization-via-discovery) to create a new Organization and Member. + - session_token: A secret token for a given Stytch Session. + - session_jwt: The JSON Web Token (JWT) for a given Stytch Session. + - session_duration_minutes: Set the session lifetime to be this many minutes from now. This will start a new session if one doesn't already exist, + returning both an opaque `session_token` and `session_jwt` for this session. Remember that the `session_jwt` will have a fixed lifetime of + five minutes regardless of the underlying session duration, and will need to be refreshed over time. + + This value must be a minimum of 5 and a maximum of 527040 minutes (366 days). + + If a `session_token` or `session_jwt` is provided then a successful authentication will continue to extend the session this many minutes. + + If the `session_duration_minutes` parameter is not specified, a Stytch session will be created with a 60 minute duration. If you don't want + to use the Stytch session product, you can ignore the session fields in the response. + - session_custom_claims: Add a custom claims map to the Session being authenticated. Claims are only created if a Session is initialized by providing a value in + `session_duration_minutes`. Claims will be included on the Session object and in the JWT. To update a key in an existing Session, supply a new value. To + delete a key, supply a null value. Custom claims made with reserved claims (`iss`, `sub`, `aud`, `exp`, `nbf`, `iat`, `jti`) will be ignored. + Total custom claims size cannot exceed four kilobytes. + - set_mfa_enrollment: Optionally sets the Member’s MFA enrollment status upon a successful authentication. If the Organization’s MFA policy is `REQUIRED_FOR_ALL`, this field will be ignored. If this field is not passed in, the Member’s `mfa_enrolled` boolean will not be affected. The options are: + + `enroll` – sets the Member's `mfa_enrolled` boolean to `true`. The Member will be required to complete an MFA step upon subsequent logins to the Organization. + + `unenroll` – sets the Member's `mfa_enrolled` boolean to `false`. The Member will no longer be required to complete MFA steps when logging in to the Organization. + + - set_default_mfa: If passed will set the authenticated method to the default MFA method. Completing an MFA authentication flow for the first time for a Member will implicitly set the method to the default MFA method. This option can be used to update the default MFA method if multiple are being used. + """ # noqa + headers: Dict[str, str] = {} + data: Dict[str, Any] = { + "organization_id": organization_id, + "member_id": member_id, + "code": code, + } + if intermediate_session_token is not None: + data["intermediate_session_token"] = intermediate_session_token + if session_token is not None: + data["session_token"] = session_token + if session_jwt is not None: + data["session_jwt"] = session_jwt + if session_duration_minutes is not None: + data["session_duration_minutes"] = session_duration_minutes + if session_custom_claims is not None: + data["session_custom_claims"] = session_custom_claims + if set_mfa_enrollment is not None: + data["set_mfa_enrollment"] = set_mfa_enrollment + if set_default_mfa is not None: + data["set_default_mfa"] = set_default_mfa + + url = self.api_base.url_for("/v1/b2b/totp/authenticate", data) + res = await self.async_client.post(url, data, headers) + return AuthenticateResponse.from_json(res.response.status, res.json) + + def migrate( + self, + organization_id: str, + member_id: str, + secret: str, + recovery_codes: List[str], + ) -> MigrateResponse: + """Migrate an existing TOTP instance for a Member. Recovery codes are not required and will be minted for the Member if not provided. + + Fields: + - organization_id: Globally unique UUID that identifies a specific Organization. The `organization_id` is critical to perform operations on an Organization, so be sure to preserve this value. + - member_id: Globally unique UUID that identifies a specific Member. The `member_id` is critical to perform operations on a Member, so be sure to preserve this value. + - secret: The TOTP secret key shared between the authenticator app and the server used to generate TOTP codes. + - recovery_codes: An existing set of recovery codes to be imported into Stytch to be used to authenticate in place of the secondary MFA method. + """ # noqa + headers: Dict[str, str] = {} + data: Dict[str, Any] = { + "organization_id": organization_id, + "member_id": member_id, + "secret": secret, + "recovery_codes": recovery_codes, + } + + url = self.api_base.url_for("/v1/b2b/totp/migrate", data) + res = self.sync_client.post(url, data, headers) + return MigrateResponse.from_json(res.response.status_code, res.json) + + async def migrate_async( + self, + organization_id: str, + member_id: str, + secret: str, + recovery_codes: List[str], + ) -> MigrateResponse: + """Migrate an existing TOTP instance for a Member. Recovery codes are not required and will be minted for the Member if not provided. + + Fields: + - organization_id: Globally unique UUID that identifies a specific Organization. The `organization_id` is critical to perform operations on an Organization, so be sure to preserve this value. + - member_id: Globally unique UUID that identifies a specific Member. The `member_id` is critical to perform operations on a Member, so be sure to preserve this value. + - secret: The TOTP secret key shared between the authenticator app and the server used to generate TOTP codes. + - recovery_codes: An existing set of recovery codes to be imported into Stytch to be used to authenticate in place of the secondary MFA method. + """ # noqa + headers: Dict[str, str] = {} + data: Dict[str, Any] = { + "organization_id": organization_id, + "member_id": member_id, + "secret": secret, + "recovery_codes": recovery_codes, + } + + url = self.api_base.url_for("/v1/b2b/totp/migrate", data) + res = await self.async_client.post(url, data, headers) + return MigrateResponse.from_json(res.response.status, res.json) diff --git a/stytch/b2b/client.py b/stytch/b2b/client.py index 0206c45c..287d9332 100644 --- a/stytch/b2b/client.py +++ b/stytch/b2b/client.py @@ -16,8 +16,10 @@ from stytch.b2b.api.otp import OTPs from stytch.b2b.api.passwords import Passwords from stytch.b2b.api.rbac import RBAC +from stytch.b2b.api.recovery_codes import RecoveryCodes from stytch.b2b.api.sessions import Sessions from stytch.b2b.api.sso import SSO +from stytch.b2b.api.totps import TOTPs from stytch.consumer.api.m2m import M2M from stytch.core.client_base import ClientBase from stytch.shared.policy_cache import PolicyCache @@ -89,6 +91,11 @@ def __init__( sync_client=self.sync_client, async_client=self.async_client, ) + self.recovery_codes = RecoveryCodes( + api_base=self.api_base, + sync_client=self.sync_client, + async_client=self.async_client, + ) self.sso = SSO( api_base=self.api_base, sync_client=self.sync_client, @@ -102,6 +109,11 @@ def __init__( project_id=project_id, policy_cache=policy_cache, ) + self.totps = TOTPs( + api_base=self.api_base, + sync_client=self.sync_client, + async_client=self.async_client, + ) def get_jwks_client(self, project_id: str) -> jwt.PyJWKClient: data = {"project_id": project_id} diff --git a/stytch/b2b/models/organizations.py b/stytch/b2b/models/organizations.py index 4cffd589..146323fb 100644 --- a/stytch/b2b/models/organizations.py +++ b/stytch/b2b/models/organizations.py @@ -204,10 +204,19 @@ class Organization(pydantic.BaseModel): The list's accepted values are: `sso`, `magic_link`, `password`, `google_oauth`, and `microsoft_oauth`. - mfa_policy: (no documentation yet) - - rbac_email_implicit_role_assignments: (Coming Soon) Implicit role assignments based off of email domains. + - rbac_email_implicit_role_assignments: Implicit role assignments based off of email domains. For each domain-Role pair, all Members whose email addresses have the specified email domain will be granted the associated Role, regardless of their login method. See the [RBAC guide](https://stytch.com/docs/b2b/guides/rbac/role-assignment) for more information about role assignment. + - mfa_methods: The setting that controls which mfa methods can be used by Members of an Organization. The accepted values are: + + `ALL_ALLOWED` – the default setting which allows all authentication methods to be used. + + `RESTRICTED` – only methods that comply with `allowed_auth_methods` can be used for authentication. This setting does not apply to Members with `is_breakglass` set to `true`. + + - allowed_mfa_methods: An array of allowed mfa authentication methods. This list is enforced when `mfa_methods` is set to `RESTRICTED`. + The list's accepted values are: `sms_otp` and `totp`. + - trusted_metadata: An arbitrary JSON object for storing application-specific data or identity-provider-specific data. - sso_default_connection_id: The default connection used for SSO when there are multiple active connections. """ # noqa @@ -226,6 +235,8 @@ class Organization(pydantic.BaseModel): allowed_auth_methods: List[str] mfa_policy: str rbac_email_implicit_role_assignments: List[EmailImplicitRoleAssignment] + mfa_methods: str + allowed_mfa_methods: List[str] trusted_metadata: Optional[Dict[str, Any]] = None sso_default_connection_id: Optional[str] = None @@ -270,12 +281,14 @@ class Member(pydantic.BaseModel): - oauth_registrations: A list of OAuth registrations for this member. - email_address_verified: Whether or not the Member's email address is verified. - mfa_phone_number_verified: Whether or not the Member's phone number is verified. - - is_admin: (Coming Soon) Whether or not the Member has the `stytch_admin` Role. This Role is automatically granted to Members + - is_admin: Whether or not the Member has the `stytch_admin` Role. This Role is automatically granted to Members who create an Organization through the [discovery flow](https://stytch.com/docs/b2b/api/create-organization-via-discovery). See the [RBAC guide](https://stytch.com/docs/b2b/guides/rbac/stytch-defaults) for more details on this Role. + - totp_registration_id: (no documentation yet) - mfa_enrolled: Sets whether the Member is enrolled in MFA. If true, the Member must complete an MFA step whenever they wish to log in to their Organization. If false, the Member only needs to complete an MFA step if the Organization's MFA policy is set to `REQUIRED_FOR_ALL`. - mfa_phone_number: The Member's phone number. A Member may only have one phone number. - - roles: (Coming Soon) Explicit or implicit Roles assigned to this Member, along with details about the role assignment source. + - default_mfa_method: (no documentation yet) + - roles: Explicit or implicit Roles assigned to this Member, along with details about the role assignment source. See the [RBAC guide](https://stytch.com/docs/b2b/guides/rbac/role-assignment) for more information about role assignment. - trusted_metadata: An arbitrary JSON object for storing application-specific data or identity-provider-specific data. - untrusted_metadata: An arbitrary JSON object of application-specific data. These fields can be edited directly by the @@ -295,8 +308,10 @@ class Member(pydantic.BaseModel): email_address_verified: bool mfa_phone_number_verified: bool is_admin: bool + totp_registration_id: str mfa_enrolled: bool mfa_phone_number: str + default_mfa_method: str roles: List[MemberRole] trusted_metadata: Optional[Dict[str, Any]] = None untrusted_metadata: Optional[Dict[str, Any]] = None diff --git a/stytch/b2b/models/organizations_members.py b/stytch/b2b/models/organizations_members.py index d6d6c7e3..7a11fa56 100644 --- a/stytch/b2b/models/organizations_members.py +++ b/stytch/b2b/models/organizations_members.py @@ -111,6 +111,22 @@ def add_headers(self, headers: Dict[str, str]) -> Dict[str, str]: return headers +class TOTPRequestOptions(pydantic.BaseModel): + """ + Fields: + - authorization: Optional authorization object. + Pass in an active Stytch Member session token or session JWT and the request + will be run using that member's permissions. + """ # noqa + + authorization: Optional[Authorization] = None + + def add_headers(self, headers: Dict[str, str]) -> Dict[str, str]: + if self.authorization is not None: + headers = self.authorization.add_headers(headers) + return headers + + class UpdateRequestOptions(pydantic.BaseModel): """ Fields: @@ -214,6 +230,12 @@ class SearchResponse(ResponseBase): organizations: Dict[str, Organization] +class TOTPResponse(ResponseBase): + member_id: str + member: Member + organization: Organization + + class UpdateResponse(ResponseBase): """Response type for `Members.update`. Fields: diff --git a/stytch/b2b/models/passwords_email.py b/stytch/b2b/models/passwords_email.py index e69afdad..027e68b0 100644 --- a/stytch/b2b/models/passwords_email.py +++ b/stytch/b2b/models/passwords_email.py @@ -63,7 +63,9 @@ class ResetStartResponse(ResponseBase): Fields: - member_id: Globally unique UUID that identifies a specific Member. - member_email_id: Globally unique UUID that identifies a member's email + - member: The [Member object](https://stytch.com/docs/b2b/api/member-object) """ # noqa member_id: str member_email_id: str + member: Member diff --git a/stytch/b2b/models/recovery_codes.py b/stytch/b2b/models/recovery_codes.py new file mode 100644 index 00000000..7b9f951c --- /dev/null +++ b/stytch/b2b/models/recovery_codes.py @@ -0,0 +1,64 @@ +# !!! +# WARNING: This file is autogenerated +# Only modify code within MANUAL() sections +# or your changes may be overwritten later! +# !!! + +from __future__ import annotations + +from typing import List, Optional + +from stytch.b2b.models.organizations import Member, Organization +from stytch.b2b.models.sessions import MemberSession +from stytch.core.response_base import ResponseBase + + +class B2BGetResponse(ResponseBase): + """Response type for `RecoveryCodes.b2_b_get`. + Fields: + - member_id: Globally unique UUID that identifies a specific Member. + - member: The [Member object](https://stytch.com/docs/b2b/api/member-object) + - organization: The [Organization object](https://stytch.com/docs/b2b/api/organization-object). + - recovery_codes: An array of recovery codes that can be used to recover a Member's account. + """ # noqa + + member_id: str + member: Member + organization: Organization + recovery_codes: List[str] + + +class RecoverResponse(ResponseBase): + """Response type for `RecoveryCodes.recover`. + Fields: + - member_id: Globally unique UUID that identifies a specific Member. + - member: The [Member object](https://stytch.com/docs/b2b/api/member-object) + - organization: The [Organization object](https://stytch.com/docs/b2b/api/organization-object). + - session_token: A secret token for a given Stytch Session. + - session_jwt: The JSON Web Token (JWT) for a given Stytch Session. + - recovery_codes_remaining: The number of recovery codes remaining for a Member. + - member_session: The [Session object](https://stytch.com/docs/b2b/api/session-object). + """ # noqa + + member_id: str + member: Member + organization: Organization + session_token: str + session_jwt: str + recovery_codes_remaining: int + member_session: Optional[MemberSession] = None + + +class RotateResponse(ResponseBase): + """Response type for `RecoveryCodes.rotate`. + Fields: + - member_id: Globally unique UUID that identifies a specific Member. + - member: The [Member object](https://stytch.com/docs/b2b/api/member-object) + - organization: The [Organization object](https://stytch.com/docs/b2b/api/organization-object). + - recovery_codes: An array of recovery codes that can be used to recover a Member's account. + """ # noqa + + member_id: str + member: Member + organization: Organization + recovery_codes: List[str] diff --git a/stytch/b2b/models/sessions.py b/stytch/b2b/models/sessions.py index 89efa454..bd625e36 100644 --- a/stytch/b2b/models/sessions.py +++ b/stytch/b2b/models/sessions.py @@ -86,7 +86,7 @@ class AuthenticateResponse(ResponseBase): - session_jwt: The JSON Web Token (JWT) for a given Stytch Session. - member: The [Member object](https://stytch.com/docs/b2b/api/member-object) - organization: The [Organization object](https://stytch.com/docs/b2b/api/organization-object). - - verdict: (Coming Soon) If an `authorization_check` is provided in the request and the check succeeds, this field will return + - verdict: If an `authorization_check` is provided in the request and the check succeeds, this field will return the complete list of Roles that gave the Member permission to perform the specified action on the specified Resource. """ # noqa diff --git a/stytch/b2b/models/totps.py b/stytch/b2b/models/totps.py new file mode 100644 index 00000000..29368207 --- /dev/null +++ b/stytch/b2b/models/totps.py @@ -0,0 +1,70 @@ +# !!! +# WARNING: This file is autogenerated +# Only modify code within MANUAL() sections +# or your changes may be overwritten later! +# !!! + +from __future__ import annotations + +from typing import List, Optional + +from stytch.b2b.models.organizations import Member, Organization +from stytch.b2b.models.sessions import MemberSession +from stytch.core.response_base import ResponseBase + + +class AuthenticateResponse(ResponseBase): + """Response type for `TOTPs.authenticate`. + Fields: + - member_id: Globally unique UUID that identifies a specific Member. + - member: The [Member object](https://stytch.com/docs/b2b/api/member-object) + - organization: The [Organization object](https://stytch.com/docs/b2b/api/organization-object). + - session_token: A secret token for a given Stytch Session. + - session_jwt: The JSON Web Token (JWT) for a given Stytch Session. + - member_session: The [Session object](https://stytch.com/docs/b2b/api/session-object). + """ # noqa + + member_id: str + member: Member + organization: Organization + session_token: str + session_jwt: str + member_session: Optional[MemberSession] = None + + +class CreateResponse(ResponseBase): + """Response type for `TOTPs.create`. + Fields: + - member_id: Globally unique UUID that identifies a specific Member. + - totp_registration_id: The unique ID for a TOTP instance. + - secret: The TOTP secret key shared between the authenticator app and the server used to generate TOTP codes. + - qr_code: The QR code image encoded in base64. + - recovery_codes: An array of recovery codes that can be used to recover a Member's account. + - member: The [Member object](https://stytch.com/docs/b2b/api/member-object) + - organization: The [Organization object](https://stytch.com/docs/b2b/api/organization-object). + """ # noqa + + member_id: str + totp_registration_id: str + secret: str + qr_code: str + recovery_codes: List[str] + member: Member + organization: Organization + + +class MigrateResponse(ResponseBase): + """Response type for `TOTPs.migrate`. + Fields: + - member_id: Globally unique UUID that identifies a specific Member. + - member: The [Member object](https://stytch.com/docs/b2b/api/member-object) + - organization: The [Organization object](https://stytch.com/docs/b2b/api/organization-object). + - totp_registration_id: The unique ID for a TOTP instance. + - recovery_codes: An array of recovery codes that can be used to recover a Member's account. + """ # noqa + + member_id: str + member: Member + organization: Organization + totp_registration_id: str + recovery_codes: List[str] diff --git a/stytch/consumer/api/otp_sms.py b/stytch/consumer/api/otp_sms.py index 3287698e..a424c9c4 100644 --- a/stytch/consumer/api/otp_sms.py +++ b/stytch/consumer/api/otp_sms.py @@ -46,6 +46,8 @@ def send( __Note:__ SMS to phone numbers outside of the US and Canada is disabled by default for customers who did not use SMS prior to October 2023. If you're interested in sending international SMS, please reach out to [support@stytch.com](mailto:support@stytch.com?subject=Enable%20international%20SMS). + Even when international SMS is enabled, we do not support sending SMS to countries on our [Unsupported countries list](https://stytch.com/docs/guides/passcodes/unsupported-countries). + ### Add a phone number to an existing user This endpoint also allows you to add a new phone number to an existing Stytch User. Including a `user_id`, `session_token`, or `session_jwt` in your Send one-time passcode by SMS request will add the new, unverified phone number to the existing Stytch User. If the user successfully authenticates within 5 minutes, the new phone number will be marked as verified and remain permanently on the existing Stytch User. Otherwise, it will be removed from the User object, and any subsequent login requests using that phone number will create a new User. @@ -108,6 +110,8 @@ async def send_async( __Note:__ SMS to phone numbers outside of the US and Canada is disabled by default for customers who did not use SMS prior to October 2023. If you're interested in sending international SMS, please reach out to [support@stytch.com](mailto:support@stytch.com?subject=Enable%20international%20SMS). + Even when international SMS is enabled, we do not support sending SMS to countries on our [Unsupported countries list](https://stytch.com/docs/guides/passcodes/unsupported-countries). + ### Add a phone number to an existing user This endpoint also allows you to add a new phone number to an existing Stytch User. Including a `user_id`, `session_token`, or `session_jwt` in your Send one-time passcode by SMS request will add the new, unverified phone number to the existing Stytch User. If the user successfully authenticates within 5 minutes, the new phone number will be marked as verified and remain permanently on the existing Stytch User. Otherwise, it will be removed from the User object, and any subsequent login requests using that phone number will create a new User. @@ -166,6 +170,8 @@ def login_or_create( __Note:__ SMS to phone numbers outside of the US and Canada is disabled by default for customers who did not use SMS prior to October 2023. If you're interested in sending international SMS, please reach out to [support@stytch.com](mailto:support@stytch.com?subject=Enable%20international%20SMS). + Even when international SMS is enabled, we do not support sending SMS to countries on our [Unsupported countries list](https://stytch.com/docs/guides/passcodes/unsupported-countries). + ### Next steps Collect the OTP which was delivered to the User. Call [Authenticate OTP](https://stytch.com/docs/api/authenticate-otp) using the OTP `code` along with the `phone_id` found in the response as the `method_id`. @@ -218,6 +224,8 @@ async def login_or_create_async( __Note:__ SMS to phone numbers outside of the US and Canada is disabled by default for customers who did not use SMS prior to October 2023. If you're interested in sending international SMS, please reach out to [support@stytch.com](mailto:support@stytch.com?subject=Enable%20international%20SMS). + Even when international SMS is enabled, we do not support sending SMS to countries on our [Unsupported countries list](https://stytch.com/docs/guides/passcodes/unsupported-countries). + ### Next steps Collect the OTP which was delivered to the User. Call [Authenticate OTP](https://stytch.com/docs/api/authenticate-otp) using the OTP `code` along with the `phone_id` found in the response as the `method_id`. diff --git a/stytch/consumer/models/oauth.py b/stytch/consumer/models/oauth.py index 825b19b8..4c905c6d 100644 --- a/stytch/consumer/models/oauth.py +++ b/stytch/consumer/models/oauth.py @@ -52,7 +52,7 @@ class AuthenticateResponse(ResponseBase): - session_jwt: The JSON Web Token (JWT) for a given Stytch Session. - provider_values: The `provider_values` object lists relevant identifiers, values, and scopes for a given OAuth provider. For example this object will include a provider's `access_token` that you can use to access the provider's API for a given user. - Note that these values will vary based on the OAuth provider in question, e.g. `id_token` is only returned by OIDC complaint identity providers. + Note that these values will vary based on the OAuth provider in question, e.g. `id_token` is only returned by OIDC compliant identity providers. - user: The `user` object affected by this API call. See the [Get user endpoint](https://stytch.com/docs/api/get-user) for complete response field details. - reset_sessions: Indicates if all other of the User's Sessions need to be reset. You should check this field if you aren't using Stytch's Session product. If you are using Stytch's Session product, we revoke the User's other sessions for you. - oauth_user_registration_id: The unique ID for an OAuth registration. diff --git a/stytch/consumer/models/sessions.py b/stytch/consumer/models/sessions.py index f46dfed4..0b832964 100644 --- a/stytch/consumer/models/sessions.py +++ b/stytch/consumer/models/sessions.py @@ -54,6 +54,7 @@ class AuthenticationFactorDeliveryMethod(str, enum.Enum): OAUTH_SALESFORCE = "oauth_salesforce" OAUTH_YAHOO = "oauth_yahoo" OAUTH_HUBSPOT = "oauth_hubspot" + IMPORTED_AUTH0 = "imported_auth0" class AuthenticationFactorType(str, enum.Enum): @@ -66,6 +67,8 @@ class AuthenticationFactorType(str, enum.Enum): PASSWORD = "password" SIGNATURE_CHALLENGE = "signature_challenge" SSO = "sso" + IMPORTED = "imported" + RECOVERY_CODES = "recovery_codes" class AmazonOAuthFactor(pydantic.BaseModel): @@ -301,12 +304,11 @@ class TwitterOAuthFactor(pydantic.BaseModel): email_id: str provider_subject: str -# MANUAL(WebAuthnFactor)(Types) + class WebAuthnFactor(pydantic.BaseModel): webauthn_registration_id: str domain: str - user_agent: Optional[str] -# ENDMANUAL(WebAuthnFactor) + user_agent: str class YahooOAuthFactor(pydantic.BaseModel): @@ -475,3 +477,13 @@ class RevokeResponse(ResponseBase): """Response type for `Sessions.revoke`. Fields: """ # noqa + + +# MANUAL(WebAuthnFactor)(Types) +class WebAuthnFactor(pydantic.BaseModel): + webauthn_registration_id: str + domain: str + user_agent: Optional[str] + + +# ENDMANUAL(WebAuthnFactor) From 717f176d0d484d3560ae982e1ad63a07a0c85584 Mon Sep 17 00:00:00 2001 From: Taronish Daruwalla Date: Mon, 22 Jan 2024 16:13:17 -0800 Subject: [PATCH 2/6] remove prefix, version --- stytch/b2b/api/recovery_codes.py | 14 +++++++------- stytch/b2b/models/recovery_codes.py | 4 ++-- stytch/version.py | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/stytch/b2b/api/recovery_codes.py b/stytch/b2b/api/recovery_codes.py index 56349908..80def103 100644 --- a/stytch/b2b/api/recovery_codes.py +++ b/stytch/b2b/api/recovery_codes.py @@ -9,7 +9,7 @@ from typing import Any, Dict, Optional from stytch.b2b.models.recovery_codes import ( - B2BGetResponse, + GetResponse, RecoverResponse, RotateResponse, ) @@ -143,11 +143,11 @@ async def recover_async( res = await self.async_client.post(url, data, headers) return RecoverResponse.from_json(res.response.status, res.json) - def b2_b_get( + def get( self, organization_id: str, member_id: str, - ) -> B2BGetResponse: + ) -> GetResponse: """Returns a Member's full set of active recovery codes. Fields: @@ -164,13 +164,13 @@ def b2_b_get( "/v1/b2b/recovery_codes/{organization_id}/{member_id}", data ) res = self.sync_client.get(url, data, headers) - return B2BGetResponse.from_json(res.response.status_code, res.json) + return GetResponse.from_json(res.response.status_code, res.json) - async def b2_b_get_async( + async def get_async( self, organization_id: str, member_id: str, - ) -> B2BGetResponse: + ) -> GetResponse: """Returns a Member's full set of active recovery codes. Fields: @@ -187,7 +187,7 @@ async def b2_b_get_async( "/v1/b2b/recovery_codes/{organization_id}/{member_id}", data ) res = await self.async_client.get(url, data, headers) - return B2BGetResponse.from_json(res.response.status, res.json) + return GetResponse.from_json(res.response.status, res.json) def rotate( self, diff --git a/stytch/b2b/models/recovery_codes.py b/stytch/b2b/models/recovery_codes.py index 7b9f951c..a19f1434 100644 --- a/stytch/b2b/models/recovery_codes.py +++ b/stytch/b2b/models/recovery_codes.py @@ -13,8 +13,8 @@ from stytch.core.response_base import ResponseBase -class B2BGetResponse(ResponseBase): - """Response type for `RecoveryCodes.b2_b_get`. +class GetResponse(ResponseBase): + """Response type for `RecoveryCodes.get`. Fields: - member_id: Globally unique UUID that identifies a specific Member. - member: The [Member object](https://stytch.com/docs/b2b/api/member-object) diff --git a/stytch/version.py b/stytch/version.py index 8026d2a6..70dab034 100644 --- a/stytch/version.py +++ b/stytch/version.py @@ -1 +1 @@ -__version__ = "8.0.4" +__version__ = "8.1.0" From aeb07d2e426bcbe888328b75115fc5cffa57a2c1 Mon Sep 17 00:00:00 2001 From: Taronish Daruwalla Date: Mon, 22 Jan 2024 16:14:49 -0800 Subject: [PATCH 3/6] remove manual webauthn factor --- stytch/consumer/models/sessions.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/stytch/consumer/models/sessions.py b/stytch/consumer/models/sessions.py index 0b832964..5015c716 100644 --- a/stytch/consumer/models/sessions.py +++ b/stytch/consumer/models/sessions.py @@ -478,12 +478,3 @@ class RevokeResponse(ResponseBase): Fields: """ # noqa - -# MANUAL(WebAuthnFactor)(Types) -class WebAuthnFactor(pydantic.BaseModel): - webauthn_registration_id: str - domain: str - user_agent: Optional[str] - - -# ENDMANUAL(WebAuthnFactor) From e667e8a97ec38e9b4a94cc2106af24cdc8a7eda3 Mon Sep 17 00:00:00 2001 From: Taronish Daruwalla Date: Mon, 22 Jan 2024 16:16:13 -0800 Subject: [PATCH 4/6] link development.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2ea7b6af..20c6320e 100644 --- a/README.md +++ b/README.md @@ -123,7 +123,7 @@ If you've found a security vulnerability, please follow our [responsible disclos ## Development -See DEVELOPMENT.md +See [DEVELOPMENT.md](DEVELOPMENT.md) ## Code of Conduct From b188b7c24cd1d642104ca8f6f91f177c24503444 Mon Sep 17 00:00:00 2001 From: Taronish Daruwalla Date: Tue, 23 Jan 2024 09:01:57 -0800 Subject: [PATCH 5/6] optional webauthn user agent --- stytch/consumer/models/sessions.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/stytch/consumer/models/sessions.py b/stytch/consumer/models/sessions.py index 5015c716..3b003331 100644 --- a/stytch/consumer/models/sessions.py +++ b/stytch/consumer/models/sessions.py @@ -308,7 +308,7 @@ class TwitterOAuthFactor(pydantic.BaseModel): class WebAuthnFactor(pydantic.BaseModel): webauthn_registration_id: str domain: str - user_agent: str + user_agent: Optional[str] = None class YahooOAuthFactor(pydantic.BaseModel): @@ -477,4 +477,3 @@ class RevokeResponse(ResponseBase): """Response type for `Sessions.revoke`. Fields: """ # noqa - From 7c9488414187178cee5c1aa88de62da4a1c48076 Mon Sep 17 00:00:00 2001 From: Taronish Daruwalla Date: Tue, 23 Jan 2024 09:55:02 -0800 Subject: [PATCH 6/6] rename delete based on new translation rule --- stytch/b2b/api/organizations_members.py | 20 ++++++++++---------- stytch/b2b/models/organizations_members.py | 18 +++++++++--------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/stytch/b2b/api/organizations_members.py b/stytch/b2b/api/organizations_members.py index 91e9dc86..5dba348a 100644 --- a/stytch/b2b/api/organizations_members.py +++ b/stytch/b2b/api/organizations_members.py @@ -18,13 +18,13 @@ DeletePasswordResponse, DeleteRequestOptions, DeleteResponse, + DeleteTOTPRequestOptions, + DeleteTOTPResponse, GetResponse, ReactivateRequestOptions, ReactivateResponse, SearchRequestOptions, SearchResponse, - TOTPRequestOptions, - TOTPResponse, UpdateRequestOptions, UpdateResponse, ) @@ -422,12 +422,12 @@ async def delete_mfa_phone_number_async( res = await self.async_client.delete(url, headers) return DeleteMFAPhoneNumberResponse.from_json(res.response.status, res.json) - def totp( + def delete_totp( self, organization_id: str, member_id: str, - method_options: Optional[TOTPRequestOptions] = None, - ) -> TOTPResponse: + method_options: Optional[DeleteTOTPRequestOptions] = None, + ) -> DeleteTOTPResponse: headers: Dict[str, str] = {} if method_options is not None: headers = method_options.add_headers(headers) @@ -440,14 +440,14 @@ def totp( "/v1/b2b/organizations/{organization_id}/members/{member_id}/totp", data ) res = self.sync_client.delete(url, headers) - return TOTPResponse.from_json(res.response.status_code, res.json) + return DeleteTOTPResponse.from_json(res.response.status_code, res.json) - async def totp_async( + async def delete_totp_async( self, organization_id: str, member_id: str, - method_options: Optional[TOTPRequestOptions] = None, - ) -> TOTPResponse: + method_options: Optional[DeleteTOTPRequestOptions] = None, + ) -> DeleteTOTPResponse: headers: Dict[str, str] = {} if method_options is not None: headers = method_options.add_headers(headers) @@ -460,7 +460,7 @@ async def totp_async( "/v1/b2b/organizations/{organization_id}/members/{member_id}/totp", data ) res = await self.async_client.delete(url, headers) - return TOTPResponse.from_json(res.response.status, res.json) + return DeleteTOTPResponse.from_json(res.response.status, res.json) def search( self, diff --git a/stytch/b2b/models/organizations_members.py b/stytch/b2b/models/organizations_members.py index 7a11fa56..39d96425 100644 --- a/stytch/b2b/models/organizations_members.py +++ b/stytch/b2b/models/organizations_members.py @@ -79,7 +79,7 @@ def add_headers(self, headers: Dict[str, str]) -> Dict[str, str]: return headers -class ReactivateRequestOptions(pydantic.BaseModel): +class DeleteTOTPRequestOptions(pydantic.BaseModel): """ Fields: - authorization: Optional authorization object. @@ -95,7 +95,7 @@ def add_headers(self, headers: Dict[str, str]) -> Dict[str, str]: return headers -class SearchRequestOptions(pydantic.BaseModel): +class ReactivateRequestOptions(pydantic.BaseModel): """ Fields: - authorization: Optional authorization object. @@ -111,7 +111,7 @@ def add_headers(self, headers: Dict[str, str]) -> Dict[str, str]: return headers -class TOTPRequestOptions(pydantic.BaseModel): +class SearchRequestOptions(pydantic.BaseModel): """ Fields: - authorization: Optional authorization object. @@ -191,6 +191,12 @@ class DeleteResponse(ResponseBase): member_id: str +class DeleteTOTPResponse(ResponseBase): + member_id: str + member: Member + organization: Organization + + class GetResponse(ResponseBase): """Response type for `Members.dangerously_get`, `Members.get`. Fields: @@ -230,12 +236,6 @@ class SearchResponse(ResponseBase): organizations: Dict[str, Organization] -class TOTPResponse(ResponseBase): - member_id: str - member: Member - organization: Organization - - class UpdateResponse(ResponseBase): """Response type for `Members.update`. Fields: