Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Changes to login via sherriff id verification #510

Merged
merged 2 commits into from
Dec 14, 2024

Conversation

ravi-bharadwaj
Copy link
Contributor

No description provided.

@ravi-bharadwaj ravi-bharadwaj changed the title changes to send proper error code back to the user Changes to login via sherriff id verification Dec 6, 2024
Copy link

@Syerramsetti915 Syerramsetti915 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Working as expected

@mw66
Copy link

mw66 commented Dec 6, 2024

@jmfernandes this is an important fix, can you take a look and merge it? Thanks.

raise Exception("Challenge not validated")
raise Exception("Id not returned in user-machine call")

def _get_sherrif_challenge(token_id:str):
Copy link

@tamablevirus tamablevirus Dec 6, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is _get_sheriff_challenge used anywhere?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

its a new function used only in one place

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hes referring to line 241 function, not the validation handler, of which he is correct, the function starting at line 241 is not called anywhere and then the logic calls data value without it being in the args nor subscripted to anything. Just trying to help, the rest of the script seems pretty good

'flow': 'suv',
'input':{'workflow_id': workflow_id}
}
data = request_post(url=url, payload=payload,json=True )

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT extra space at end of request_post params

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed, I had to disable autoformatting on my end as it was introducing lot of changes on PR.

@Adelantado
Copy link

Thanks for all the time and effort, it is very much appreciated, I wish I could help but this is way above my head.
Just to let you know that fix did not work for me, issue persist and that for an unknown and unexpected reason a copy and paste of the code results in an undefined "data" .... unsure if this the reason why it does not work for me.

Data not defined

@bfaircloth1
Copy link

bfaircloth1 commented Dec 7, 2024

Your hard work and devotion are greatly appreciated. I am receiving another KeyError - status. My Robinhood account is a newer account, opened in March 2024. I'm fairly new to Python so there's not much hope for me helping out. You guys rock though!
Picture1

UPDATE: I'm getting the MFA code sent to me, but the script isn't asking me for it. So, I put a breakpoint at this line,
challenge_payload = {
'response': mfa_code
}
and manually entered the mfa_code that I received in the debugger. This worked and I was logged in after this.

I'm a noob at Python and woefully unqualified to be posting anything here, but here are modifications I made to the _validate_sherrif_id method that worked for me (Python 3.12.7 and Spyder 6.0.1):

def _validate_sherrif_id(device_token:str, workflow_id:str, mfa_code:str=None):
  url = "https://api.robinhood.com/pathfinder/user_machine/"
  payload = {
      'device_id': device_token,
      'flow': 'suv',
      'input': {'workflow_id': workflow_id}
  }
  data = request_post(url=url, payload=payload, json=True)
  if "id" in data:
      inquiries_url = f"https://api.robinhood.com/pathfinder/inquiries/{data['id']}/user_view/"
      res = request_get(inquiries_url)
      challenge_id = res['type_context']["context"]["sheriff_challenge"]["id"]
      challenge_url = f"https://api.robinhood.com/challenge/{challenge_id}/respond/"

      if mfa_code is None:
          # Add manual input for MFA code
          mfa_code = input("Please enter your MFA code: ")

      challenge_payload = {
          'response': mfa_code
      }
      challenge_response = request_post(url=challenge_url, payload=challenge_payload, json=True)

      if challenge_response["status"] == "validated":
          inquiries_payload = {"sequence": 0, "user_input": {"status": "continue"}}
          inquiries_response = request_post(url=inquiries_url, payload=inquiries_payload, json=True)
          if inquiries_response["type_context"]["result"] == "workflow_status_approved":
              return
          else:
              raise Exception("Workflow status not approved")
      else:
          raise Exception("Challenge not validated")
  else:
      raise Exception("ID not returned in user-machine call")

@MaxxRK
Copy link

MaxxRK commented Dec 7, 2024

This fixed the login error for me

@Gates8911
Copy link

Gates8911 commented Dec 8, 2024

