def logout(): """Removes authorization from the session header. :returns: None """ helper.update_session('Authorization',None)
def get_new_device_token(username, password,by_sms=True): """This function will create and activate a new device token for the user, which should be stored and used for future login attempts. :param username: The username for your robinhood account. Usually your email. :type username: str :param password: The password for your robinhood account. :type password: str :param by_sms: Specifies whether to send an email(False) or an sms(True) for auth. :type by_sms: Optional[boolean] :returns: A string which is the device token or None if the token could not be validated with an sms code. """ device_token = generate_device_token() initial_login = base_login(username, password, device_token,by_sms=by_sms) if 'challenge' not in initial_login: global LOGIN_DATA LOGIN_DATA = initial_login helper.set_device_token(device_token) return(device_token) challenge_id = initial_login['challenge']['id'] sms_code = input('Enter sms code for validating device_token: ') res = respond_to_challenge(challenge_id, sms_code) while 'challenge' in res and res['challenge']['remaining_attempts'] > 0: sms_code = input('Incorrect code, try again: ') res = respond_to_challenge(challenge_id, sms_code) if 'status' in res and res['status'] == 'validated': helper.update_session('X-ROBINHOOD-CHALLENGE-RESPONSE-ID', challenge_id) helper.set_device_token(device_token) return(device_token) else: raise Exception(res['detail'])
def login(username, password, expiresIn=86400, scope='internal'): """This function will effectivly log the user into robinhood by getting an authentication token and saving it to the session header. :param username: The username for your robinhood account. Usually your email. :type username: str :param password: The password for your robinhood account. :type password: str :param expiresIn: The time until your login session expires. This is in seconds. :type expiresIn: Optional[int] :param scope: Specifies the scope of the authentication. :type scope: Optional[str] :returns: A dictionary with log in information. """ if not username or not password: raise Exception('login must be called with a non-empty username and ' 'password') url = urls.login_url() payload = { 'client_id': 'c82SH0WZOsabOXGP2sxqcj34FxkvfnWRZBKlBjFS', 'expires_in': expiresIn, 'grant_type': 'password', 'password': password, 'scope': scope, 'username': username } data = helper.request_post(url, payload) token = 'Bearer {}'.format(data['access_token']) helper.update_session('Authorization', token) helper.set_login_state(True) return (data)
def logout(): """Removes authorization from the session header. :returns: None """ helper.set_login_state(False) helper.update_session('Authorization', "")
def send_challenge_response(username, password, challenge_id, sms_code): respond_to_challenge(challenge_id, sms_code) helper.update_session('X-ROBINHOOD-CHALLENGE-RESPONSE-ID', challenge_id) data = _post_to_login(username, password) if 'access_token' in data: return _save_credentials(data) else: print(data) raise AuthError(data)
def logout(): """Removes authorization from the session header. :returns: None """ helper.set_login_state(False) helper.update_session('Authorization', None) print('Successfully Logged Out.')
def refresh_token(refresh_token=None): url = "https://api.robinhood.com/oauth2/token/" payload = { "grant_type": "refresh_token", "refresh_token": refresh_token, "scope": "internal", "client_id": "c82SH0WZOsabOXGP2sxqcj34FxkvfnWRZBKlBjFS", "expires_in": 734000, } helper.update_session('Authorization', None) data = helper.request_post(url, payload) return data
def login2(data, payload, url, pickle_path, device_token, auth_key, store_session=True): if data: if 'mfa_required' in data: mfa_token = auth_key payload['mfa_code'] = mfa_token res = helper.request_post(url, payload, jsonify_data=False) if (res.status_code != 200): raise Exception( "That MFA code was not correct. Please type in another MFA code." ) data = res.json() elif 'challenge' in data: challenge_id = data['challenge']['id'] sms_code = auth_key res = respond_to_challenge(challenge_id, sms_code) if 'challenge' in res and res['challenge'][ 'remaining_attempts'] > 0: raise Exception( 'That code was not correct. {0} tries remaining. Please type in another code: ' .format(res['challenge']['remaining_attempts'])) helper.update_session('X-ROBINHOOD-CHALLENGE-RESPONSE-ID', challenge_id) data = helper.request_post(url, payload) # Update Session data with authorization or raise exception with the information present in data. if 'access_token' in data: token = '{0} {1}'.format(data['token_type'], data['access_token']) helper.update_session('Authorization', token) helper.set_login_state(True) data['detail'] = "logged in with brand new authentication code." if store_session: with open(pickle_path, 'wb') as f: pickle.dump( { 'token_type': data['token_type'], 'access_token': data['access_token'], 'refresh_token': data['refresh_token'], 'device_token': device_token }, f) else: raise Exception(data['detail']) else: raise Exception( 'Error: Trouble connecting to robinhood API. Check internet connection.' ) return True, (data)
def lambda_handler(event, context): """ Lambda function to return the current positions held in the Robinhood account. Using this to test functionality of this whole shebang before I go further. This is a stripped down version of the robin_stocks.authentication.login function. Lambda only allows writing to /tmp directory, so the concept of pickling a file into the home directory won't work. Using SSM securestring for non-ephemeral replacement because its free (to 40 TPS), encrypted, and SSM is backed by dynamodb so its fast. """ robinhood_credentials = robinhood_creds() #Trick robin_stocks into thinking login was successful. helper.set_login_state(True) #Set the JWT. helper.update_session( 'Authorization', '{0} {1}'.format(robinhood_credentials['token_type'], robinhood_credentials['access_token'])) #Skip verification. Can handle this here if needed. #result = robin_stocks.account.get_current_positions() result = robin_stocks.get_name_by_symbol('aapl') try: return { 'statusCode': 200, 'body': json.dumps(result, cls=LambdaMessageEncoder), 'headers': { 'Content-Type': 'application/json' } } except Exception as e: _LOGGER.error( 'Something went wrong querying Robinhood account. {0}'.format(e)) return { 'statusCode': 500, 'body': json.dumps( { 'message': 'Something went wrong querying Robinhood account. {0}'. format(e) }, cls=LambdaMessageEncoder), 'headers': { 'Content-Type': 'application/json' } }
def __init__(self): """ The constructor will download credentials stored in DDB and set robin_stocks to a logged-in state. """ _robinhood_credentials = robinhood_creds() self.expiry = _robinhood_credentials['expiry'] #Trick robin_stocks into thinking login was successful. helper.set_login_state(True) #Set the JWT. helper.update_session( 'Authorization', '{0} {1}'.format(_robinhood_credentials['token_type'], _robinhood_credentials['access_token']))
def logout(): """Removes authorization from the session header. :returns: None """ helper.set_login_state(False) helper.update_session('Authorization', None) home_dir = os.path.expanduser("~") data_dir = os.path.join(home_dir, ".tokens") if not os.path.exists(data_dir): return creds_file = "robinhood.pickle" pickle_path = os.path.join(data_dir, creds_file) if os.path.isfile(pickle_path): os.remove(pickle_path)
def process_login(username=None, password=None, device_token=None, mfa_code=None, challenge_id=None): expires_in = 86400 scope = 'internal' challenge_type = 'sms' url = urls.login_url() payload = { 'client_id': 'c82SH0WZOsabOXGP2sxqcj34FxkvfnWRZBKlBjFS', 'expires_in': expires_in, 'grant_type': 'password', 'password': password, 'scope': scope, 'username': username, 'challenge_type': challenge_type, 'device_token': device_token } if challenge_id != None: helper.update_session('X-ROBINHOOD-CHALLENGE-RESPONSE-ID', challenge_id) data = helper.request_post(url, payload) return data
def login(username, password, device_token=TEMP_DEVICE_TOKEN, expiresIn=86400, scope='internal'): """This function will effectivly log the user into robinhood by getting an authentication token and saving it to the session header. :param username: The username for your robinhood account. Usually your email. :type username: str :param password: The password for your robinhood account. :type password: str :param device_token: The device_token you should re-use (can be saved with "robin_stocks.get_new_device_token()"). :type device_token: str :param expiresIn: The time until your login session expires. This is in seconds. :type expiresIn: Optional[int] :param scope: Specifies the scope of the authentication. :type scope: Optional[str] :returns: A dictionary with log in information. """ if not device_token: TEMP_DEVICE_TOKEN = get_new_device_token(username, password) device_token = TEMP_DEVICE_TOKEN if not LOGIN_DATA: data = base_login(username, password, device_token) if LOGIN_DATA: token = 'Bearer {}'.format(LOGIN_DATA['access_token']) helper.update_session('Authorization', token) helper.set_login_state(True) data = LOGIN_DATA elif 'access_token' in data: token = 'Bearer {}'.format(data['access_token']) helper.update_session('Authorization', token) helper.set_login_state(True) else: print(data) return (data)
def login(username, password, expiresIn=86400, scope='internal'): """This function will effectivly log the user into robinhood by getting an authentication token and saving it to the session header. :param name: The username. :type name: str :param password: The password. :type state: str :returns: A dictionary with log in information. """ url = urls.login_url() payload = { 'client_id': 'c82SH0WZOsabOXGP2sxqcj34FxkvfnWRZBKlBjFS', 'expires_in': expiresIn, 'grant_type': 'password', 'password': password, 'scope': scope, 'username': username } data = helper.request_post(url, payload) token = 'Bearer {}'.format(data['access_token']) helper.update_session('Authorization', token) return (data)
def login(): """This function will effectively log the user into robinhood by getting an authentication token and saving it to the session header. By default, it will store the authentication token in a pickle file and load that value on subsequent logins. :param username: The username for your robinhood account, usually your email. Not required if credentials are already cached and valid. :type username: Optional[str] :param password: The password for your robinhood account. Not required if credentials are already cached and valid. :type password: Optional[str] :param expiresIn: The time until your login session expires. This is in seconds. :type expiresIn: Optional[int] :param scope: Specifies the scope of the authentication. :type scope: Optional[str] :param by_sms: Specifies whether to send an email(False) or an sms(True) :type by_sms: Optional[boolean] :param store_session: Specifies whether to save the log in authorization for future log ins. :type store_session: Optional[boolean] :param mfa_code: MFA token if enabled. :type mfa_code: Optional[str] :returns: A dictionary with log in information. The 'access_token' keyword contains the access token, and the 'detail' keyword \ contains information on whether the access token was generated or loaded from pickle file. """ print('logging in') username = None password = None expiresIn = 86400 scope = 'internal' by_sms = True store_session = True mfa_code = None device_token = generate_device_token() # home_dir = path if path else os.path.expanduser("~") home_dir = os.path.expanduser("~") data_dir = os.path.join(home_dir, ".tokens") if not os.path.exists(data_dir): os.makedirs(data_dir) creds_file = "robinhood.pickle" pickle_path = os.path.join(data_dir, creds_file) print(pickle_path) # Challenge type is used if not logging in with two-factor authentication. if by_sms: challenge_type = "sms" else: challenge_type = "email" url = urls.login_url() payload = { 'client_id': 'c82SH0WZOsabOXGP2sxqcj34FxkvfnWRZBKlBjFS', 'expires_in': expiresIn, 'grant_type': 'password', 'password': password, 'scope': scope, 'username': username, 'challenge_type': challenge_type, 'device_token': device_token } if mfa_code: payload['mfa_code'] = mfa_code # If authentication has been stored in pickle file then load it. Stops login server from being pinged so much. if os.path.isfile(pickle_path): # if blob_pickle_path: # If store_session has been set to false then delete the pickle file, otherwise try to load it. # Loading pickle file will fail if the acess_token has expired. if store_session: try: with open(pickle_path, 'rb') as f: # if blob_pickle_path: pickle_data = pickle.load(f) print('loaded pickle') # pickle_data = pickle.loads(blob_pickle) # print('loaded blob_pickle') access_token = pickle_data['access_token'] token_type = pickle_data['token_type'] refresh_token = pickle_data['refresh_token'] # Set device_token to be the original device token when first logged in. pickle_device_token = pickle_data['device_token'] payload['device_token'] = pickle_device_token # Set login status to True in order to try and get account info. helper.set_login_state(True) helper.update_session( 'Authorization', '{0} {1}'.format(token_type, access_token)) # Try to load account profile to check that authorization token is still valid. res = helper.request_get( urls.portfolio_profile(), 'regular', payload, jsonify_data=False) # Raises exception is response code is not 200. res.raise_for_status() return({'access_token': access_token, 'token_type': token_type, 'expires_in': expiresIn, 'scope': scope, 'detail': 'logged in using authentication in {0}'.format(creds_file), 'backup_code': None, 'refresh_token': refresh_token}) except: print( "ERROR: There was an issue loading pickle file. Authentication may be expired - logging in normally.", file=helper.get_output()) helper.set_login_state(False) helper.update_session('Authorization', None) else: os.remove(pickle_path) # Try to log in normally. if not username: # username = input("Robinhood username: "******"RB_USER") if not password: # password = getpass.getpass("Robinhood password: "******"RB_PASS") data = helper.request_post(url, payload) # Handle case where mfa or challenge is required. if data: if 'mfa_required' in data: # totp = str(pyotp.TOTP(access_secret_version('quantwannabe', 'RB_TOKEN', 'latest')).now()) totp = str(pyotp.TOTP(os.environ.get("RB_TOKEN")).now()) print(f"logging in with {totp} at {str(datetime.now())}") # mfa_token = input("Please type in the MFA code: ") mfa_token=totp payload['mfa_code'] = mfa_token res = helper.request_post(url, payload, jsonify_data=False) while (res.status_code != 200): mfa_token = input( "That MFA code was not correct. Please type in another MFA code: ") payload['mfa_code'] = mfa_token res = helper.request_post(url, payload, jsonify_data=False) data = res.json() elif 'challenge' in data: challenge_id = data['challenge']['id'] sms_code = input('Enter Robinhood code for validation: ') res = respond_to_challenge(challenge_id, sms_code) while 'challenge' in res and res['challenge']['remaining_attempts'] > 0: sms_code = input('That code was not correct. {0} tries remaining. Please type in another code: '.format( res['challenge']['remaining_attempts'])) res = respond_to_challenge(challenge_id, sms_code) helper.update_session( 'X-ROBINHOOD-CHALLENGE-RESPONSE-ID', challenge_id) data = helper.request_post(url, payload) # Update Session data with authorization or raise exception with the information present in data. if 'access_token' in data: token = '{0} {1}'.format(data['token_type'], data['access_token']) helper.update_session('Authorization', token) helper.set_login_state(True) data['detail'] = "logged in with brand new authentication code." if store_session: with open(pickle_path, 'wb') as f: pickle.dump({'token_type': data['token_type'], 'access_token': data['access_token'], 'refresh_token': data['refresh_token'], 'device_token': device_token}, f) print(f'updated pickle in {pickle_path}') # blob = bucket.blob(blob_pickle_path) # print(f'upload blob from {pickle_path} to {blob_pickle_path}') # with open(pickle_path, 'rb') as f: # new_pickle = f.read() # blob.upload_from_string(new_pickle) else: raise Exception(data['detail']) else: raise Exception('Error: Trouble connecting to robinhood API. Check internet connection.') return data
def token_login(access_token=None, token_type=None): token = '{0} {1}'.format(token_type, access_token) helper.update_session('Authorization', token) helper.set_login_state(True)
def login(username=None, password=None, expiresIn=86400, scope='internal', by_sms=True, store_session=True, mfa_code=None): """This function will effectively log the user into robinhood by getting an authentication token and saving it to the session header. By default, it will store the authentication token in a pickle file and load that value on subsequent logins. :param username: The username for your robinhood account, usually your email. Not required if credentials are already cached and valid. :type username: Optional[str] :param password: The password for your robinhood account. Not required if credentials are already cached and valid. :type password: Optional[str] :param expiresIn: The time until your login session expires. This is in seconds. :type expiresIn: Optional[int] :param scope: Specifies the scope of the authentication. :type scope: Optional[str] :param by_sms: Specifies whether to send an email(False) or an sms(True) :type by_sms: Optional[boolean] :param store_session: Specifies whether to save the log in authorization for future log ins. :type store_session: Optional[boolean] :param mfa_code: MFA token if enabled. :type mfa_code: Optional[str] :returns: A dictionary with log in information. The 'access_token' keyword contains the access token, and the 'detail' keyword \ contains information on whether the access token was generated or loaded from pickle file. """ device_token = generate_device_token() home_dir = os.path.expanduser("~") data_dir = os.path.join(home_dir, ".tokens") if not os.path.exists(data_dir): os.makedirs(data_dir) creds_file = "robinhood.pickle" pickle_path = os.path.join(data_dir, creds_file) # Challenge type is used if not logging in with two-factor authentication. if by_sms: challenge_type = "sms" else: challenge_type = "email" url = urls.login_url() payload = { 'client_id': 'c82SH0WZOsabOXGP2sxqcj34FxkvfnWRZBKlBjFS', 'expires_in': expiresIn, 'grant_type': 'password', 'password': password, 'scope': scope, 'username': username, 'challenge_type': challenge_type, 'device_token': device_token } if mfa_code: payload['mfa_code'] = mfa_code # If authentication has been stored in pickle file then load it. Stops login server from being pinged so much. if os.path.isfile(pickle_path): # If store_session has been set to false then delete the pickle file, otherwise try to load it. # Loading pickle file will fail if the access_token has expired. if store_session: try: with open(pickle_path, 'rb') as f: pickle_data = pickle.load(f) access_token = pickle_data['access_token'] token_type = pickle_data['token_type'] refresh_token = pickle_data['refresh_token'] # Set device_token to be the original device token when first logged in. pickle_device_token = pickle_data['device_token'] payload['device_token'] = pickle_device_token # Set login status to True in order to try and get account info. helper.set_login_state(True) helper.update_session( 'Authorization', '{0} {1}'.format(token_type, access_token)) # Try to load account profile to check that authorization token is still valid. res = helper.request_get(urls.portfolio_profile(), 'regular', payload, jsonify_data=False) # Raises exception is response code is not 200. res.raise_for_status() return ({ 'access_token': access_token, 'token_type': token_type, 'expires_in': expiresIn, 'scope': scope, 'detail': 'logged in using authentication in {0}'.format( creds_file), 'backup_code': None, 'refresh_token': refresh_token }) except: print( "ERROR: There was an issue loading pickle file. Authentication may be expired - logging in normally." ) helper.set_login_state(False) helper.update_session('Authorization', None) else: os.remove(pickle_path) # Try to log in normally. if not username: username = input("Robinhood username: "******"Robinhood password: ") payload['password'] = password data = helper.request_post(url, payload) return data, payload, url, pickle_path, device_token
def respond_challenge(username, password, device_token, mfa_token=None, sms_code=None, expiresIn=86400, scope='internal', by_sms=True): if by_sms: challenge_type = "sms" else: challenge_type = "email" payload = { 'client_id': 'c82SH0WZOsabOXGP2sxqcj34FxkvfnWRZBKlBjFS', 'expires_in': expiresIn, 'grant_type': 'password', 'password': password, 'scope': scope, 'username': username, 'challenge_type': challenge_type, 'device_token': device_token } url = urls.login_url() if mfa_token: payload['mfa_required'] = True payload['mfa_code'] = mfa_token # data = payload['login_response'] data = payload response = {'status': "Success"} success = False # Handle case where mfa or challenge is required. if data: if 'mfa_required' in data: res = helper.request_post(url, payload, jsonify_data=False) data = res.json() elif 'challenge' in data: challenge_id = data['challenge']['id'] res = authentication.respond_to_challenge(challenge_id, sms_code) helper.update_session('X-ROBINHOOD-CHALLENGE-RESPONSE-ID', challenge_id) data = helper.request_post(url, payload) # Update Session data with authorization or raise exception with the information present in data. if 'access_token' in data: token = '{0} {1}'.format(data['token_type'], data['access_token']) helper.update_session('Authorization', token) helper.set_login_state(True) data['detail'] = "Logged in with brand new authentication code." success = True else: response['status'] = data['detail'] # raise Exception(data['detail']) else: response['status'] = 'Error: Trouble connecting to robinhood API.' # raise Exception('Error: Trouble connecting to robinhood API. Check internet connection.') # import orders if success: update_results = import_orders() response['updates'] = update_results else: response['updates'] = None return response
def lambda_handler(event, context): """ Lambda function to generate and cache full Robinhood login details using the 2FA code. This is a stripped down version of the robin_stocks.authentication module. """ try: body = json.loads(event['body']) code = body['code'] challenge_id = body['challenge'] username = body['username'] password = body['password'] sendviasms = body['sms'] except KeyError: return { 'statusCode': 400, 'body': json.dumps( { 'rh_login_successful': False, 'message': 'No parameters specified or missing parameters.' }, cls=LambdaMessageEncoder), 'headers': { 'Content-Type': 'application/json' } } ddb_client = boto3.resource('dynamodb') table = ddb_client.Table(os.environ['CREDENTIALS_TABLE']) try: device_token = table.get_item( Key={'credsPlatform': 'robinhood'})['Item']['deviceToken'] except Exception: _LOGGER.error('Unable to grab Robinhood device token.') return { 'statusCode': 500, 'body': json.dumps( { 'rh_login_successful': False, 'message': 'Something went wrong server-side.' }, cls=LambdaMessageEncoder), 'headers': { 'Content-Type': 'application/json' } } url = urls.login_url() if sendviasms: challenge_type = "sms" else: challenge_type = "email" #Client ID appears to be a hardcoded magic number from client requests? #Something to watch out for. Could be related to User-Agent & app version. payload = { 'client_id': 'c82SH0WZOsabOXGP2sxqcj34FxkvfnWRZBKlBjFS', 'expires_in': 86400, 'grant_type': 'password', 'password': password, 'scope': 'internal', 'username': username, 'challenge_type': challenge_type, 'device_token': device_token } #Send back the 2FA code to get a challenge ID. res = respond_to_challenge(challenge_id, code) #For code failures. if 'challenge' in res: return { 'statusCode': 401, 'body': json.dumps( { 'rh_login_successful': False, 'message': 'Failed to login.' }, cls=LambdaMessageEncoder), 'headers': { 'Content-Type': 'application/json' } } #Request to receive the JWT. helper.update_session('X-ROBINHOOD-CHALLENGE-RESPONSE-ID', challenge_id) data = helper.request_post(url, payload) one_day_expiry = datetime.now(timezone.utc) + timedelta(seconds=86400) if 'access_token' in data: try: table.put_item( Item={ 'credsPlatform': 'robinhood', 'deviceToken': device_token, 'tokenType': data['token_type'], 'accessToken': data['access_token'], 'refreshToken': data['refresh_token'], 'expiry': one_day_expiry.isoformat() }) except Exception as e: _LOGGER.error('Unable stick Robinhood credentials into DDB.') return { 'statusCode': 500, 'body': json.dumps( { 'rh_login_successful': False, 'message': 'Something went wrong server-side.' }, cls=LambdaMessageEncoder), 'headers': { 'Content-Type': 'application/json' } } return { 'statusCode': 200, 'body': json.dumps( { 'rh_login_successful': True, 'message': 'Successfully authenticated. Logging in.' }, cls=LambdaMessageEncoder), 'headers': { 'Content-Type': 'application/json' } }
def request_get(url, dataType='regular', payload=None, jsonify_data=True, access_token=''): """For a given url and payload, makes a get request and returns the data. :param url: The url to send a get request to. :type url: str :param dataType: Determines how to filter the data. 'regular' returns the unfiltered data. \ 'results' will return data['results']. 'pagination' will return data['results'] and append it with any \ data that is in data['next']. 'indexzero' will return data['results'][0]. :type dataType: Optional[str] :param payload: Dictionary of parameters to pass to the url. Will append the requests url as url/?key1=value1&key2=value2. :type payload: Optional[dict] :param jsonify_data: If this is true, will return requests.post().json(), otherwise will return response from requests.post(). :type jsonify_data: bool :returns: Returns the data from the get request. If jsonify_data=True and requests returns an http code other than <200> \ then either '[None]' or 'None' will be returned based on what the dataType parameter was set as. """ if (dataType == 'results' or dataType == 'pagination'): data = [None] else: data = None res = None if jsonify_data: try: ### helper.set_login_state(True) helper.update_session('Authorization', '{0} {1}'.format('Bearer', access_token)) ### res = SESSION.get(url, params=payload) res.raise_for_status() ### helper.set_login_state(False) helper.update_session('Authorization', None) ### data = res.json() except (requests.exceptions.HTTPError, AttributeError) as message: print(message, file=get_output()) helper.set_login_state(False) helper.update_session('Authorization', None) return (data) else: ### helper.set_login_state(True) helper.update_session('Authorization', '{0} {1}'.format('Bearer', access_token)) ### res = SESSION.get(url, params=payload) ### helper.set_login_state(False) helper.update_session('Authorization', None) ### return (res) # Only continue to filter data if jsonify_data=True, and Session.get returned status code <200>. if (dataType == 'results'): try: data = data['results'] except KeyError as message: print("{0} is not a key in the dictionary".format(message), file=get_output()) return ([None]) elif (dataType == 'pagination'): counter = 2 nextData = data try: data = data['results'] except KeyError as message: print("{0} is not a key in the dictionary".format(message), file=get_output()) return ([None]) if nextData['next']: print('Found Additional pages.', file=get_output()) while nextData['next']: try: ### helper.set_login_state(True) helper.update_session('Authorization', '{0} {1}'.format('Bearer', access_token)) ### res = SESSION.get(nextData['next']) res.raise_for_status() ### helper.set_login_state(False) helper.update_session('Authorization', None) ### nextData = res.json() except: print('Additional pages exist but could not be loaded.', file=get_output()) ### helper.set_login_state(False) helper.update_session('Authorization', None) ### return (data) print('Loading page ' + str(counter) + ' ...', file=get_output()) counter += 1 for item in nextData['results']: data.append(item) elif (dataType == 'indexzero'): try: data = data['results'][0] except KeyError as message: print("{0} is not a key in the dictionary".format(message), file=get_output()) helper.set_login_state(False) helper.update_session('Authorization', None) return (None) except IndexError as message: helper.set_login_state(False) helper.update_session('Authorization', None) return (None) return (data)
def set_token(token): helper.update_session('Authorization', token) helper.set_login_state(True)
def login(username, password, expiresIn=86400, scope='internal', by_sms=True, store_session=True): """This function will effectivly log the user into robinhood by getting an authentication token and saving it to the session header. By default, it will store the authentication token in a pickle file and load that value on subsequent logins. :param username: The username for your robinhood account. Usually your email. :type username: str :param password: The password for your robinhood account. :type password: str :param expiresIn: The time until your login session expires. This is in seconds. :type expiresIn: Optional[int] :param scope: Specifies the scope of the authentication. :type scope: Optional[str] :param by_sms: Specifies whether to send an email(False) or an sms(True) :type by_sms: Optional[boolean] :param store_session: Specifies whether to save the log in authorization for future log ins. :type store_session: Optional[boolean] :returns: A dictionary with log in information. The 'access_token' keyword contains the access token, and the 'detail' keyword \ contains information on whether the access token was generated or loaded from pickle file. """ device_token = generate_device_token() dir_path = os.path.dirname(os.path.realpath(__file__)) pickle_path = os.path.join(dir_path, "data.pickle") # Challenge type is used if not logging in with two-factor authentication. if by_sms: challenge_type = "sms" else: challenge_type = "email" url = urls.login_url() payload = { 'client_id': 'c82SH0WZOsabOXGP2sxqcj34FxkvfnWRZBKlBjFS', 'expires_in': expiresIn, 'grant_type': 'password', 'password': password, 'scope': scope, 'username': username, 'challenge_type': challenge_type, 'device_token': device_token } # If authentication has been stored in pickle file then load it. Stops login server from being pinged so much. if os.path.isfile(pickle_path): # If store_session has been set to false then delete the pickle file, otherwise try to load it. # Loading pickle file will fail if the acess_token has expired. if store_session: try: with open(pickle_path, 'rb') as f: pickle_data = pickle.load(f) access_token = pickle_data['access_token'] token_type = pickle_data['token_type'] refresh_token = pickle_data['refresh_token'] # Set device_token to be the original device token when first logged in. pickle_device_token = pickle_data['device_token'] payload['device_token'] = pickle_device_token # Set login status to True in order to try and get account info. helper.set_login_state(True) helper.update_session( 'Authorization', '{0} {1}'.format(token_type, access_token)) # Try to load account profile to check that authorization token is still valid. res = helper.request_get(urls.portfolio_profile(), 'regular', payload, jsonify_data=False) # Raises exception is response code is not 200. res.raise_for_status() return ({ 'access_token': access_token, 'token_type': token_type, 'expires_in': expiresIn, 'scope': scope, 'detail': 'logged in using authentication in data.pickle', 'backup_code': None, 'refresh_token': refresh_token }) except: print( "ERROR: There was an issue loading pickle file. Authentication may be expired - logging in normally." ) helper.set_login_state(False) helper.update_session('Authorization', None) else: os.remove(pickle_path) # Try to log in normally. data = helper.request_post(url, payload) # Handle case where mfa or challenge is required. if 'mfa_required' in data: mfa_token = input("Please type in the MFA code: ") payload['mfa_code'] = mfa_token res = helper.request_post(url, payload, jsonify_data=False) while (res.status_code != 200): mfa_token = input( "That MFA code was not correct. Please type in another MFA code: " ) payload['mfa_code'] = mfa_token res = helper.request_post(url, payload, jsonify_data=False) data = res.json() elif 'challenge' in data: challenge_id = data['challenge']['id'] sms_code = input('Enter Robinhood code for validation: ') res = respond_to_challenge(challenge_id, sms_code) while 'challenge' in res and res['challenge']['remaining_attempts'] > 0: sms_code = input( 'That code was not correct. {0} tries remaining. Please type in another code: ' .format(res['challenge']['remaining_attempts'])) res = respond_to_challenge(challenge_id, sms_code) helper.update_session('X-ROBINHOOD-CHALLENGE-RESPONSE-ID', challenge_id) data = helper.request_post(url, payload) # Update Session data with authorization or raise exception with the information present in data. if 'access_token' in data: token = '{0} {1}'.format(data['token_type'], data['access_token']) helper.update_session('Authorization', token) helper.set_login_state(True) data['detail'] = "logged in with brand new authentication code." if store_session: with open(pickle_path, 'wb') as f: pickle.dump( { 'token_type': data['token_type'], 'access_token': data['access_token'], 'refresh_token': data['refresh_token'], 'device_token': device_token }, f) else: raise Exception(data['detail']) return (data)
def verify(username=None, password=None, expiresIn=86400, scope='internal', by_sms=True, store_session=True, auth_type="challenge", code=None, device_token=None, challenge_id=None): """This function will effectivly log the user into robinhood by getting an authentication token and saving it to the session header. By default, it will store the authentication token in a pickle file and load that value on subsequent logins. :param username: The username for your robinhood account, usually your email. Not required if credentials are already cached and valid. :type username: Optional[str] :param password: The password for your robinhood account. Not required if credentials are already cached and valid. :type password: Optional[str] :param expiresIn: The time until your login session expires. This is in seconds. :type expiresIn: Optional[int] :param scope: Specifies the scope of the authentication. :type scope: Optional[str] :param by_sms: Specifies whether to send an email(False) or an sms(True) :type by_sms: Optional[boolean] :param store_session: Specifies whether to save the log in authorization for future log ins. :type store_session: Optional[boolean] :param auth_type: Specifies whetner authorization is through mfa code or sms challenge :type auth_type: Optional[str] :param code: Mfa code or sms challenge code :type code: Required[str] :param device_token: Previously used device token :type device_token: Required[str] :param challenge_id: Challenge id :type challenge_id: Required[str] :returns: A dictionary with log in information. The 'access_token' keyword contains the access token, and the 'detail' keyword \ contains information on whether the access token was generated or loaded from pickle file. """ home_dir = os.path.expanduser("~") data_dir = os.path.join(home_dir, ".tokens") if not os.path.exists(data_dir): os.makedirs(data_dir) creds_file = "robinhood.pickle" pickle_path = os.path.join(data_dir, creds_file) # Challenge type is used if not logging in with two-factor authentication. if by_sms: challenge_type = "sms" else: challenge_type = "email" url = urls.login_url() payload = { 'client_id': 'c82SH0WZOsabOXGP2sxqcj34FxkvfnWRZBKlBjFS', 'expires_in': expiresIn, 'grant_type': 'password', 'password': password, 'scope': scope, 'username': username, 'challenge_type': challenge_type, 'device_token': device_token } if auth_type == "mfa": mfa_token = code payload['mfa_code'] = mfa_token res = helper.request_post(url, payload, jsonify_data=False) if res.status_code != 200: return {'Error': "Incorrect code"} data = res.json() elif auth_type == "challenge": sms_code = code print("challenge:\n", challenge_id, code) res = respond_to_challenge(challenge_id, sms_code) if 'challenge' in res and res['challenge']['remaining_attempts'] > 0: print("retry\n") return {'error': "Incorrect code", 'attempts_left': res['challenge']['remaining_attempts']} else: print('res', res) helper.update_session( 'X-ROBINHOOD-CHALLENGE-RESPONSE-ID', challenge_id) print('payload:\n', payload) data = helper.request_post(url, payload) print('data:\n', data) # Update Session data with authorization or raise exception with the information present in data. if 'access_token' in data: token = '{0} {1}'.format(data['token_type'], data['access_token']) helper.update_session('Authorization', token) helper.set_login_state(True) data['detail'] = "logged in with brand new authentication code." print('store', store_session) if store_session: print('yeboi') with open(pickle_path, 'wb') as f: pickle.dump({'token_type': data['token_type'], 'access_token': data['access_token'], 'refresh_token': data['refresh_token'], 'device_token': device_token}, f) else: return {"error": data["detail"]} return(data)