Skip to content

Commit 7d947db

Browse files
authored
🐟 Support 2FA (#3)
1 parent 36dabd9 commit 7d947db

File tree

6 files changed

+124
-45
lines changed

6 files changed

+124
-45
lines changed

README.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ A NodeJS wrapper for the Venmo API.
44

55
## Usage
66

7-
### Login
7+
### Login / Logout
88

9-
Call `Venmo.login(username, password)` with username and password to sign in, generate an access key, and automatically set it.
9+
Call `Venmo.login(username, password)` with username and password to sign in, generate an access key, and automatically set it. May require a 2FA code to be input. It will also return the personal access token. To logout, call `Venmo.logout()`. Optionally provide the access token to logout of a specific account with `Venmo.logout(accessToken)`.
1010

1111
### User Information
1212

@@ -64,7 +64,7 @@ Call `Venmo.getTransactions` with a userID to retrieve a list of transactions in
6464

6565
### Errors
6666

67-
Errors will be thrown if there are invalid credentials (or not passed), or if you hit Venmo's rate limit.
67+
Errors will be thrown if there are invalid credentials (or not passed), or if you hit Venmo's rate limit (quite low).
6868

6969
### Example
7070

@@ -84,3 +84,7 @@ Venmo.getUserIDfromUsername("username")
8484
Venmo.logout();
8585
});
8686
```
87+
88+
### Practically
89+
90+
You can build a easy server with this package to serve a frontend such as [venmo.lol](https://github.com/pineapplelol/venmo.lol). Note that Venmo's rate limit is quite low, so you should aim to cache results of API calls to prevent duplicate calls.

index.js

Lines changed: 0 additions & 3 deletions
This file was deleted.

package-lock.json

Lines changed: 14 additions & 14 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
{
22
"name": "venmojs",
3-
"version": "0.0.3",
3+
"version": "0.0.4",
44
"description": "Venmo API wrapper for NodeJS",
55
"type": "module",
6-
"main": "index.js",
6+
"main": "src/index.js",
77
"scripts": {
8-
"start": "node index.js"
8+
"start": "node src/index.js"
99
},
10-
"author": "",
10+
"author": "pineapplelol",
1111
"license": "MIT",
1212
"dependencies": {
1313
"node-fetch": "^3.2.4"

src/index.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import Venmo from "./venmo.js";
2+
3+
export default Venmo;

lib/venmo.js renamed to src/venmo.js

Lines changed: 96 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,23 @@
11
import fetch from "node-fetch";
2+
import readline from "readline";
23

34
const baseUrl = "https://api.venmo.com/v1";
45

6+
// Generates a random device ID
7+
const deviceId = "88884260-05O3-8U81-58I1-2WA76F357GR9"
8+
.replace(/[a-z]/gi, function (letter) {
9+
return String.fromCharCode(Math.floor(Math.random() * 26) + 65);
10+
})
11+
.replace(/[0-9]/gi, function (number) {
12+
return Math.floor(Math.random() * 10);
13+
});
14+
15+
// Headers that are sent with every request
16+
const persistentHeaders = {
17+
"device-id": deviceId,
18+
"Content-Type": "application/json",
19+
};
20+
521
const Venmo = {
622
/**
723
* Gets the access token for the Venmo object.
@@ -20,38 +36,97 @@ const Venmo = {
2036
},
2137

2238
/**
23-
* Logins given a username and password. Currently does not support
24-
* 2FA (coming soon). This function will automatically set the access token
25-
* for the user.
39+
* Performs a login attempt given a username, password, and optionally 2FA authentication
40+
* credentials. This function does not set the access token for the Venmo object. It can return
41+
* two kinds of values:
42+
* 1. If 2FA is required, it will return {type: '2fa', value: otp_secret}.
43+
* 2. If login is successful, it will return {type: 'accessToken', value: accessToken}.
44+
*
2645
* @param {string} username The username for the Venmo account.
2746
* @param {string} password The password for the Venmo account.
28-
* @returns {Promise} A promise that resolves to the user accessToken.
29-
* @example
30-
* await Venmo.login("pineapplelol", "pineapplesaregreat")
47+
* @returns {Promise} A promise that resolves to a dictionary containing the value type and
48+
* the value (either the 2FA secret or the access token).
3149
**/
32-
login: function (username, password) {
33-
const deviceId = "88884260-05O3-8U81-58I1-2WA76F357GR9"
34-
.replace(/[a-z]/gi, function (letter) {
35-
return String.fromCharCode(Math.floor(Math.random() * 26) + 65);
36-
})
37-
.replace(/[0-9]/gi, function (number) {
38-
return Math.floor(Math.random() * 10);
39-
});
40-
50+
initLogin: async function (username, password, twofaHeaders = {}) {
4151
const resourcePath = baseUrl + "/oauth/access_token";
4252
const header_params = {
43-
"device-id": deviceId,
44-
"Content-Type": "application/json",
4553
Host: "api.venmo.com",
54+
...persistentHeaders,
55+
...twofaHeaders,
4656
};
4757
const body = { phone_email_or_username: username, client_id: "1", password: password };
4858

59+
let type = "",
60+
value = "";
4961
return fetch(resourcePath, { method: "POST", headers: header_params, body: JSON.stringify(body) })
50-
.then((res) => res.json())
51-
.then((json) => {
52-
this.setAccessToken(json.access_token);
53-
return json.access_token;
62+
.then((res) => {
63+
if (res.status == 401) {
64+
console.log("2fa required in initLogin");
65+
type = "2fa";
66+
value = res.headers.get("venmo-otp-secret");
67+
}
68+
return res.json();
69+
})
70+
.then((data) => {
71+
if (type !== "2fa") {
72+
console.log(data);
73+
type = "accessToken";
74+
value = data.access_token;
75+
}
76+
})
77+
.then(() => {
78+
return { type, value };
79+
});
80+
},
81+
82+
/**
83+
* Will send a request to the Venmo API to complete the login process with 2FA.
84+
* @param {string} otpSecret The otp secret returned in the header of the initLogin request.
85+
*/
86+
sendTwoFactor: function (otpSecret) {
87+
const resourcePath = baseUrl + "/account/two-factor/token";
88+
const header_params = {
89+
"venmo-otp-secret": otpSecret,
90+
...persistentHeaders,
91+
};
92+
const body = { via: "sms" };
93+
return fetch(resourcePath, { method: "POST", headers: header_params, body: JSON.stringify(body) }).then(
94+
(res) => {}
95+
);
96+
},
97+
98+
/**
99+
* Will complete an end-to-end login for a user given their username and password. If 2FA is required, it will
100+
* prompt the user for the 2FA code and then complete the login. It will automatically set the access token for
101+
* the Venmo object.
102+
* @param {string} username The username for the Venmo account.
103+
* @param {string} password The password for the Venmo account.
104+
* @returns The personal access token for the user.
105+
*/
106+
login: async function (username, password) {
107+
const loginRes = await this.initLogin(username, password);
108+
let { type, value } = loginRes;
109+
110+
if (type === "2fa") {
111+
console.log("2FA required. Please enter the code sent to your phone.");
112+
this.sendTwoFactor(value);
113+
const rl = readline.createInterface({
114+
input: process.stdin,
115+
output: process.stdout,
54116
});
117+
const code = await new Promise((res) => {
118+
rl.question("Enter OTP code:", (answer) => res(answer));
119+
});
120+
const loginRes = await this.initLogin(username, password, {
121+
"venmo-otp-secret": value,
122+
"venmo-otp": code,
123+
});
124+
value = loginRes.value;
125+
}
126+
127+
this.setAccessToken(value);
128+
console.log("Logged in successfully! Your access token is: " + value);
129+
return value;
55130
},
56131

57132
/**

0 commit comments

Comments
 (0)