sekai-soft / guide-nitter-self-hosting

A guide for self-hosting a Nitter instance
MIT License
155 stars 8 forks source link

Auth token script with 2FA support #14

Closed MatthewL246 closed 4 months ago

MatthewL246 commented 6 months ago

After about a month of using Nitter with an account with 2FA disabled, my account got locked and I had to log in and complete a captcha to unlock it. The notification said that enabling 2FA would prevent this locking from happening, so I did so.

I looked for a version of the auth token generation script that supports 2FA, but it didn't seem like anyone had created one yet. This uses the TOTP secret key from Twitter's "authentication app" 2FA option. Note that saving the TOTP secret key that is shown when enabling 2FA is required, so accounts with 2FA already enabled will need to disable and re-enable it.

AI Disclosure: This script was generated using ChatGPT, prompted with the original script from https://github.com/zedeus/nitter/issues/983#issuecomment-1914616663. It worked completely fine for me, but I am by no means familiar enough with the Twitter API to review the code's accuracy. I just wanted to post this in case someone else finds it useful and see if it could potentially be integrated into the great setup guide here.

import requests
import base64
import pyotp

# Replace these with your actual credentials
username = ''
password = ''
totp_secret = ''

TW_CONSUMER_KEY = '3nVuSoBZnx6U4vzUxf5w'
TW_CONSUMER_SECRET = 'Bcs59EFbbsdF6Sl9Ng71smgStWEGwXXKSjYvPVt7qys'
TW_ANDROID_BASIC_TOKEN = 'Basic {token}'.format(token=base64.b64encode(
    (TW_CONSUMER_KEY + ":" + TW_CONSUMER_SECRET).encode()
).decode())

# Request bearer token
bearer_token_req = requests.post("https://api.twitter.com/oauth2/token",
    headers={
        'Authorization': TW_ANDROID_BASIC_TOKEN,
        "Content-Type": "application/x-www-form-urlencoded",
    },
    data='grant_type=client_credentials'
).json()
bearer_token = ' '.join(str(x) for x in bearer_token_req.values())
print(bearer_token)

# Request guest token
guest_token = requests.post("https://api.twitter.com/1.1/guest/activate.json", headers={
    'Authorization': bearer_token,
}).json()['guest_token']
print(guest_token)

twitter_header = {
    'Authorization': bearer_token,
    "Content-Type": "application/json",
    "User-Agent": "TwitterAndroid/9.95.0-release.0 (29950000-r-0) ONEPLUS+A3010/9 (OnePlus;ONEPLUS+A3010;OnePlus;OnePlus3;0;;1;2016)",
    "X-Twitter-API-Version": '5',
    "X-Twitter-Client": "TwitterAndroid",
    "X-Twitter-Client-Version": "9.95.0-release.0",
    "OS-Version": "28",
    "System-User-Agent": "Dalvik/2.1.0 (Linux; U; Android 9; ONEPLUS A3010 Build/PKQ1.181203.001)",
    "X-Twitter-Active-User": "yes",
    "X-Guest-Token": guest_token,
}

session = requests.Session()

# Start login flow
task1 = session.post('https://api.twitter.com/1.1/onboarding/task.json',
    params={
        'flow_name': 'login',
        'api_version': '1',
        'known_device_token': '',
        'sim_country_code': 'us'
    },
    json={
        "flow_token": None,
        "input_flow_data": {
            "country_code": None,
            "flow_context": {
                "referrer_context": {
                    "referral_details": "utm_source=google-play&utm_medium=organic",
                    "referrer_url": ""
                },
                "start_location": {
                    "location": "deeplink"
                }
            },
            "requested_variant": None,
            "target_user_id": 0
        }
    },
    headers=twitter_header
)

session.headers['att'] = task1.headers.get('att')

# Enter username
task2 = session.post('https://api.twitter.com/1.1/onboarding/task.json',
    json={
        "flow_token": task1.json().get('flow_token'),
        "subtask_inputs": [{
                "enter_text": {
                    "suggestion_id": None,
                    "text": username,
                    "link": "next_link"
                },
                "subtask_id": "LoginEnterUserIdentifier"
            }
        ]
    },
    headers=twitter_header
)

# Enter password
task3 = session.post('https://api.twitter.com/1.1/onboarding/task.json',
    json={
        "flow_token": task2.json().get('flow_token'),
        "subtask_inputs": [{
                "enter_password": {
                    "password": password,
                    "link": "next_link"
                },
                "subtask_id": "LoginEnterPassword"
            }
        ],
    },
    headers=twitter_header
)

# Enter TOTP code if prompted
task4 = session.post('https://api.twitter.com/1.1/onboarding/task.json',
    json={
        "flow_token": task3.json().get('flow_token'),
        "subtask_inputs": [{
                "check_logged_in_account": {
                    "link": "AccountDuplicationCheck_false"
                },
                "subtask_id": "AccountDuplicationCheck"
            }
        ]
    },
    headers=twitter_header
).json()

authentication = None

for t4_subtask in task4.get('subtasks', []):
    if 'open_account' in t4_subtask:
        authentication = t4_subtask['open_account']
        break
    elif t4_subtask.get('subtask_id') == 'LoginTwoFactorAuthChallenge':
        # Generate TOTP code
        totp = pyotp.TOTP(totp_secret)
        totp_code = totp.now()
        print("TOTP code: " + totp_code)

        # Enter TOTP code
        task5 = session.post('https://api.twitter.com/1.1/onboarding/task.json', json={
            "flow_token": task4.get('flow_token'),
            "subtask_inputs": [{
                "enter_text": {
                    "suggestion_id": None,
                    "text": totp_code,
                    "link": "next_link"
                },
                "subtask_id": "LoginTwoFactorAuthChallenge"
            }]
        }, headers=twitter_header).json()

        for t5_subtask in task5.get('subtasks', []):
            if 'open_account' in t5_subtask:
                authentication = t5_subtask['open_account']

print(authentication)
MatthewL246 commented 6 months ago

Whoops, I should have done more research first. Looks like someone already created a 2FA script: https://github.com/zedeus/nitter/issues/1155#issuecomment-1951602687

It would still be nice to add 2FA support information to the guide here though!

KTachibanaM commented 4 months ago

I added basic support for 2fa code. The caveats are that you have to deploy fast enough for the mfa code to not expire. https://github.com/sekai-soft/nitter/commit/06c95a8537825c7e43df163ffb4f71e628fa4957