From 04eaeefd013314f2baa151bde1e83d56d4d098e0 Mon Sep 17 00:00:00 2001 From: Harryacorn2 Date: Mon, 31 Jul 2023 16:40:41 -0700 Subject: [PATCH 1/3] Testing commits I am testing if commits work the way I think they do --- venmo_api/apis/auth_api.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/venmo_api/apis/auth_api.py b/venmo_api/apis/auth_api.py index 22fa22b..4f35f57 100644 --- a/venmo_api/apis/auth_api.py +++ b/venmo_api/apis/auth_api.py @@ -178,3 +178,5 @@ def __ask_user_for_otp_password(): otp = input("Enter OTP that you received on your phone from Venmo: (It must be 6 digits)\n") return otp + +# Making modification to file to test if github works the way I think it does \ No newline at end of file From 5613e774d0bbe9172c0c4fef695f2891dfbc4edf Mon Sep 17 00:00:00 2001 From: Harryacorn2 Date: Mon, 31 Jul 2023 17:39:48 -0700 Subject: [PATCH 2/3] Added functionality to login with 2FA without using CLI You can now pass a function to get_access_token so that when it needs the OTP it calls your function instead of using the CLI. Your function should take no parameters, and should return a string. --- venmo_api/apis/auth_api.py | 64 ++++++++++++++++++++++++++++++++++++-- venmo_api/venmo.py | 11 +++++-- 2 files changed, 71 insertions(+), 4 deletions(-) diff --git a/venmo_api/apis/auth_api.py b/venmo_api/apis/auth_api.py index 4f35f57..7454f00 100644 --- a/venmo_api/apis/auth_api.py +++ b/venmo_api/apis/auth_api.py @@ -1,4 +1,5 @@ from venmo_api import random_device_id, warn, confirm, AuthenticationFailedError, ApiClient +from collections.abc import Callable, Awaitable class AuthenticationApi(object): @@ -39,6 +40,37 @@ def login_with_credentials_cli(self, username: str, password: str) -> str: f"device-id: {self.__device_id}") return access_token + + def login_with_credentials_func(self, username: str, password: str, otp_func: Callable[[], str]) -> str: + """ + Pass your username and password to get an access_token for using the API. + :param username: Phone, email or username + :param password: Your account password to login + :param otp_func: Function that returns the OTP provided to the user + :return: + """ + + # Give warnings to the user about device-id and token expiration + warn("IMPORTANT: Take a note of your device-id to avoid 2-factor-authentication for your next login.") + print(f"device-id: {self.__device_id}") + warn("IMPORTANT: Your Access Token will NEVER expire, unless you logout manually (client.log_out(token)).\n" + "Take a note of your token, so you don't have to login every time.\n") + + response = self.authenticate_using_username_password(username, password) + + # if two-factor error + if response.get('body').get('error'): + access_token = self.__two_factor_process_func(response=response, otp_func=otp_func) + + self.trust_this_device() + else: + access_token = response['body']['access_token'] + + confirm("Successfully logged in. Note your token and device-id") + print(f"access_token: {access_token}\n" + f"device-id: {self.__device_id}") + + return access_token @staticmethod def log_out(access_token: str) -> bool: @@ -77,6 +109,27 @@ def __two_factor_process_cli(self, response: dict) -> str: return access_token + def __two_factor_process_func(self, response: dict, otp_func: Callable[[], str] = None) -> str: + """ + Get response from authenticate_with_username_password for a CLI two-factor process + :param response: + :param otp_func: + :return: access_token + """ + + otp_secret = response['headers'].get('venmo-otp-secret') + if not otp_secret: + raise AuthenticationFailedError("Failed to get the otp-secret for the 2-factor authentication process. " + "(check your password)") + + self.send_text_otp(otp_secret=otp_secret) + user_otp = self.__check_otp_validity(otp_func()); + + access_token = self.authenticate_using_otp(user_otp, otp_secret) + self.__api_client.update_access_token(access_token=access_token) + + return access_token + def authenticate_using_username_password(self, username: str, password: str) -> dict: """ Authenticate with username and password. Raises exception if either be incorrect. @@ -178,5 +231,12 @@ def __ask_user_for_otp_password(): otp = input("Enter OTP that you received on your phone from Venmo: (It must be 6 digits)\n") return otp - -# Making modification to file to test if github works the way I think it does \ No newline at end of file + + + @staticmethod + def __check_otp_validity(otp: str): + + if len(otp) >= 6 and otp.isdigit(): + return otp + else: + return __ask_user_for_otp_password() diff --git a/venmo_api/venmo.py b/venmo_api/venmo.py index a8ea1fb..dac6e45 100644 --- a/venmo_api/venmo.py +++ b/venmo_api/venmo.py @@ -1,4 +1,5 @@ from venmo_api import ApiClient, UserApi, PaymentApi, AuthenticationApi, validate_access_token +from collections.abc import Callable, Awaitable class Client(object): @@ -27,17 +28,23 @@ def my_profile(self, force_update=False): return self.__profile @staticmethod - def get_access_token(username: str, password: str, device_id: str = None) -> str: + def get_access_token(username: str, password: str, device_id: str = None, otp_func: Callable[[], str] = None) -> str: """ Log in using your credentials and get an access_token to use in the API :param username: Can be username, phone number (without +1) or email address. :param password: Account's password :param device_id: [optional] A valid device-id. + :param otp_func: [optional] Function to be called when API asks for OTP. :return: access_token """ authn_api = AuthenticationApi(api_client=ApiClient(), device_id=device_id) - return authn_api.login_with_credentials_cli(username=username, password=password) + if otp_func == None: + return authn_api.login_with_credentials_cli(username=username, password=password) + + else: + return authn_api.login_with_credentials_func(username=username, password=password, otp_func=otp_func) + @staticmethod def log_out(access_token) -> bool: From efd9ee3e493e8d4681dc436ea36878c450729cb6 Mon Sep 17 00:00:00 2001 From: Harryacorn2 Date: Mon, 31 Jul 2023 20:40:22 -0700 Subject: [PATCH 3/3] Changed when 2FA function is called Now if the string provided by the external function isn't a valid code (at least 6 characters and all digits) it runs the external function again, the same way the CLI version does. --- venmo_api/apis/auth_api.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/venmo_api/apis/auth_api.py b/venmo_api/apis/auth_api.py index 7454f00..48d4547 100644 --- a/venmo_api/apis/auth_api.py +++ b/venmo_api/apis/auth_api.py @@ -123,7 +123,9 @@ def __two_factor_process_func(self, response: dict, otp_func: Callable[[], str] "(check your password)") self.send_text_otp(otp_secret=otp_secret) - user_otp = self.__check_otp_validity(otp_func()); + user_otp = otp_func() + while not self.__check_otp_validity(user_otp): + user_otp = otp_func() access_token = self.authenticate_using_otp(user_otp, otp_secret) self.__api_client.update_access_token(access_token=access_token) @@ -232,11 +234,10 @@ def __ask_user_for_otp_password(): return otp - @staticmethod def __check_otp_validity(otp: str): if len(otp) >= 6 and otp.isdigit(): - return otp + return True else: - return __ask_user_for_otp_password() + return False \ No newline at end of file