Skip to content

Commit

Permalink
Add in cross-org password sdk support
Browse files Browse the repository at this point in the history
  • Loading branch information
bgier-stytch committed Oct 21, 2024
1 parent 3e7155d commit fccb84e
Show file tree
Hide file tree
Showing 14 changed files with 350 additions and 25 deletions.
18 changes: 18 additions & 0 deletions stytch/b2b/api/organizations_members.py
Original file line number Diff line number Diff line change
Expand Up @@ -655,6 +655,15 @@ def oidc_providers(
member_id: str,
include_refresh_token: Optional[bool] = None,
) -> OIDCProvidersResponse:
"""Retrieve the saved OIDC access tokens and ID tokens for a member. After a successful OIDC login, Stytch will save the
issued access token and ID token from the identity provider. If a refresh token has been issued, Stytch will refresh the
access token automatically.
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.
- include_refresh_token: Whether to return the refresh token Stytch has stored for the OAuth Provider. Defaults to false. **Important:** If your application exchanges the refresh token, Stytch may not be able to automatically refresh access tokens in the future.
""" # noqa
headers: Dict[str, str] = {}
data: Dict[str, Any] = {
"organization_id": organization_id,
Expand All @@ -676,6 +685,15 @@ async def oidc_providers_async(
member_id: str,
include_refresh_token: Optional[bool] = None,
) -> OIDCProvidersResponse:
"""Retrieve the saved OIDC access tokens and ID tokens for a member. After a successful OIDC login, Stytch will save the
issued access token and ID token from the identity provider. If a refresh token has been issued, Stytch will refresh the
access token automatically.
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.
- include_refresh_token: Whether to return the refresh token Stytch has stored for the OAuth Provider. Defaults to false. **Important:** If your application exchanges the refresh token, Stytch may not be able to automatically refresh access tokens in the future.
""" # noqa
headers: Dict[str, str] = {}
data: Dict[str, Any] = {
"organization_id": organization_id,
Expand Down
12 changes: 8 additions & 4 deletions stytch/b2b/api/otp_sms.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ def send(
An error will be thrown if the Member already has a phone number and the provided `mfa_phone_number` does not match the existing one.
Note that sending another OTP code before the first has expired will invalidate the first code.
OTP codes expire after two minutes. Note that sending another OTP code before the first has expired will invalidate the first code.
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.
Expand Down Expand Up @@ -106,7 +106,7 @@ async def send_async(
An error will be thrown if the Member already has a phone number and the provided `mfa_phone_number` does not match the existing one.
Note that sending another OTP code before the first has expired will invalidate the first code.
OTP codes expire after two minutes. Note that sending another OTP code before the first has expired will invalidate the first code.
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.
Expand Down Expand Up @@ -168,7 +168,9 @@ def authenticate(
) -> 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.
This endpoint verifies that the one-time passcode (OTP) is valid and hasn't expired or been previously used. A given Member may only have a single active OTP code at any given time. If a Member requests another OTP code before the first one has expired, the first one will be invalidated.
This endpoint verifies that the one-time passcode (OTP) is valid and hasn't expired or been previously used. OTP codes expire after two minutes.
A given Member may only have a single active OTP code at any given time. If a Member requests another OTP code before the first one has expired, the first one will be invalidated.
Exactly one of `intermediate_session_token`, `session_token`, or `session_jwt` must be provided in the request.
If an intermediate session token is provided, this operation will consume it.
Expand Down Expand Up @@ -252,7 +254,9 @@ async def authenticate_async(
) -> 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.
This endpoint verifies that the one-time passcode (OTP) is valid and hasn't expired or been previously used. A given Member may only have a single active OTP code at any given time. If a Member requests another OTP code before the first one has expired, the first one will be invalidated.
This endpoint verifies that the one-time passcode (OTP) is valid and hasn't expired or been previously used. OTP codes expire after two minutes.
A given Member may only have a single active OTP code at any given time. If a Member requests another OTP code before the first one has expired, the first one will be invalidated.
Exactly one of `intermediate_session_token`, `session_token`, or `session_jwt` must be provided in the request.
If an intermediate session token is provided, this operation will consume it.
Expand Down
6 changes: 6 additions & 0 deletions stytch/b2b/api/passwords.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

from typing import Any, Dict, List, Optional, Union

from stytch.b2b.api.passwords_discovery import Discovery
from stytch.b2b.api.passwords_email import Email
from stytch.b2b.api.passwords_existing_password import ExistingPassword
from stytch.b2b.api.passwords_session import Sessions
Expand Down Expand Up @@ -51,6 +52,11 @@ def __init__(
sync_client=self.sync_client,
async_client=self.async_client,
)
self.discovery = Discovery(
api_base=self.api_base,
sync_client=self.sync_client,
async_client=self.async_client,
)

def strength_check(
self,
Expand Down
58 changes: 58 additions & 0 deletions stytch/b2b/api/passwords_discovery.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# !!!
# 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

from stytch.b2b.api.passwords_discovery_email import Email
from stytch.b2b.models.passwords_discovery import AuthenticateResponse
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
self.email = Email(
api_base=self.api_base,
sync_client=self.sync_client,
async_client=self.async_client,
)

def authenticate(
self,
email_address: str,
password: str,
) -> AuthenticateResponse:
headers: Dict[str, str] = {}
data: Dict[str, Any] = {
"email_address": email_address,
"password": password,
}

url = self.api_base.url_for("/v1/b2b/passwords/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,
password: str,
) -> AuthenticateResponse:
headers: Dict[str, str] = {}
data: Dict[str, Any] = {
"email_address": email_address,
"password": password,
}

url = self.api_base.url_for("/v1/b2b/passwords/discovery/authenticate", data)
res = await self.async_client.post(url, data, headers)
return AuthenticateResponse.from_json(res.response.status, res.json)
137 changes: 137 additions & 0 deletions stytch/b2b/api/passwords_discovery_email.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
# !!!
# 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.passwords_discovery_email import (
ResetResponse,
ResetStartResponse,
)
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

def reset_start(
self,
email_address: str,
reset_password_redirect_url: Optional[str] = None,
discovery_redirect_url: Optional[str] = None,
reset_password_template_id: Optional[str] = None,
reset_password_expiration_minutes: Optional[int] = None,
pkce_code_challenge: Optional[str] = None,
locale: Optional[str] = None,
) -> ResetStartResponse:
headers: Dict[str, str] = {}
data: Dict[str, Any] = {
"email_address": email_address,
}
if reset_password_redirect_url is not None:
data["reset_password_redirect_url"] = reset_password_redirect_url
if discovery_redirect_url is not None:
data["discovery_redirect_url"] = discovery_redirect_url
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
)
if pkce_code_challenge is not None:
data["pkce_code_challenge"] = pkce_code_challenge
if locale is not None:
data["locale"] = locale

url = self.api_base.url_for(
"/v1/b2b/passwords/discovery/email/reset/start", data
)
res = self.sync_client.post(url, data, headers)
return ResetStartResponse.from_json(res.response.status_code, res.json)

async def reset_start_async(
self,
email_address: str,
reset_password_redirect_url: Optional[str] = None,
discovery_redirect_url: Optional[str] = None,
reset_password_template_id: Optional[str] = None,
reset_password_expiration_minutes: Optional[int] = None,
pkce_code_challenge: Optional[str] = None,
locale: Optional[str] = None,
) -> ResetStartResponse:
headers: Dict[str, str] = {}
data: Dict[str, Any] = {
"email_address": email_address,
}
if reset_password_redirect_url is not None:
data["reset_password_redirect_url"] = reset_password_redirect_url
if discovery_redirect_url is not None:
data["discovery_redirect_url"] = discovery_redirect_url
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
)
if pkce_code_challenge is not None:
data["pkce_code_challenge"] = pkce_code_challenge
if locale is not None:
data["locale"] = locale

url = self.api_base.url_for(
"/v1/b2b/passwords/discovery/email/reset/start", data
)
res = await self.async_client.post(url, data, headers)
return ResetStartResponse.from_json(res.response.status, res.json)

def reset(
self,
password_reset_token: str,
password: str,
pkce_code_verifier: Optional[str] = None,
locale: Optional[str] = None,
) -> ResetResponse:
headers: Dict[str, str] = {}
data: Dict[str, Any] = {
"password_reset_token": password_reset_token,
"password": password,
}
if pkce_code_verifier is not None:
data["pkce_code_verifier"] = pkce_code_verifier
if locale is not None:
data["locale"] = locale

url = self.api_base.url_for("/v1/b2b/passwords/discovery/email/reset", data)
res = self.sync_client.post(url, data, headers)
return ResetResponse.from_json(res.response.status_code, res.json)

async def reset_async(
self,
password_reset_token: str,
password: str,
pkce_code_verifier: Optional[str] = None,
locale: Optional[str] = None,
) -> ResetResponse:
headers: Dict[str, str] = {}
data: Dict[str, Any] = {
"password_reset_token": password_reset_token,
"password": password,
}
if pkce_code_verifier is not None:
data["pkce_code_verifier"] = pkce_code_verifier
if locale is not None:
data["locale"] = locale

url = self.api_base.url_for("/v1/b2b/passwords/discovery/email/reset", data)
res = await self.async_client.post(url, data, headers)
return ResetResponse.from_json(res.response.status, res.json)
25 changes: 16 additions & 9 deletions stytch/b2b/api/passwords_email.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
from typing import Any, Dict, Optional, Union

from stytch.b2b.models.passwords_email import (
DeleteResponse,
RequireResetRequestOptions,
RequireResetResponse,
ResetRequestLocale,
ResetResponse,
ResetStartRequestLocale,
Expand Down Expand Up @@ -317,13 +318,16 @@ async def reset_async(
res = await self.async_client.post(url, data, headers)
return ResetResponse.from_json(res.response.status, res.json)

def delete(
def require_reset(
self,
email_address: str,
organization_id: Optional[str] = None,
member_id: Optional[str] = None,
) -> DeleteResponse:
method_options: Optional[RequireResetRequestOptions] = None,
) -> RequireResetResponse:
headers: Dict[str, str] = {}
if method_options is not None:
headers = method_options.add_headers(headers)
data: Dict[str, Any] = {
"email_address": email_address,
}
Expand All @@ -332,17 +336,20 @@ def delete(
if member_id is not None:
data["member_id"] = member_id

url = self.api_base.url_for("/v1/b2b/passwords/email/delete", data)
url = self.api_base.url_for("/v1/b2b/passwords/email/require_reset", data)
res = self.sync_client.post(url, data, headers)
return DeleteResponse.from_json(res.response.status_code, res.json)
return RequireResetResponse.from_json(res.response.status_code, res.json)

async def delete_async(
async def require_reset_async(
self,
email_address: str,
organization_id: Optional[str] = None,
member_id: Optional[str] = None,
) -> DeleteResponse:
method_options: Optional[RequireResetRequestOptions] = None,
) -> RequireResetResponse:
headers: Dict[str, str] = {}
if method_options is not None:
headers = method_options.add_headers(headers)
data: Dict[str, Any] = {
"email_address": email_address,
}
Expand All @@ -351,6 +358,6 @@ async def delete_async(
if member_id is not None:
data["member_id"] = member_id

url = self.api_base.url_for("/v1/b2b/passwords/email/delete", data)
url = self.api_base.url_for("/v1/b2b/passwords/email/require_reset", data)
res = await self.async_client.post(url, data, headers)
return DeleteResponse.from_json(res.response.status, res.json)
return RequireResetResponse.from_json(res.response.status, res.json)
Loading

0 comments on commit fccb84e

Please sign in to comment.