From 583c55ffbaa8b6657f162b037f52f9ad78996509 Mon Sep 17 00:00:00 2001 From: etaylormcgregor-stytch Date: Thu, 14 Nov 2024 13:15:59 -0500 Subject: [PATCH] Add email otp to b2b (#225) * add email otp to b2b * update comments --- stytch/b2b/api/organizations.py | 12 +- stytch/b2b/api/otp.py | 6 + stytch/b2b/api/otp_email.py | 263 ++++++++++++++++++++ stytch/b2b/api/otp_email_discovery.py | 130 ++++++++++ stytch/b2b/api/passwords_discovery_email.py | 12 +- stytch/b2b/api/passwords_email.py | 12 +- stytch/b2b/models/otp_email.py | 71 ++++++ stytch/b2b/models/otp_email_discovery.py | 49 ++++ stytch/consumer/api/passwords_email.py | 12 +- stytch/consumer/api/webauthn.py | 24 +- stytch/consumer/models/m2m.py | 1 + stytch/core/response_base.py | 3 +- stytch/version.py | 2 +- 13 files changed, 558 insertions(+), 39 deletions(-) create mode 100644 stytch/b2b/api/otp_email.py create mode 100644 stytch/b2b/api/otp_email_discovery.py create mode 100644 stytch/b2b/models/otp_email.py create mode 100644 stytch/b2b/models/otp_email_discovery.py diff --git a/stytch/b2b/api/organizations.py b/stytch/b2b/api/organizations.py index 6cf7ace3..a9da8c7f 100644 --- a/stytch/b2b/api/organizations.py +++ b/stytch/b2b/api/organizations.py @@ -501,9 +501,9 @@ def update( if sso_jit_provisioning is not None: data["sso_jit_provisioning"] = sso_jit_provisioning if sso_jit_provisioning_allowed_connections is not None: - data[ - "sso_jit_provisioning_allowed_connections" - ] = sso_jit_provisioning_allowed_connections + data["sso_jit_provisioning_allowed_connections"] = ( + sso_jit_provisioning_allowed_connections + ) if email_allowed_domains is not None: data["email_allowed_domains"] = email_allowed_domains if email_jit_provisioning is not None: @@ -689,9 +689,9 @@ async def update_async( if sso_jit_provisioning is not None: data["sso_jit_provisioning"] = sso_jit_provisioning if sso_jit_provisioning_allowed_connections is not None: - data[ - "sso_jit_provisioning_allowed_connections" - ] = sso_jit_provisioning_allowed_connections + data["sso_jit_provisioning_allowed_connections"] = ( + sso_jit_provisioning_allowed_connections + ) if email_allowed_domains is not None: data["email_allowed_domains"] = email_allowed_domains if email_jit_provisioning is not None: diff --git a/stytch/b2b/api/otp.py b/stytch/b2b/api/otp.py index e87b3347..4352c5bf 100644 --- a/stytch/b2b/api/otp.py +++ b/stytch/b2b/api/otp.py @@ -6,6 +6,7 @@ from __future__ import annotations +from stytch.b2b.api.otp_email import Email from stytch.b2b.api.otp_sms import Sms from stytch.core.api_base import ApiBase from stytch.core.http.client import AsyncClient, SyncClient @@ -23,3 +24,8 @@ def __init__( sync_client=self.sync_client, async_client=self.async_client, ) + self.email = Email( + api_base=self.api_base, + sync_client=self.sync_client, + async_client=self.async_client, + ) diff --git a/stytch/b2b/api/otp_email.py b/stytch/b2b/api/otp_email.py new file mode 100644 index 00000000..1812f2d6 --- /dev/null +++ b/stytch/b2b/api/otp_email.py @@ -0,0 +1,263 @@ +# !!! +# 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, Union + +from stytch.b2b.api.otp_email_discovery import Discovery +from stytch.b2b.models.otp_email import ( + AuthenticateRequestLocale, + AuthenticateResponse, + LoginOrSignupRequestLocale, + LoginOrSignupResponse, +) +from stytch.core.api_base import ApiBase +from stytch.core.http.client import AsyncClient, SyncClient + + +class Email: + 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 + self.discovery = Discovery( + api_base=self.api_base, + sync_client=self.sync_client, + async_client=self.async_client, + ) + + def login_or_signup( + self, + organization_id: str, + email_address: str, + login_template_id: Optional[str] = None, + signup_template_id: Optional[str] = None, + locale: Optional[Union[LoginOrSignupRequestLocale, str]] = None, + ) -> LoginOrSignupResponse: + """Send either a login or signup email OTP to a Member. A new, pending, or invited Member will receive a signup email OTP. Non-active members will have a pending status until they successfully authenticate. An active Member will receive a login email OTP. + + The OTP is valid for 10 minutes. Only the most recently sent OTP is valid: when an OTP is sent, all OTPs previously sent to the same email address are invalidated, even if unused or unexpired. + + 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. + - email_address: The email address of the Member. + - login_template_id: Use a custom template for login emails. By default, it will use your default email template. The template must be a template using our built-in customizations or a custom HTML email for OTP - Login. + - signup_template_id: Use a custom template for signup emails. By default, it will use your default email template. The template must be a template using our built-in customizations or a custom HTML email for OTP - Signup. + - locale: Used to determine which language to use when sending the user this delivery method. Parameter is a [IETF BCP 47 language tag](https://www.w3.org/International/articles/language-tags/), e.g. `"en"`. + + Currently supported languages are English (`"en"`), Spanish (`"es"`), and Brazilian Portuguese (`"pt-br"`); if no value is provided, the copy defaults to English. + + Request support for additional languages [here](https://docs.google.com/forms/d/e/1FAIpQLScZSpAu_m2AmLXRT3F3kap-s_mcV6UTBitYn6CdyWP0-o7YjQ/viewform?usp=sf_link")! + + """ # noqa + headers: Dict[str, str] = {} + data: Dict[str, Any] = { + "organization_id": organization_id, + "email_address": email_address, + } + if login_template_id is not None: + data["login_template_id"] = login_template_id + if signup_template_id is not None: + data["signup_template_id"] = signup_template_id + if locale is not None: + data["locale"] = locale + + url = self.api_base.url_for("/v1/b2b/otps/email/login_or_signup", data) + res = self.sync_client.post(url, data, headers) + return LoginOrSignupResponse.from_json(res.response.status_code, res.json) + + async def login_or_signup_async( + self, + organization_id: str, + email_address: str, + login_template_id: Optional[str] = None, + signup_template_id: Optional[str] = None, + locale: Optional[LoginOrSignupRequestLocale] = None, + ) -> LoginOrSignupResponse: + """Send either a login or signup email OTP to a Member. A new, pending, or invited Member will receive a signup email OTP. Non-active members will have a pending status until they successfully authenticate. An active Member will receive a login email OTP. + + The OTP is valid for 10 minutes. Only the most recently sent OTP is valid: when an OTP is sent, all OTPs previously sent to the same email address are invalidated, even if unused or unexpired. + + 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. + - email_address: The email address of the Member. + - login_template_id: Use a custom template for login emails. By default, it will use your default email template. The template must be a template using our built-in customizations or a custom HTML email for OTP - Login. + - signup_template_id: Use a custom template for signup emails. By default, it will use your default email template. The template must be a template using our built-in customizations or a custom HTML email for OTP - Signup. + - locale: Used to determine which language to use when sending the user this delivery method. Parameter is a [IETF BCP 47 language tag](https://www.w3.org/International/articles/language-tags/), e.g. `"en"`. + + Currently supported languages are English (`"en"`), Spanish (`"es"`), and Brazilian Portuguese (`"pt-br"`); if no value is provided, the copy defaults to English. + + Request support for additional languages [here](https://docs.google.com/forms/d/e/1FAIpQLScZSpAu_m2AmLXRT3F3kap-s_mcV6UTBitYn6CdyWP0-o7YjQ/viewform?usp=sf_link")! + + """ # noqa + headers: Dict[str, str] = {} + data: Dict[str, Any] = { + "organization_id": organization_id, + "email_address": email_address, + } + if login_template_id is not None: + data["login_template_id"] = login_template_id + if signup_template_id is not None: + data["signup_template_id"] = signup_template_id + if locale is not None: + data["locale"] = locale + + url = self.api_base.url_for("/v1/b2b/otps/email/login_or_signup", data) + res = await self.async_client.post(url, data, headers) + return LoginOrSignupResponse.from_json(res.response.status, res.json) + + def authenticate( + self, + organization_id: str, + email_address: str, + code: str, + session_token: Optional[str] = None, + session_jwt: Optional[str] = None, + intermediate_session_token: Optional[str] = None, + session_duration_minutes: Optional[int] = None, + session_custom_claims: Optional[Dict[str, Any]] = None, + locale: Optional[Union[AuthenticateRequestLocale, str]] = None, + ) -> AuthenticateResponse: + """Authenticate a with a one-time passcode (OTP). This endpoint requires an OTP that is not expired or previously used. + OTPs have a default expiry of 10 minutes. If the Member’s status is `pending` or `invited`, they will be updated to `active`. + Provide the `session_duration_minutes` parameter to set the lifetime of the session. If the `session_duration_minutes` parameter is not specified, a Stytch session will be created with a 60 minute duration. + + If the Member is required to complete MFA to log in to the, the returned value of `member_authenticated` will be `false`, and an `intermediate_session_token` will be returned. + The `intermediate_session_token` can be passed into the [OTP SMS Authenticate endpoint](https://stytch.com/docs/b2b/api/authenticate-otp-sms), [TOTP Authenticate endpoint](https://stytch.com/docs/b2b/api/authenticate-totp), + or [Recovery Codes Recover endpoint](https://stytch.com/docs/b2b/api/recovery-codes-recover) to complete the MFA step and acquire a full member session. + The `intermediate_session_token` can also be used with the [Exchange Intermediate Session endpoint](https://stytch.com/docs/b2b/api/exchange-intermediate-session) or the [Create Organization via Discovery endpoint](https://stytch.com/docs/b2b/api/create-organization-via-discovery) to join a different Organization or create a new one. + The `session_duration_minutes` and `session_custom_claims` parameters will be ignored. + + If a valid `session_token` or `session_jwt` is passed in, the Member will not be required to complete an MFA step. + + 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. + - email_address: The email address of the Member. + - code: The code to authenticate. + - session_token: A secret token for a given Stytch Session. + - session_jwt: The JSON Web Token (JWT) for a given Stytch Session. + - 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), [TOTP Authenticate endpoint](https://stytch.com/docs/b2b/api/authenticate-totp), or [Recovery Codes Recover endpoint](https://stytch.com/docs/b2b/api/recovery-codes-recover) to complete an MFA flow and log in to the Organization. It can also be used with 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_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. + - locale: Used to determine which language to use when sending the user this delivery method. Parameter is a [IETF BCP 47 language tag](https://www.w3.org/International/articles/language-tags/), e.g. `"en"`. + + Currently supported languages are English (`"en"`), Spanish (`"es"`), and Brazilian Portuguese (`"pt-br"`); if no value is provided, the copy defaults to English. + + Request support for additional languages [here](https://docs.google.com/forms/d/e/1FAIpQLScZSpAu_m2AmLXRT3F3kap-s_mcV6UTBitYn6CdyWP0-o7YjQ/viewform?usp=sf_link")! + + """ # noqa + headers: Dict[str, str] = {} + data: Dict[str, Any] = { + "organization_id": organization_id, + "email_address": email_address, + "code": code, + } + if session_token is not None: + data["session_token"] = session_token + if session_jwt is not None: + data["session_jwt"] = session_jwt + if intermediate_session_token is not None: + data["intermediate_session_token"] = intermediate_session_token + 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 locale is not None: + data["locale"] = locale + + url = self.api_base.url_for("/v1/b2b/otps/email/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, + email_address: str, + code: str, + session_token: Optional[str] = None, + session_jwt: Optional[str] = None, + intermediate_session_token: Optional[str] = None, + session_duration_minutes: Optional[int] = None, + session_custom_claims: Optional[Dict[str, Any]] = None, + locale: Optional[AuthenticateRequestLocale] = None, + ) -> AuthenticateResponse: + """Authenticate a with a one-time passcode (OTP). This endpoint requires an OTP that is not expired or previously used. + OTPs have a default expiry of 10 minutes. If the Member’s status is `pending` or `invited`, they will be updated to `active`. + Provide the `session_duration_minutes` parameter to set the lifetime of the session. If the `session_duration_minutes` parameter is not specified, a Stytch session will be created with a 60 minute duration. + + If the Member is required to complete MFA to log in to the, the returned value of `member_authenticated` will be `false`, and an `intermediate_session_token` will be returned. + The `intermediate_session_token` can be passed into the [OTP SMS Authenticate endpoint](https://stytch.com/docs/b2b/api/authenticate-otp-sms), [TOTP Authenticate endpoint](https://stytch.com/docs/b2b/api/authenticate-totp), + or [Recovery Codes Recover endpoint](https://stytch.com/docs/b2b/api/recovery-codes-recover) to complete the MFA step and acquire a full member session. + The `intermediate_session_token` can also be used with the [Exchange Intermediate Session endpoint](https://stytch.com/docs/b2b/api/exchange-intermediate-session) or the [Create Organization via Discovery endpoint](https://stytch.com/docs/b2b/api/create-organization-via-discovery) to join a different Organization or create a new one. + The `session_duration_minutes` and `session_custom_claims` parameters will be ignored. + + If a valid `session_token` or `session_jwt` is passed in, the Member will not be required to complete an MFA step. + + 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. + - email_address: The email address of the Member. + - code: The code to authenticate. + - session_token: A secret token for a given Stytch Session. + - session_jwt: The JSON Web Token (JWT) for a given Stytch Session. + - 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), [TOTP Authenticate endpoint](https://stytch.com/docs/b2b/api/authenticate-totp), or [Recovery Codes Recover endpoint](https://stytch.com/docs/b2b/api/recovery-codes-recover) to complete an MFA flow and log in to the Organization. It can also be used with 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_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. + - locale: Used to determine which language to use when sending the user this delivery method. Parameter is a [IETF BCP 47 language tag](https://www.w3.org/International/articles/language-tags/), e.g. `"en"`. + + Currently supported languages are English (`"en"`), Spanish (`"es"`), and Brazilian Portuguese (`"pt-br"`); if no value is provided, the copy defaults to English. + + Request support for additional languages [here](https://docs.google.com/forms/d/e/1FAIpQLScZSpAu_m2AmLXRT3F3kap-s_mcV6UTBitYn6CdyWP0-o7YjQ/viewform?usp=sf_link")! + + """ # noqa + headers: Dict[str, str] = {} + data: Dict[str, Any] = { + "organization_id": organization_id, + "email_address": email_address, + "code": code, + } + if session_token is not None: + data["session_token"] = session_token + if session_jwt is not None: + data["session_jwt"] = session_jwt + if intermediate_session_token is not None: + data["intermediate_session_token"] = intermediate_session_token + 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 locale is not None: + data["locale"] = locale + + url = self.api_base.url_for("/v1/b2b/otps/email/authenticate", data) + res = await self.async_client.post(url, data, headers) + return AuthenticateResponse.from_json(res.response.status, res.json) diff --git a/stytch/b2b/api/otp_email_discovery.py b/stytch/b2b/api/otp_email_discovery.py new file mode 100644 index 00000000..b138af04 --- /dev/null +++ b/stytch/b2b/api/otp_email_discovery.py @@ -0,0 +1,130 @@ +# !!! +# 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, Union + +from stytch.b2b.models.otp_email_discovery import ( + AuthenticateResponse, + SendRequestLocale, + SendResponse, +) +from stytch.core.api_base import ApiBase +from stytch.core.http.client import AsyncClient, SyncClient + + +class Discovery: + 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 send( + self, + email_address: str, + login_template_id: Optional[str] = None, + locale: Optional[Union[SendRequestLocale, str]] = None, + ) -> SendResponse: + """Send a discovery OTP to an email address. The OTP is valid for 10 minutes. Only the most recently sent OTP is valid: when an OTP is sent, all OTPs previously sent to the same email address are invalidated, even if unused or unexpired. + + Fields: + - email_address: The email address to start the discovery flow for. + - login_template_id: Use a custom template for login emails. By default, it will use your default email template. The template must be a template using our built-in customizations or a custom HTML email for OTP - Login. + - locale: Used to determine which language to use when sending the user this delivery method. Parameter is a [IETF BCP 47 language tag](https://www.w3.org/International/articles/language-tags/), e.g. `"en"`. + + Currently supported languages are English (`"en"`), Spanish (`"es"`), and Brazilian Portuguese (`"pt-br"`); if no value is provided, the copy defaults to English. + + Request support for additional languages [here](https://docs.google.com/forms/d/e/1FAIpQLScZSpAu_m2AmLXRT3F3kap-s_mcV6UTBitYn6CdyWP0-o7YjQ/viewform?usp=sf_link")! + + """ # noqa + headers: Dict[str, str] = {} + data: Dict[str, Any] = { + "email_address": email_address, + } + if login_template_id is not None: + data["login_template_id"] = login_template_id + if locale is not None: + data["locale"] = locale + + url = self.api_base.url_for("/v1/b2b/otps/email/discovery/send", data) + res = self.sync_client.post(url, data, headers) + return SendResponse.from_json(res.response.status_code, res.json) + + async def send_async( + self, + email_address: str, + login_template_id: Optional[str] = None, + locale: Optional[SendRequestLocale] = None, + ) -> SendResponse: + """Send a discovery OTP to an email address. The OTP is valid for 10 minutes. Only the most recently sent OTP is valid: when an OTP is sent, all OTPs previously sent to the same email address are invalidated, even if unused or unexpired. + + Fields: + - email_address: The email address to start the discovery flow for. + - login_template_id: Use a custom template for login emails. By default, it will use your default email template. The template must be a template using our built-in customizations or a custom HTML email for OTP - Login. + - locale: Used to determine which language to use when sending the user this delivery method. Parameter is a [IETF BCP 47 language tag](https://www.w3.org/International/articles/language-tags/), e.g. `"en"`. + + Currently supported languages are English (`"en"`), Spanish (`"es"`), and Brazilian Portuguese (`"pt-br"`); if no value is provided, the copy defaults to English. + + Request support for additional languages [here](https://docs.google.com/forms/d/e/1FAIpQLScZSpAu_m2AmLXRT3F3kap-s_mcV6UTBitYn6CdyWP0-o7YjQ/viewform?usp=sf_link")! + + """ # noqa + headers: Dict[str, str] = {} + data: Dict[str, Any] = { + "email_address": email_address, + } + if login_template_id is not None: + data["login_template_id"] = login_template_id + if locale is not None: + data["locale"] = locale + + url = self.api_base.url_for("/v1/b2b/otps/email/discovery/send", data) + res = await self.async_client.post(url, data, headers) + return SendResponse.from_json(res.response.status, res.json) + + def authenticate( + self, + email_address: str, + code: str, + ) -> AuthenticateResponse: + """Authenticates the OTP and returns an intermediate session token. Intermediate session tokens can be used for various Discovery login flows and are valid for 10 minutes. + + Fields: + - email_address: The email address of the Member. + - code: The code to authenticate. + """ # noqa + headers: Dict[str, str] = {} + data: Dict[str, Any] = { + "email_address": email_address, + "code": code, + } + + url = self.api_base.url_for("/v1/b2b/otps/email/discovery/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, + email_address: str, + code: str, + ) -> AuthenticateResponse: + """Authenticates the OTP and returns an intermediate session token. Intermediate session tokens can be used for various Discovery login flows and are valid for 10 minutes. + + Fields: + - email_address: The email address of the Member. + - code: The code to authenticate. + """ # noqa + headers: Dict[str, str] = {} + data: Dict[str, Any] = { + "email_address": email_address, + "code": code, + } + + url = self.api_base.url_for("/v1/b2b/otps/email/discovery/authenticate", data) + res = await self.async_client.post(url, data, headers) + return AuthenticateResponse.from_json(res.response.status, res.json) diff --git a/stytch/b2b/api/passwords_discovery_email.py b/stytch/b2b/api/passwords_discovery_email.py index b0c94fd6..aebf7b2e 100644 --- a/stytch/b2b/api/passwords_discovery_email.py +++ b/stytch/b2b/api/passwords_discovery_email.py @@ -71,9 +71,9 @@ def reset_start( if reset_password_template_id is not None: data["reset_password_template_id"] = reset_password_template_id if reset_password_expiration_minutes is not None: - data[ - "reset_password_expiration_minutes" - ] = reset_password_expiration_minutes + data["reset_password_expiration_minutes"] = ( + reset_password_expiration_minutes + ) if pkce_code_challenge is not None: data["pkce_code_challenge"] = pkce_code_challenge if locale is not None: @@ -132,9 +132,9 @@ async def reset_start_async( if reset_password_template_id is not None: data["reset_password_template_id"] = reset_password_template_id if reset_password_expiration_minutes is not None: - data[ - "reset_password_expiration_minutes" - ] = reset_password_expiration_minutes + data["reset_password_expiration_minutes"] = ( + reset_password_expiration_minutes + ) if pkce_code_challenge is not None: data["pkce_code_challenge"] = pkce_code_challenge if locale is not None: diff --git a/stytch/b2b/api/passwords_email.py b/stytch/b2b/api/passwords_email.py index c2926560..e907cbb6 100644 --- a/stytch/b2b/api/passwords_email.py +++ b/stytch/b2b/api/passwords_email.py @@ -75,9 +75,9 @@ def reset_start( if reset_password_redirect_url is not None: data["reset_password_redirect_url"] = reset_password_redirect_url if reset_password_expiration_minutes is not None: - data[ - "reset_password_expiration_minutes" - ] = reset_password_expiration_minutes + data["reset_password_expiration_minutes"] = ( + reset_password_expiration_minutes + ) if code_challenge is not None: data["code_challenge"] = code_challenge if login_redirect_url is not None: @@ -138,9 +138,9 @@ async def reset_start_async( if reset_password_redirect_url is not None: data["reset_password_redirect_url"] = reset_password_redirect_url if reset_password_expiration_minutes is not None: - data[ - "reset_password_expiration_minutes" - ] = reset_password_expiration_minutes + data["reset_password_expiration_minutes"] = ( + reset_password_expiration_minutes + ) if code_challenge is not None: data["code_challenge"] = code_challenge if login_redirect_url is not None: diff --git a/stytch/b2b/models/otp_email.py b/stytch/b2b/models/otp_email.py new file mode 100644 index 00000000..e854e8a6 --- /dev/null +++ b/stytch/b2b/models/otp_email.py @@ -0,0 +1,71 @@ +# !!! +# WARNING: This file is autogenerated +# Only modify code within MANUAL() sections +# or your changes may be overwritten later! +# !!! + +from __future__ import annotations + +import enum +from typing import Optional + +from stytch.b2b.models.mfa import MfaRequired +from stytch.b2b.models.organizations import Member, Organization +from stytch.b2b.models.sessions import MemberSession +from stytch.core.response_base import ResponseBase + + +class AuthenticateRequestLocale(str, enum.Enum): + EN = "en" + ES = "es" + PTBR = "pt-br" + + +class LoginOrSignupRequestLocale(str, enum.Enum): + EN = "en" + ES = "es" + PTBR = "pt-br" + + +class AuthenticateResponse(ResponseBase): + """Response type for `Email.authenticate`. + Fields: + - member_id: Globally unique UUID that identifies a specific Member. + - method_id: The email or device involved in the authentication. + - 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: The [Member object](https://stytch.com/docs/b2b/api/member-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). + - organization: The [Organization object](https://stytch.com/docs/b2b/api/organization-object). + - 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), [TOTP Authenticate endpoint](https://stytch.com/docs/b2b/api/authenticate-totp), or [Recovery Codes Recover endpoint](https://stytch.com/docs/b2b/api/recovery-codes-recover) to complete an MFA flow and log in to the Organization. It can also be used with 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. + - member_authenticated: Indicates whether the Member is fully authenticated. If false, the Member needs to complete an MFA step to log in to the Organization. + - mfa_required: Information about the MFA requirements of the Organization and the Member's options for fulfilling MFA. + """ # noqa + + member_id: str + method_id: str + organization_id: str + member: Member + session_token: str + session_jwt: str + member_session: MemberSession + organization: Organization + intermediate_session_token: str + member_authenticated: bool + mfa_required: Optional[MfaRequired] = None + + +class LoginOrSignupResponse(ResponseBase): + """Response type for `Email.login_or_signup`. + Fields: + - member_id: Globally unique UUID that identifies a specific Member. + - member_created: A flag indicating `true` if a new Member object was created and `false` if the Member object already existed. + - 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 + member_created: bool + member: Member + organization: Organization diff --git a/stytch/b2b/models/otp_email_discovery.py b/stytch/b2b/models/otp_email_discovery.py new file mode 100644 index 00000000..d09c8d29 --- /dev/null +++ b/stytch/b2b/models/otp_email_discovery.py @@ -0,0 +1,49 @@ +# !!! +# WARNING: This file is autogenerated +# Only modify code within MANUAL() sections +# or your changes may be overwritten later! +# !!! + +from __future__ import annotations + +import enum +from typing import List + +from stytch.b2b.models.discovery import DiscoveredOrganization +from stytch.core.response_base import ResponseBase + + +class SendRequestLocale(str, enum.Enum): + EN = "en" + ES = "es" + PTBR = "pt-br" + + +class AuthenticateResponse(ResponseBase): + """Response type for `Discovery.authenticate`. + Fields: + - 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), [TOTP Authenticate endpoint](https://stytch.com/docs/b2b/api/authenticate-totp), or [Recovery Codes Recover endpoint](https://stytch.com/docs/b2b/api/recovery-codes-recover) to complete an MFA flow and log in to the Organization. It can also be used with 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. + - email_address: The email address. + - discovered_organizations: An array of `discovered_organization` objects tied to the `intermediate_session_token`, `session_token`, or `session_jwt`. See the [Discovered Organization Object](https://stytch.com/docs/b2b/api/discovered-organization-object) for complete details. + + Note that Organizations will only appear here under any of the following conditions: + 1. The end user is already a Member of the Organization. + 2. The end user is invited to the Organization. + 3. The end user can join the Organization because: + + a) The Organization allows JIT provisioning. + + b) The Organizations' allowed domains list contains the Member's email domain. + + c) The Organization has at least one other Member with a verified email address with the same domain as the end user (to prevent phishing attacks). + """ # noqa + + intermediate_session_token: str + email_address: str + discovered_organizations: List[DiscoveredOrganization] + + +class SendResponse(ResponseBase): + """Response type for `Discovery.send`. + Fields: + """ # noqa diff --git a/stytch/consumer/api/passwords_email.py b/stytch/consumer/api/passwords_email.py index b0414254..b00f378d 100644 --- a/stytch/consumer/api/passwords_email.py +++ b/stytch/consumer/api/passwords_email.py @@ -70,9 +70,9 @@ def reset_start( if reset_password_redirect_url is not None: data["reset_password_redirect_url"] = reset_password_redirect_url if reset_password_expiration_minutes is not None: - data[ - "reset_password_expiration_minutes" - ] = reset_password_expiration_minutes + data["reset_password_expiration_minutes"] = ( + reset_password_expiration_minutes + ) if code_challenge is not None: data["code_challenge"] = code_challenge if attributes is not None: @@ -133,9 +133,9 @@ async def reset_start_async( if reset_password_redirect_url is not None: data["reset_password_redirect_url"] = reset_password_redirect_url if reset_password_expiration_minutes is not None: - data[ - "reset_password_expiration_minutes" - ] = reset_password_expiration_minutes + data["reset_password_expiration_minutes"] = ( + reset_password_expiration_minutes + ) if code_challenge is not None: data["code_challenge"] = code_challenge if attributes is not None: diff --git a/stytch/consumer/api/webauthn.py b/stytch/consumer/api/webauthn.py index ca82d3d1..304a697a 100644 --- a/stytch/consumer/api/webauthn.py +++ b/stytch/consumer/api/webauthn.py @@ -67,9 +67,9 @@ def register_start( if authenticator_type is not None: data["authenticator_type"] = authenticator_type if return_passkey_credential_options is not None: - data[ - "return_passkey_credential_options" - ] = return_passkey_credential_options + data["return_passkey_credential_options"] = ( + return_passkey_credential_options + ) if override_id is not None: data["override_id"] = override_id if override_name is not None: @@ -121,9 +121,9 @@ async def register_start_async( if authenticator_type is not None: data["authenticator_type"] = authenticator_type if return_passkey_credential_options is not None: - data[ - "return_passkey_credential_options" - ] = return_passkey_credential_options + data["return_passkey_credential_options"] = ( + return_passkey_credential_options + ) if override_id is not None: data["override_id"] = override_id if override_name is not None: @@ -260,9 +260,9 @@ def authenticate_start( if user_id is not None: data["user_id"] = user_id if return_passkey_credential_options is not None: - data[ - "return_passkey_credential_options" - ] = return_passkey_credential_options + data["return_passkey_credential_options"] = ( + return_passkey_credential_options + ) url = self.api_base.url_for("/v1/webauthn/authenticate/start", data) res = self.sync_client.post(url, data, headers) @@ -295,9 +295,9 @@ async def authenticate_start_async( if user_id is not None: data["user_id"] = user_id if return_passkey_credential_options is not None: - data[ - "return_passkey_credential_options" - ] = return_passkey_credential_options + data["return_passkey_credential_options"] = ( + return_passkey_credential_options + ) url = self.api_base.url_for("/v1/webauthn/authenticate/start", data) res = await self.async_client.post(url, data, headers) diff --git a/stytch/consumer/models/m2m.py b/stytch/consumer/models/m2m.py index 5e220847..16a6288b 100644 --- a/stytch/consumer/models/m2m.py +++ b/stytch/consumer/models/m2m.py @@ -135,6 +135,7 @@ class GetTokenResponse(ResponseBase): # ENDMANUAL(GetTokenResponse) + # MANUAL(M2MJWTClaims)(TYPES) # ADDIMPORT: from typing import Any, Dict, List, Optional class M2MJWTClaims(pydantic.BaseModel): diff --git a/stytch/core/response_base.py b/stytch/core/response_base.py index f3cb0ea0..ad90c40d 100644 --- a/stytch/core/response_base.py +++ b/stytch/core/response_base.py @@ -7,8 +7,7 @@ import pydantic -class ResponseError(ValueError): - ... +class ResponseError(ValueError): ... class ResponseBase(pydantic.BaseModel): diff --git a/stytch/version.py b/stytch/version.py index 9a03deb9..90c432d6 100644 --- a/stytch/version.py +++ b/stytch/version.py @@ -1 +1 @@ -__version__ = "11.9.0" +__version__ = "11.10.0"