For anyone else still stuck I found a work around after using the new module that included the extra validation (but still wasnt allowing requests, would return normal like i was logged in but I was not actually logged in, always failed at first request logic). I had to set up 3rd party authentication via the robinhood app, I used google auth app, but I dont think what you use for this matters, as long as the app will generate the totp_secret (random 6 digit code periodically generated and can be requested once you set up the robinhood app to auth using said app. After this I put any sensitive requests into my user environment variables via "edit the system environment variables". After all of this is adjusted, the login is as follows: (I realize to many of you this message is unnecessary, but i wanted to contribute any way i can, had no idea there were this many people trying to keep this module going, greatly appreciate you, and if anyone is interested in indicators and strategies 4 crypto & stocks I have any indicator you could possibly name + some experimentals in testing phase, some great performing strategies that are optimized for dry markets, so they typically dont need much volitility to perform well, and finally I have 3 machine learning models used for predictions (test phase, showing promise, gpu required to run them) u can contact me at mgates8900@gmail.com if any of this interests anyone, but i digress, now onto the work around for the api update):
totp_secret = os.environ['mfa_code'] # set with code you used when you set up 3rd party app to communicate with RobinH
totp = pyotp.TOTP(totp_secret).now()
username = os.environ['your_username']
password = os.environ['your_password']
login_data = robinhood.login(username, password, expiresIn=86400, scope='internal', store_session=True, mfa_code=totp, pickle_name="Name_file_optional")
rs.update_session("access_token", login_data["access_token"]) # manually updates the session instead of relying on auth func
rs.update_session("refresh_token", login_data.get("refresh_token")) #same, happy coding gentlemen :)

@Christopher-C-Robinson
Copy link

This works for me. Will be using this branch until merged in.

@ravi-bharadwaj
Copy link
Contributor Author

@jmfernandes let us know if you have any concerns for merging the PR.

@Two20Two21
Copy link

Thanks everyone for getting this resolved.

Do we understand the root cause? Did the API change?

@Gates8911
Copy link

@Two20Two21 From what I have gathered the root cause was they updated the API to require 2 factor authentication, for example i couldnt get the new authentication script to work until i used a 3rd party app to verify login, whereas before i had device approvals as my security method, and that used to work just fine, it would prompt me to verify via mobile phone when logging the bot in, now if you try this it will still prompt you to verfy new device login but the script will get a key error, so they added an extra step to their security, as for all the details someone else would have to comment that has more experience with that data.

@jmfernandes jmfernandes merged commit 45dd9ed into jmfernandes:master Dec 14, 2024
@samboy84
Copy link

Iam using R and below is the syntax Iam using, it was working last week but now getting below error. Please provide correct syntax to use. Thanks

RH <- RobinHood ("gmail.com", "password", mfa_code="012601")
Error in RobinHood::api_login(username, password, mfa_code) :
Forbidden (HTTP 403)

@tsikerdekis
Copy link

This is honestly the only thing that worked for me.

Your hard work and devotion are greatly appreciated. I am receiving another KeyError - status. My Robinhood account is a newer account, opened in March 2024. I'm fairly new to Python so there's not much hope for me helping out. You guys rock though! Picture1

UPDATE: I'm getting the MFA code sent to me, but the script isn't asking me for it. So, I put a breakpoint at this line, challenge_payload = { 'response': mfa_code } and manually entered the mfa_code that I received in the debugger. This worked and I was logged in after this.

I'm a noob at Python and woefully unqualified to be posting anything here, but here are modifications I made to the _validate_sherrif_id method that worked for me (Python 3.12.7 and Spyder 6.0.1):

def _validate_sherrif_id(device_token:str, workflow_id:str, mfa_code:str=None):
  url = "https://api.robinhood.com/pathfinder/user_machine/"
  payload = {
      'device_id': device_token,
      'flow': 'suv',
      'input': {'workflow_id': workflow_id}
  }
  data = request_post(url=url, payload=payload, json=True)
  if "id" in data:
      inquiries_url = f"https://api.robinhood.com/pathfinder/inquiries/{data['id']}/user_view/"
      res = request_get(inquiries_url)
      challenge_id = res['type_context']["context"]["sheriff_challenge"]["id"]
      challenge_url = f"https://api.robinhood.com/challenge/{challenge_id}/respond/"

      if mfa_code is None:
          # Add manual input for MFA code
          mfa_code = input("Please enter your MFA code: ")

      challenge_payload = {
          'response': mfa_code
      }
      challenge_response = request_post(url=challenge_url, payload=challenge_payload, json=True)

      if challenge_response["status"] == "validated":
          inquiries_payload = {"sequence": 0, "user_input": {"status": "continue"}}
          inquiries_response = request_post(url=inquiries_url, payload=inquiries_payload, json=True)
          if inquiries_response["type_context"]["result"] == "workflow_status_approved":
              return
          else:
              raise Exception("Workflow status not approved")
      else:
          raise Exception("Challenge not validated")
  else:
      raise Exception("ID not returned in user-machine call")

@regholl2023
Copy link

ok,I'm like totally confused.What is the full correct code we are supposed to be using?

@samboy84
Copy link

ok,I'm like totally confused.What is the full correct code we are supposed to be using?

Load required libraries

library(httr)
library(jsonlite)
library(uuid)
library(otp)

Function to authenticate and handle TOTP

RobinHoodAuth <- function(username, password, totp_secret) {

Generate TOTP code using the provided secret

totp <- TOTP$new(totp_secret)
mfa_code <- totp$now()

Generate a device token for unique identification

device_token <- uuid::UUIDgenerate()

Prepare the login payload

login_payload <- list(
username = username,
password = password,
grant_type = "password",
scope = "internal",
expires_in = 86400,
client_id = "c82SH0WZOsabOXGP2sxqcj34",
device_token = device_token,
mfa_code = mfa_code
)

API URL for authentication

login_url <- "https://api.robinhood.com/oauth2/token/"

Send POST request to authenticate

response <- httr::POST(
login_url,
body = toJSON(login_payload, auto_unbox = TRUE),
encode = "json",
add_headers(
Accept = "application/json",
Content-Type = "application/json"
)
)

Check response status

if (response$status_code == 200) {
login_data <- content(response, as = "parsed")

# Manually update the session with access and refresh tokens
access_token <- login_data$access_token
refresh_token <- login_data$refresh_token

# Return the tokens
session_data <- list(
  access_token = access_token,
  refresh_token = refresh_token,
  device_token = device_token
)
return(session_data)

} else {
stop("Authentication failed. Check your credentials or MFA setup.")
}
}

Example usage:

Replace with your own credentials and TOTP secret

username <- "your_username"
password <- "your_password"
totp_secret <- Sys.getenv("mfa_code") # Ensure the environment variable is set

Authenticate and retrieve session data

session_data <- RobinHoodAuth(username, password, totp_secret)
print(session_data)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.