diff --git a/README.md b/README.md index f7cc8cbaa..6868325eb 100644 --- a/README.md +++ b/README.md @@ -1113,7 +1113,7 @@ Embedded links can be created to directly receive a verifiable token without sen This token can then be verified using the magic link 'verify' function, either directly or through a flow. ```python -token = descope_client.mgmt.user.generate_embedded_link("desmond@descope.com", {"key1":"value1"}) +token = descope_client.mgmt.user.generate_embedded_link("desmond@descope.com", {"key1":"value1"}, 120) ``` ### Audit diff --git a/descope/management/common.py b/descope/management/common.py index a7f35a791..edd800be1 100644 --- a/descope/management/common.py +++ b/descope/management/common.py @@ -57,6 +57,7 @@ class MgmtV1: user_generate_magic_link_for_test_path = "/v1/mgmt/tests/generate/magiclink" user_generate_enchanted_link_for_test_path = "/v1/mgmt/tests/generate/enchantedlink" user_generate_embedded_link_path = "/v1/mgmt/user/signin/embeddedlink" + user_generate_sign_up_embedded_link_path = "/v1/mgmt/user/signup/embeddedlink" user_history_path = "/v1/mgmt/user/history" # access key diff --git a/descope/management/user.py b/descope/management/user.py index b443d349a..3ca71f9ba 100644 --- a/descope/management/user.py +++ b/descope/management/user.py @@ -56,6 +56,24 @@ def __init__( self.status = status +class CreateUserObj: + def __init__( + self, + email: Optional[str] = None, + phone: Optional[str] = None, + name: Optional[str] = None, + given_name: Optional[str] = None, + middle_name: Optional[str] = None, + family_name: Optional[str] = None, + ): + self.email = email + self.phone = phone + self.name = name + self.given_name = given_name + self.middle_name = middle_name + self.family_name = family_name + + class User(AuthBase): def create( self, @@ -1655,7 +1673,7 @@ def generate_enchanted_link_for_test_user( return response.json() def generate_embedded_link( - self, login_id: str, custom_claims: Optional[dict] = None + self, login_id: str, custom_claims: Optional[dict] = None, timeout: int = 0 ) -> str: """ Generate Embedded Link for the given user login ID. @@ -1673,7 +1691,44 @@ def generate_embedded_link( """ response = self._auth.do_post( MgmtV1.user_generate_embedded_link_path, - {"loginId": login_id, "customClaims": custom_claims}, + {"loginId": login_id, "customClaims": custom_claims, "timeout": timeout}, + pswd=self._auth.management_key, + ) + return response.json()["token"] + + def generate_sign_up_embedded_link( + self, login_id: str, user: Optional[CreateUserObj] = None, + email_verified: bool = False, phone_verified: bool = False, + login_options: Optional[LoginOptions] = None, timeout: int = 0 + ) -> str: + """ + Generate sign up Embedded Link for the given user login ID. + The return value is a token that can be verified via magic link, or using flows + + Args: + login_id (str): The login ID of the user to authenticate with. + user (CreateUserObj): Optional user object to create the user with + email_verified (bool): Optional, set to true if the email is verified + phone_verified (bool): Optional, set to true if the phone is verified + login_options (LoginOptions): Optional login options to customize the link + timeout (int): Optional, the timeout in seconds for the link to be valid + + Return value (str): + Return the token to be used in verification process + + Raise: + AuthException: raised if the operation fails + """ + response = self._auth.do_post( + MgmtV1.user_generate_sign_up_embedded_link_path, + { + "loginId": login_id, + "user": user.__dict__ if user else {}, + "loginOptions": login_options.__dict__ if login_options else {}, + "emailVerified": email_verified, + "phoneVerified": phone_verified, + "timeout": timeout + }, pswd=self._auth.management_key, ) return response.json()["token"] diff --git a/tests/management/test_user.py b/tests/management/test_user.py index eb679dd34..5696c7386 100644 --- a/tests/management/test_user.py +++ b/tests/management/test_user.py @@ -2406,6 +2406,46 @@ def test_generate_embedded_link(self): json={ "loginId": "login-id", "customClaims": {"k1": "v1"}, + "timeout": 0, + }, + allow_redirects=False, + verify=True, + params=None, + timeout=DEFAULT_TIMEOUT_SECONDS, + ) + + def test_generate_sign_up_embedded_link(self): + # Test failed flows + with patch("requests.post") as mock_post: + mock_post.return_value.ok = False + self.assertRaises( + AuthException, self.client.mgmt.user.generate_sign_up_embedded_link, "login-id" + ) + + # Test success flow + with patch("requests.post") as mock_post: + network_resp = mock.Mock() + network_resp.ok = True + network_resp.json.return_value = json.loads("""{"token": "some-token"}""") + mock_post.return_value = network_resp + resp = self.client.mgmt.user.generate_sign_up_embedded_link( + "login-id", email_verified=True, phone_verified=True + ) + self.assertEqual(resp, "some-token") + mock_post.assert_called_with( + f"{common.DEFAULT_BASE_URL}{MgmtV1.user_generate_sign_up_embedded_link_path}", + headers={ + **common.default_headers, + "Authorization": f"Bearer {self.dummy_project_id}:{self.dummy_management_key}", + "x-descope-project-id": self.dummy_project_id, + }, + json={ + "loginId": "login-id", + "phoneVerified": True, + "emailVerified": True, + "user": {}, + "loginOptions": {}, + "timeout": 0, }, allow_redirects=False, verify=True,