Skip to content

Commit

Permalink
Added ability to programmatically submit a MFA code to Robinhood
Browse files Browse the repository at this point in the history
When calling User.authenticate(), a function can be passed to automatically retrieve the multi-factor authentication code from Robinhood
  • Loading branch information
torreyleonard committed Jan 12, 2019
1 parent cc2aadd commit 3aa7eb1
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 29 deletions.
18 changes: 17 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,12 +109,28 @@ Personally, I either store user data as an array in a .json file, then require i

##### MFA

Algotrader now supports multifactor authentication. So, if you have this enabled on your account (which is a good idea by the way), you'll be prompted to enter the six-digit code after login. If you run a trading script with this library automatically and have MFA enabled, it may be worth your while to utilize a telecom API (possible through Twilio?) to have the code programmatically entered into the CLI.
Algotrader now supports multi-factor authentication. So, if you have this enabled on your account (which is a good idea by the way), you'll be prompted to enter the six-digit code after login. If you run a trading script with this library automatically and have MFA enabled, it may be worth your while to utilize a telecom API (possible through Twilio?) to have the code programmatically entered into the CLI (see below).

The MFA prompt will appear like so:

![Algotrader MFA Prompt](https://i.gyazo.com/11420983d69bf02a59026947513408ac.png)

To enter the code programmatically, you can pass a function to [```User.authenticate()```](https://github.com/Ladinn/algotrader/blob/master/docs/ROBINHOOD.md#User) that returns a promise containing the MFA code in a six-character string. For example:

```js
function getMFA() {
return new Promise((resolve, reject) => {
// Get the code here
const mfa = "123456"
resolve(mfa);
})
}
myUser.authenticate(getMFA)
.then(() => {
// User was authenticated
})
```

#### Get a user's portfolio
There are a good amount of query functions that you can run on the user's portfolio. Using your [```User```](https://github.com/Ladinn/algotrader/blob/master/docs/ROBINHOOD.md#User) instance, you can grab the portfolio using ``` User.getPortfolio()``` which returns a new [```Portfolio```](https://github.com/Ladinn/algotrader/blob/master/docs/ROBINHOOD.md#Portfolio) object.
```js
Expand Down
9 changes: 7 additions & 2 deletions docs/ROBINHOOD.md
Original file line number Diff line number Diff line change
Expand Up @@ -1150,7 +1150,7 @@ Represents the user that is logged in while accessing the Robinhood API.

* [User](#User)
* [new User(username, password)](#new_User_new)
* [.authenticate()](#User+authenticate) ⇒ <code>Promise.&lt;Boolean&gt;</code>
* [.authenticate(mfaFunction)](#User+authenticate) ⇒ <code>Promise.&lt;Boolean&gt;</code>
* [.logout()](#User+logout) ⇒ <code>Promise.&lt;Boolean&gt;</code>
* [.getAccount()](#User+getAccount) ⇒ <code>Promise</code>
* [.getBalances()](#User+getBalances) ⇒ <code>Promise.&lt;Object&gt;</code>
Expand Down Expand Up @@ -1185,10 +1185,15 @@ Creates a new User object.

<a name="User+authenticate"></a>

### user.authenticate() ⇒ <code>Promise.&lt;Boolean&gt;</code>
### user.authenticate(mfaFunction) ⇒ <code>Promise.&lt;Boolean&gt;</code>
Authenticates a user using the inputted username and password.

**Kind**: instance method of [<code>User</code>](#User)

| Param | Type | Description |
| --- | --- | --- |
| mfaFunction | <code>function</code> \| <code>Null</code> | Optional function that is called when prompted for multi-factor authentication. Must return a promise with a six-character string. If not provided the CLI will be prompted. |

<a name="User+logout"></a>

### user.logout() ⇒ <code>Promise.&lt;Boolean&gt;</code>
Expand Down
69 changes: 44 additions & 25 deletions objects/broker/robinhood/User.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,10 @@ class User extends Robinhood {

/**
* Authenticates a user using the inputted username and password.
* @param {Function|Null} mfaFunction - Optional function that is called when prompted for multi-factor authentication. Must return a promise with a six-character string. If not provided the CLI will be prompted.
* @returns {Promise<Boolean>}
*/
authenticate() {
authenticate(mfaFunction) {
const _this = this;
return new Promise((resolve, reject) => {
if (_this.password == null) {
Expand Down Expand Up @@ -67,36 +68,54 @@ class User extends Robinhood {
else {
const json = JSON.parse(body);
if (json.mfa_required) {
console.log("Multifactor authentication detected. Please enter your six-digit code below:");
prompt.get({
properties: {
code: {
pattern: /^[0-9]{6}$/,
message: "Your Robinhood code will most likely be texted to you and should only contain 6 integers.",
required: true
if (mfaFunction !== undefined) {
console.log("Multi-factor authentication detected. Executing the provided function...");
mfaFunction()
.then(mfa => {
if (!mfa instanceof String) reject(new Error("The provided function did not return a string after the promise resolved."));
else if (mfa.length !== 6) reject(new Error("The provided function returned a string, but it is not six-characters in length."));
else _sendMFA(mfa, resolve, reject);
})
.catch(error => {
console.log("An error occurred while executing the provided MFA function.");
reject(error);
})
} else {
console.log("Multi-factor authentication detected. Please enter your six-digit code below:");
console.log(" - This can be entered programmatically by passing a function when authenticating. See documentation for more.");
prompt.get({
properties: {
code: {
pattern: /^[0-9]{6}$/,
message: "Your Robinhood code will most likely be texted to you and should only contain 6 integers.",
required: true
}
}
}
}, (error, result) => {
request.post({
uri: _this.url + '/oauth2/token/',
form: {
username: _this.username,
password: _this.password,
client_id: 'c82SH0WZOsabOXGP2sxqcj34FxkvfnWRZBKlBjFS',
grant_type: 'password',
scope: 'internal',
mfa_code: result.code
}
}, (error, response, body) => {
if (error) reject(error);
else if (response.statusCode !== 200) reject(new LibraryError(body));
else _postAuth(JSON.parse(body), resolve, reject);
}, (error, mfaCode) => {
_sendMFA(mfaCode.code, resolve, reject);
})
})
}
} else _postAuth(json, resolve, reject);
}
})
}
function _sendMFA(mfaCode, resolve, reject) {
request.post({
uri: _this.url + '/oauth2/token/',
form: {
username: _this.username,
password: _this.password,
client_id: 'c82SH0WZOsabOXGP2sxqcj34FxkvfnWRZBKlBjFS',
grant_type: 'password',
scope: 'internal',
mfa_code: mfaCode
}
}, (error, response, body) => {
if (error) reject(error);
else if (response.statusCode !== 200) reject(new LibraryError(body));
else _postAuth(JSON.parse(body), resolve, reject);
})
}
function _postAuth(json, resolve, reject) {
_this.token = json.access_token;
_this.getAccount().then(account => {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "algotrader",
"version": "1.4.1",
"version": "1.4.2",
"description": "Algorithmically trade stocks and options using Robinhood, Yahoo Finance, and more.",
"main": "index.js",
"scripts": {
Expand Down

0 comments on commit 3aa7eb1

Please sign in to comment.