Skip to content

Commit

Permalink
feat: add support for logging in using 2FA
Browse files Browse the repository at this point in the history
  • Loading branch information
catdevnull authored and karashiiro committed Apr 9, 2024
1 parent 6fbf886 commit 6738b21
Show file tree
Hide file tree
Showing 6 changed files with 65 additions and 5 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"cross-fetch": "^4.0.0-alpha.5",
"headers-polyfill": "^3.1.2",
"json-stable-stringify": "^1.0.2",
"otpauth": "^9.2.2",
"set-cookie-parser": "^2.6.0",
"tough-cookie": "^4.1.2",
"tslib": "^2.5.2"
Expand Down
38 changes: 38 additions & 0 deletions src/auth-user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { Headers } from 'headers-polyfill';
import { TwitterApiErrorRaw } from './errors';
import { Type, type Static } from '@sinclair/typebox';
import { Check } from '@sinclair/typebox/value';
import * as OTPAuth from 'otpauth';

interface TwitterUserAuthFlowInitRequest {
flow_name: string;
Expand Down Expand Up @@ -73,6 +74,7 @@ export class TwitterUserAuth extends TwitterGuestAuth {
username: string,
password: string,
email?: string,
twoFactorSecret?: string,
): Promise<void> {
await this.updateGuestToken();

Expand All @@ -86,6 +88,14 @@ export class TwitterUserAuth extends TwitterGuestAuth {
next = await this.handleEnterPassword(next, password);
} else if (next.subtask.subtask_id === 'AccountDuplicationCheck') {
next = await this.handleAccountDuplicationCheck(next);
} else if (next.subtask.subtask_id === 'LoginTwoFactorAuthChallenge') {
if (twoFactorSecret) {
next = await this.handleTwoFactorAuthChallenge(next, twoFactorSecret);
} else {
throw new Error(
'Requested two factor authentication code but no secret provided',
);
}
} else if (next.subtask.subtask_id === 'LoginAcid') {
next = await this.handleAcid(next, email);
} else if (next.subtask.subtask_id === 'LoginSuccessSubtask') {
Expand Down Expand Up @@ -213,6 +223,34 @@ export class TwitterUserAuth extends TwitterGuestAuth {
});
}

private async handleTwoFactorAuthChallenge(
prev: FlowTokenResultSuccess,
secret: string,
) {
const totp = new OTPAuth.TOTP({ secret });
let error;
for (let attempts = 1; attempts < 4; attempts += 1) {
try {
return await this.executeFlowTask({
flow_token: prev.flowToken,
subtask_inputs: [
{
subtask_id: 'LoginTwoFactorAuthChallenge',
enter_text: {
link: 'next_link',
text: totp.generate(),
},
},
],
});
} catch (err) {
error = err;
await new Promise((resolve) => setTimeout(resolve, 2000 * attempts));
}
}
throw error;
}

private async handleAcid(
prev: FlowTokenResultSuccess,
email: string | undefined,
Expand Down
10 changes: 8 additions & 2 deletions src/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,15 @@ export interface TwitterAuth {
* Logs into a Twitter account.
* @param username The username to log in with.
* @param password The password to log in with.
* @param email The password to log in with, if you have email confirmation enabled.
* @param email The email to log in with, if you have email confirmation enabled.
* @param twoFactorSecret The secret to generate two factor authentication tokens with, if you have two factor authentication enabled.
*/
login(username: string, password: string, email?: string): Promise<void>;
login(
username: string,
password: string,
email?: string,
twoFactorSecret?: string,
): Promise<void>;

/**
* Logs out of the current session.
Expand Down
6 changes: 4 additions & 2 deletions src/scraper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -368,16 +368,18 @@ export class Scraper {
* searches.
* @param username The username of the Twitter account to login with.
* @param password The password of the Twitter account to login with.
* @param email The password to log in with, if you have email confirmation enabled.
* @param email The email to log in with, if you have email confirmation enabled.
* @param twoFactorSecret The secret to generate two factor authentication tokens with, if you have two factor authentication enabled.
*/
public async login(
username: string,
password: string,
email?: string,
twoFactorSecret?: string,
): Promise<void> {
// Swap in a real authorizer for all requests
const userAuth = new TwitterUserAuth(this.token, this.getAuthOptions());
await userAuth.login(username, password, email);
await userAuth.login(username, password, email, twoFactorSecret);
this.auth = userAuth;
this.authTrends = userAuth;
}
Expand Down
3 changes: 2 additions & 1 deletion src/test-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export async function getScraper(
const username = process.env['TWITTER_USERNAME'];
const password = process.env['TWITTER_PASSWORD'];
const email = process.env['TWITTER_EMAIL'];
const twoFactorSecret = process.env['TWITTER_2FA_SECRET'];
const cookies = process.env['TWITTER_COOKIES'];
const proxyUrl = process.env['PROXY_URL'];
let agent: any;
Expand Down Expand Up @@ -51,7 +52,7 @@ export async function getScraper(
});

if (options.authMethod === 'password') {
await scraper.login(username!, password!, email);
await scraper.login(username!, password!, email, twoFactorSecret);
} else if (options.authMethod === 'cookies') {
await scraper.setCookies(JSON.parse(cookies!));
}
Expand Down
12 changes: 12 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3102,6 +3102,11 @@ jsonparse@^1.2.0:
resolved "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz"
integrity sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==

jssha@~3.3.1:
version "3.3.1"
resolved "https://registry.yarnpkg.com/jssha/-/jssha-3.3.1.tgz#c5b7fc7fb9aa745461923b87df0e247dd59c7ea8"
integrity sha512-VCMZj12FCFMQYcFLPRm/0lOBbLi8uM2BhXPTqw3U4YAfs4AZfiApOoBLoN8cQE60Z50m1MYMTQVCfgF/KaCVhQ==

keyv@^4.5.3:
version "4.5.3"
resolved "https://registry.npmjs.org/keyv/-/keyv-4.5.3.tgz"
Expand Down Expand Up @@ -3555,6 +3560,13 @@ os-tmpdir@~1.0.2:
resolved "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz"
integrity sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==

otpauth@^9.2.2:
version "9.2.2"
resolved "https://registry.yarnpkg.com/otpauth/-/otpauth-9.2.2.tgz#64bda9ea501a5d86e69a964a45062f1f17f740f4"
integrity sha512-2VcnYRUmq1dNckIfySNYP32ITWp1bvTeAEW0BSCR6G3GBf3a5zb9E+ubY62t3Dma9RjoHlvd7QpmzHfJZRkiNg==
dependencies:
jssha "~3.3.1"

p-limit@^2.2.0:
version "2.3.0"
resolved "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz"
Expand Down

0 comments on commit 6738b21

Please sign in to comment.