diff --git a/descope/auth.py b/descope/auth.py index 518722627..5980b498a 100644 --- a/descope/auth.py +++ b/descope/auth.py @@ -100,21 +100,32 @@ def do_post(self, uri: str, body: dict, pswd: str = None) -> requests.Response: return response @staticmethod - def verify_delivery_method(method: DeliveryMethod, identifier: str) -> bool: - if identifier == "" or identifier is None: + def verify_delivery_method( + method: DeliveryMethod, identifier: str, user: dict + ) -> bool: + if not identifier: + return False + + if not user or not isinstance(user, dict): return False if method == DeliveryMethod.EMAIL: + if not user.get("email", None): + user["email"] = identifier try: - validate_email(identifier) + validate_email(user["email"]) return True except EmailNotValidError: return False elif method == DeliveryMethod.PHONE: - if not re.match(PHONE_REGEX, identifier): + if not user.get("phone", None): + user["phone"] = identifier + if not re.match(PHONE_REGEX, user["phone"]): return False elif method == DeliveryMethod.WHATSAPP: - if not re.match(PHONE_REGEX, identifier): + if not user.get("phone", None): + user["phone"] = identifier + if not re.match(PHONE_REGEX, user["phone"]): return False else: return False diff --git a/descope/authmethod/magiclink.py b/descope/authmethod/magiclink.py index b79b7468b..5423133b4 100644 --- a/descope/authmethod/magiclink.py +++ b/descope/authmethod/magiclink.py @@ -4,7 +4,7 @@ from descope.auth import Auth from descope.common import REFRESH_SESSION_COOKIE_NAME, DeliveryMethod, EndpointsV1 -from descope.exceptions import ERROR_TYPE_INVALID_PUBLIC_KEY, AuthException +from descope.exceptions import ERROR_TYPE_INVALID_ARGUMENT, AuthException class MagicLink: @@ -90,11 +90,11 @@ def update_user_phone_cross_device( def _sign_in( self, method: DeliveryMethod, identifier: str, uri: str, cross_device: bool ) -> requests.Response: - if not self._auth.verify_delivery_method(method, identifier): + if not identifier: raise AuthException( 400, - ERROR_TYPE_INVALID_PUBLIC_KEY, - f"Identifier {identifier} does not support delivery method {method}", + ERROR_TYPE_INVALID_ARGUMENT, + "Identifier is empty", ) body = MagicLink._compose_signin_body(identifier, uri, cross_device) @@ -110,11 +110,11 @@ def _sign_up( cross_device: bool, user: dict = None, ) -> requests.Response: - if not self._auth.verify_delivery_method(method, identifier): + if not self._auth.verify_delivery_method(method, identifier, user): raise AuthException( 400, - ERROR_TYPE_INVALID_PUBLIC_KEY, - f"Identifier {identifier} does not support delivery method {method}", + ERROR_TYPE_INVALID_ARGUMENT, + f"Identifier {identifier} is not valid by delivery method {method}", ) body = MagicLink._compose_signup_body( @@ -126,12 +126,6 @@ def _sign_up( def _sign_up_or_in( self, method: DeliveryMethod, identifier: str, uri: str, cross_device: bool ) -> requests.Response: - if not self._auth.verify_delivery_method(method, identifier): - raise AuthException( - 400, - ERROR_TYPE_INVALID_PUBLIC_KEY, - f"Identifier {identifier} does not support delivery method {method}", - ) body = MagicLink._compose_signin_body(identifier, uri, cross_device) uri = MagicLink._compose_sign_up_or_in_url(method) @@ -142,7 +136,7 @@ def _update_user_email( ) -> requests.Response: if not identifier: raise AuthException( - 400, ERROR_TYPE_INVALID_PUBLIC_KEY, "Identifier cannot be empty" + 400, ERROR_TYPE_INVALID_ARGUMENT, "Identifier cannot be empty" ) Auth.validate_email(email) @@ -163,7 +157,7 @@ def _update_user_phone( ) -> requests.Response: if not identifier: raise AuthException( - 400, ERROR_TYPE_INVALID_PUBLIC_KEY, "Identifier cannot be empty" + 400, ERROR_TYPE_INVALID_ARGUMENT, "Identifier cannot be empty" ) Auth.validate_phone(method, phone) diff --git a/descope/authmethod/oauth.py b/descope/authmethod/oauth.py index 723ead116..855c5cc56 100644 --- a/descope/authmethod/oauth.py +++ b/descope/authmethod/oauth.py @@ -1,7 +1,7 @@ from descope.auth import Auth from descope.authmethod.exchanger import Exchanger # noqa: F401 from descope.common import EndpointsV1, OAuthProviders -from descope.exceptions import ERROR_TYPE_INVALID_PUBLIC_KEY, AuthException +from descope.exceptions import ERROR_TYPE_INVALID_ARGUMENT, AuthException class OAuth(Exchanger): @@ -13,7 +13,7 @@ def start(self, provider: str, return_url: str = "") -> dict: if not self._verify_provider(provider): raise AuthException( 400, - ERROR_TYPE_INVALID_PUBLIC_KEY, + ERROR_TYPE_INVALID_ARGUMENT, f"Unknown OAuth provider: {provider}", ) diff --git a/descope/authmethod/otp.py b/descope/authmethod/otp.py index 63ce0e621..91b5bf104 100644 --- a/descope/authmethod/otp.py +++ b/descope/authmethod/otp.py @@ -1,6 +1,6 @@ from descope.auth import Auth from descope.common import REFRESH_SESSION_COOKIE_NAME, DeliveryMethod, EndpointsV1 -from descope.exceptions import ERROR_TYPE_INVALID_PUBLIC_KEY, AuthException +from descope.exceptions import ERROR_TYPE_INVALID_ARGUMENT, AuthException class OTP: @@ -23,12 +23,9 @@ def sign_in(self, method: DeliveryMethod, identifier: str) -> None: Raise: AuthException: raised if sign-in operation fails """ - - if not self._auth.verify_delivery_method(method, identifier): + if not identifier: raise AuthException( - 400, - ERROR_TYPE_INVALID_PUBLIC_KEY, - f"Identifier {identifier} does not support delivery method {method}", + 400, ERROR_TYPE_INVALID_ARGUMENT, "Identifier cannot be empty" ) uri = OTP._compose_signin_url(method) @@ -53,11 +50,11 @@ def sign_up( AuthException: raised if sign-up operation fails """ - if not self._auth.verify_delivery_method(method, identifier): + if not self._auth.verify_delivery_method(method, identifier, user): raise AuthException( 400, - ERROR_TYPE_INVALID_PUBLIC_KEY, - f"Identifier {identifier} is does not support delivery method {method}", + ERROR_TYPE_INVALID_ARGUMENT, + f"Identifier {identifier} is not valid by delivery method {method}", ) uri = OTP._compose_signup_url(method) @@ -78,11 +75,9 @@ def sign_up_or_in(self, method: DeliveryMethod, identifier: str) -> None: Raise: AuthException: raised if either the sign_up or sign_in operation fails """ - if not self._auth.verify_delivery_method(method, identifier): + if not identifier: raise AuthException( - 400, - ERROR_TYPE_INVALID_PUBLIC_KEY, - f"Identifier {identifier} does not support delivery method {method}", + 400, ERROR_TYPE_INVALID_ARGUMENT, "Identifier cannot be empty" ) uri = OTP._compose_sign_up_or_in_url(method) @@ -107,12 +102,9 @@ def verify_code(self, method: DeliveryMethod, identifier: str, code: str) -> dic Raise: AuthException: raised if the OTP code is not valid or if token verification failed """ - - if not self._auth.verify_delivery_method(method, identifier): + if not identifier: raise AuthException( - 400, - ERROR_TYPE_INVALID_PUBLIC_KEY, - f"Identifier {identifier} does not support delivery method {method}", + 400, ERROR_TYPE_INVALID_ARGUMENT, "Identifier cannot be empty" ) uri = OTP._compose_verify_code_url(method) @@ -142,7 +134,7 @@ def update_user_email( if not identifier: raise AuthException( - 400, ERROR_TYPE_INVALID_PUBLIC_KEY, "Identifier cannot be empty" + 400, ERROR_TYPE_INVALID_ARGUMENT, "Identifier cannot be empty" ) Auth.validate_email(email) @@ -169,7 +161,7 @@ def update_user_phone( if not identifier: raise AuthException( - 400, ERROR_TYPE_INVALID_PUBLIC_KEY, "Identifier cannot be empty" + 400, ERROR_TYPE_INVALID_ARGUMENT, "Identifier cannot be empty" ) Auth.validate_phone(method, phone) diff --git a/descope/authmethod/saml.py b/descope/authmethod/saml.py index fb6132b53..6c116b237 100644 --- a/descope/authmethod/saml.py +++ b/descope/authmethod/saml.py @@ -1,7 +1,7 @@ from descope.auth import Auth from descope.authmethod.exchanger import Exchanger # noqa: F401 from descope.common import EndpointsV1 -from descope.exceptions import ERROR_TYPE_INVALID_PUBLIC_KEY, AuthException +from descope.exceptions import ERROR_TYPE_INVALID_ARGUMENT, AuthException class SAML(Exchanger): @@ -14,12 +14,12 @@ def start(self, tenant: str, return_url: str = None) -> dict: """ if not tenant: raise AuthException( - 400, ERROR_TYPE_INVALID_PUBLIC_KEY, "Tenant cannot be empty" + 400, ERROR_TYPE_INVALID_ARGUMENT, "Tenant cannot be empty" ) if not return_url: raise AuthException( - 400, ERROR_TYPE_INVALID_PUBLIC_KEY, "Return url cannot be empty" + 400, ERROR_TYPE_INVALID_ARGUMENT, "Return url cannot be empty" ) uri = EndpointsV1.authSAMLStart diff --git a/descope/authmethod/totp.py b/descope/authmethod/totp.py index 340671199..608568a9c 100644 --- a/descope/authmethod/totp.py +++ b/descope/authmethod/totp.py @@ -1,6 +1,6 @@ from descope.auth import Auth from descope.common import REFRESH_SESSION_COOKIE_NAME, EndpointsV1 -from descope.exceptions import ERROR_TYPE_INVALID_PUBLIC_KEY, AuthException +from descope.exceptions import ERROR_TYPE_INVALID_ARGUMENT, AuthException class TOTP: @@ -16,7 +16,7 @@ def sign_up(self, identifier: str, user: dict = None) -> dict: if not identifier: raise AuthException( - 400, ERROR_TYPE_INVALID_PUBLIC_KEY, "Identifier cannot be empty" + 400, ERROR_TYPE_INVALID_ARGUMENT, "Identifier cannot be empty" ) uri = EndpointsV1.signUpAuthTOTPPath @@ -37,12 +37,12 @@ def sign_in_code(self, identifier: str, code: str) -> dict: if not identifier: raise AuthException( - 400, ERROR_TYPE_INVALID_PUBLIC_KEY, "Identifier cannot be empty" + 400, ERROR_TYPE_INVALID_ARGUMENT, "Identifier cannot be empty" ) if not code: raise AuthException( - 400, ERROR_TYPE_INVALID_PUBLIC_KEY, "Code cannot be empty" + 400, ERROR_TYPE_INVALID_ARGUMENT, "Code cannot be empty" ) uri = EndpointsV1.verifyTOTPPath @@ -62,12 +62,12 @@ def update_user(self, identifier: str, refresh_token: str) -> None: if not identifier: raise AuthException( - 400, ERROR_TYPE_INVALID_PUBLIC_KEY, "Identifier cannot be empty" + 400, ERROR_TYPE_INVALID_ARGUMENT, "Identifier cannot be empty" ) if not refresh_token: raise AuthException( - 400, ERROR_TYPE_INVALID_PUBLIC_KEY, "Refresh token cannot be empty" + 400, ERROR_TYPE_INVALID_ARGUMENT, "Refresh token cannot be empty" ) uri = EndpointsV1.updateTOTPPath diff --git a/descope/authmethod/webauthn.py b/descope/authmethod/webauthn.py index ffc744f16..f6f03c1ad 100644 --- a/descope/authmethod/webauthn.py +++ b/descope/authmethod/webauthn.py @@ -1,6 +1,6 @@ from descope.auth import Auth from descope.common import REFRESH_SESSION_COOKIE_NAME, EndpointsV1 -from descope.exceptions import ERROR_TYPE_INVALID_PUBLIC_KEY, AuthException +from descope.exceptions import ERROR_TYPE_INVALID_ARGUMENT, AuthException class WebauthN: @@ -15,12 +15,12 @@ def sign_up_start(self, identifier: str, origin: str, user: dict = None) -> dict """ if not identifier: raise AuthException( - 400, ERROR_TYPE_INVALID_PUBLIC_KEY, "Identifier cannot be empty" + 400, ERROR_TYPE_INVALID_ARGUMENT, "Identifier cannot be empty" ) if not origin: raise AuthException( - 400, ERROR_TYPE_INVALID_PUBLIC_KEY, "Origin cannot be empty" + 400, ERROR_TYPE_INVALID_ARGUMENT, "Origin cannot be empty" ) uri = EndpointsV1.signUpAuthWebauthnStart @@ -35,12 +35,12 @@ def sign_up_finish(self, transactionID: str, response: str) -> dict: """ if not transactionID: raise AuthException( - 400, ERROR_TYPE_INVALID_PUBLIC_KEY, "Transaction id cannot be empty" + 400, ERROR_TYPE_INVALID_ARGUMENT, "Transaction id cannot be empty" ) if not response: raise AuthException( - 400, ERROR_TYPE_INVALID_PUBLIC_KEY, "Response cannot be empty" + 400, ERROR_TYPE_INVALID_ARGUMENT, "Response cannot be empty" ) uri = EndpointsV1.signUpAuthWebauthnFinish @@ -59,12 +59,12 @@ def sign_in_start(self, identifier: str, origin: str) -> dict: """ if not identifier: raise AuthException( - 400, ERROR_TYPE_INVALID_PUBLIC_KEY, "Identifier cannot be empty" + 400, ERROR_TYPE_INVALID_ARGUMENT, "Identifier cannot be empty" ) if not origin: raise AuthException( - 400, ERROR_TYPE_INVALID_PUBLIC_KEY, "Origin cannot be empty" + 400, ERROR_TYPE_INVALID_ARGUMENT, "Origin cannot be empty" ) uri = EndpointsV1.signInAuthWebauthnStart @@ -79,12 +79,12 @@ def sign_in_finish(self, transaction_id: str, response: str) -> dict: """ if not transaction_id: raise AuthException( - 400, ERROR_TYPE_INVALID_PUBLIC_KEY, "Transaction id cannot be empty" + 400, ERROR_TYPE_INVALID_ARGUMENT, "Transaction id cannot be empty" ) if not response: raise AuthException( - 400, ERROR_TYPE_INVALID_PUBLIC_KEY, "Response cannot be empty" + 400, ERROR_TYPE_INVALID_ARGUMENT, "Response cannot be empty" ) uri = EndpointsV1.signInAuthWebauthnFinish @@ -103,12 +103,12 @@ def add_device_start(self, identifier: str, refresh_token: str, origin: str): """ if not identifier: raise AuthException( - 400, ERROR_TYPE_INVALID_PUBLIC_KEY, "Identifier cannot be empty" + 400, ERROR_TYPE_INVALID_ARGUMENT, "Identifier cannot be empty" ) if not refresh_token: raise AuthException( - 400, ERROR_TYPE_INVALID_PUBLIC_KEY, "Refresh token cannot be empty" + 400, ERROR_TYPE_INVALID_ARGUMENT, "Refresh token cannot be empty" ) uri = EndpointsV1.deviceAddAuthWebauthnStart @@ -123,12 +123,12 @@ def add_device_finish(self, transaction_id: str, response: str) -> None: """ if not transaction_id: raise AuthException( - 400, ERROR_TYPE_INVALID_PUBLIC_KEY, "Transaction id cannot be empty" + 400, ERROR_TYPE_INVALID_ARGUMENT, "Transaction id cannot be empty" ) if not response: raise AuthException( - 400, ERROR_TYPE_INVALID_PUBLIC_KEY, "Response cannot be empty" + 400, ERROR_TYPE_INVALID_ARGUMENT, "Response cannot be empty" ) uri = EndpointsV1.deviceAddAuthWebauthnFinish diff --git a/samples/otp_web_sample_app.py b/samples/otp_web_sample_app.py index d49e98a6e..71d00c97f 100644 --- a/samples/otp_web_sample_app.py +++ b/samples/otp_web_sample_app.py @@ -86,8 +86,10 @@ def signin(): try: descope_client.otp.sign_in(DeliveryMethod.EMAIL, email) - except AuthException: - return Response("Unauthorized, something went wrong when sending email", 401) + except AuthException as ex: + return Response( + f"Unauthorized, something went wrong when sending email {ex}", 401 + ) return Response("This is SignIn API handling", 200) diff --git a/tests/test_auth.py b/tests/test_auth.py index 8c984d3a0..07aabcd52 100644 --- a/tests/test_auth.py +++ b/tests/test_auth.py @@ -109,54 +109,83 @@ def test_fetch_public_key(self): def test_verify_delivery_method(self): self.assertEqual( - Auth.verify_delivery_method(DeliveryMethod.EMAIL, "dummy@dummy.com"), + Auth.verify_delivery_method(DeliveryMethod.EMAIL, "dummy@dummy.com", None), + False, + ) + + self.assertEqual( + Auth.verify_delivery_method( + DeliveryMethod.EMAIL, "dummy@dummy.com", {"phone": ""} + ), True, ) self.assertEqual( - Auth.verify_delivery_method(DeliveryMethod.EMAIL, "dummy@dummy.com"), + Auth.verify_delivery_method( + DeliveryMethod.EMAIL, "dummy@dummy.com", {"phone": ""} + ), True, ) self.assertEqual( - Auth.verify_delivery_method(DeliveryMethod.EMAIL, "dummy@dummy.com"), + Auth.verify_delivery_method( + DeliveryMethod.EMAIL, "dummy@dummy.com", {"phone": ""} + ), True, ) - self.assertEqual(Auth.verify_delivery_method(DeliveryMethod.EMAIL, ""), False) self.assertEqual( - Auth.verify_delivery_method(DeliveryMethod.EMAIL, "dummy@dummy"), + Auth.verify_delivery_method(DeliveryMethod.EMAIL, "", {"phone": ""}), False + ) + self.assertEqual( + Auth.verify_delivery_method( + DeliveryMethod.EMAIL, "dummy@dummy", {"phone": ""} + ), False, ) self.assertEqual( - Auth.verify_delivery_method(DeliveryMethod.PHONE, "111111111111"), + Auth.verify_delivery_method( + DeliveryMethod.PHONE, "111111111111", {"email": ""} + ), True, ) self.assertEqual( - Auth.verify_delivery_method(DeliveryMethod.PHONE, "+111111111111"), + Auth.verify_delivery_method( + DeliveryMethod.PHONE, "+111111111111", {"email": ""} + ), True, ) self.assertEqual( - Auth.verify_delivery_method(DeliveryMethod.PHONE, "++111111111111"), + Auth.verify_delivery_method( + DeliveryMethod.PHONE, "++111111111111", {"email": ""} + ), + False, + ) + self.assertEqual( + Auth.verify_delivery_method(DeliveryMethod.PHONE, "asdsad", {"email": ""}), False, ) self.assertEqual( - Auth.verify_delivery_method(DeliveryMethod.PHONE, "asdsad"), False + Auth.verify_delivery_method(DeliveryMethod.PHONE, "", {"email": ""}), False ) - self.assertEqual(Auth.verify_delivery_method(DeliveryMethod.PHONE, ""), False) self.assertEqual( - Auth.verify_delivery_method(DeliveryMethod.PHONE, "unvalid@phone.number"), + Auth.verify_delivery_method( + DeliveryMethod.PHONE, "unvalid@phone.number", {"email": ""} + ), False, ) self.assertEqual( - Auth.verify_delivery_method(DeliveryMethod.WHATSAPP, "111111111111"), + Auth.verify_delivery_method( + DeliveryMethod.WHATSAPP, "111111111111", {"email": ""} + ), True, ) self.assertEqual( - Auth.verify_delivery_method(DeliveryMethod.WHATSAPP, ""), False + Auth.verify_delivery_method(DeliveryMethod.WHATSAPP, "", {"email": ""}), + False, ) self.assertEqual( Auth.verify_delivery_method( - DeliveryMethod.WHATSAPP, "unvalid@phone.number" + DeliveryMethod.WHATSAPP, "unvalid@phone.number", {"email": ""} ), False, ) @@ -165,7 +194,9 @@ class AAA(Enum): DUMMY = 4 self.assertEqual( - Auth.verify_delivery_method(AAA.DUMMY, "unvalid@phone.number"), + Auth.verify_delivery_method( + AAA.DUMMY, "unvalid@phone.number", {"phone": ""} + ), False, )