1+ import { TwitterApi } from './dist/cjs/index.js' ;
2+ import 'dotenv/config' ;
3+
4+ console . log ( '🔐 TWITTER OAUTH 2.0 SETUP FOR TESTING' ) ;
5+ console . log ( '======================================' ) ;
6+
7+ // Check if this is the token exchange step
8+ const command = process . argv [ 2 ] ;
9+ const authCode = process . argv [ 3 ] ;
10+ const codeVerifier = process . argv [ 4 ] ;
11+
12+ if ( command === 'exchange' && authCode && codeVerifier ) {
13+ // Step 2: Exchange authorization code for access token
14+ exchangeCodeForToken ( authCode , codeVerifier ) ;
15+ } else {
16+ // Step 1: Generate authorization URL
17+ generateAuthorizationUrl ( ) ;
18+ }
19+
20+ function generateAuthorizationUrl ( ) {
21+ console . log ( '\n📋 PREREQUISITES:' ) ;
22+ console . log ( '1. Twitter Developer Account with an app created' ) ;
23+ console . log ( '2. App configured as "Web App" (not Native App)' ) ;
24+ console . log ( '3. CLIENT_ID, CLIENT_SECRET, and CALLBACK_URL in your .env file' ) ;
25+ console . log ( '4. Same callback URL added to your Twitter app settings' ) ;
26+
27+ // Check credentials - all required
28+ if ( ! process . env . CLIENT_ID || ! process . env . CLIENT_SECRET || ! process . env . CALLBACK_URL ) {
29+ console . log ( '\n❌ ERROR: Missing required environment variables' ) ;
30+ console . log ( '\nCreate a .env file with:' ) ;
31+ console . log ( 'CLIENT_ID=your_oauth2_client_id' ) ;
32+ console . log ( 'CLIENT_SECRET=your_oauth2_client_secret' ) ;
33+ console . log ( 'CALLBACK_URL=your_callback_url' ) ;
34+ console . log ( '\nThen add the same callback URL to your Twitter app settings!' ) ;
35+ return ;
36+ }
37+
38+ const callbackUrl = process . env . CALLBACK_URL ;
39+
40+ try {
41+ const client = new TwitterApi ( {
42+ clientId : process . env . CLIENT_ID ,
43+ clientSecret : process . env . CLIENT_SECRET ,
44+ } ) ;
45+
46+ const { url, codeVerifier } = client . generateOAuth2AuthLink (
47+ callbackUrl ,
48+ { scope : [ 'tweet.read' , 'users.read' , 'users.email' , 'offline.access' ] }
49+ ) ;
50+
51+ console . log ( '\n🔗 STEP 1: Visit this authorization URL:' ) ;
52+ console . log ( '─' . repeat ( 60 ) ) ;
53+ console . log ( url ) ;
54+ console . log ( '─' . repeat ( 60 ) ) ;
55+
56+ console . log ( '\n👆 What will happen:' ) ;
57+ console . log ( '• You\'ll be redirected to Twitter to sign in' ) ;
58+ console . log ( '• Twitter will ask you to authorize the app' ) ;
59+ console . log ( `• You'll be redirected to: ${ callbackUrl } ?code=...` ) ;
60+ console . log ( '• The page will show "This site can\'t be reached" - that\'s OK!' ) ;
61+
62+ console . log ( '\n⚡ STEP 2: After authorization, IMMEDIATELY run:' ) ;
63+ console . log ( `node setup-oauth2.mjs exchange YOUR_CODE "${ codeVerifier } "` ) ;
64+
65+ console . log ( '\n⚠️ IMPORTANT:' ) ;
66+ console . log ( '• Copy the "code" parameter from the redirect URL' ) ;
67+ console . log ( '• Run the exchange command IMMEDIATELY (codes expire in ~30 seconds)' ) ;
68+ console . log ( '• Don\'t include the full URL, just the code parameter' ) ;
69+
70+ console . log ( '\n📧 SCOPES INCLUDED:' ) ;
71+ console . log ( '• tweet.read - Read tweets' ) ;
72+ console . log ( '• users.read - Read user information' ) ;
73+ console . log ( '• users.email - Access confirmed email (key feature!)' ) ;
74+ console . log ( '• offline.access - Get refresh token' ) ;
75+
76+ } catch ( error ) {
77+ console . log ( '\n❌ ERROR:' , error . message ) ;
78+ }
79+ }
80+
81+ async function exchangeCodeForToken ( authCode , codeVerifier ) {
82+ console . log ( '\n⚡ EXCHANGING CODE FOR ACCESS TOKEN...' ) ;
83+
84+ // Check that CALLBACK_URL is set
85+ if ( ! process . env . CALLBACK_URL ) {
86+ console . log ( '\n❌ ERROR: CALLBACK_URL is required in .env file' ) ;
87+ return ;
88+ }
89+
90+ const callbackUrl = process . env . CALLBACK_URL ;
91+
92+ const client = new TwitterApi ( {
93+ clientId : process . env . CLIENT_ID ,
94+ clientSecret : process . env . CLIENT_SECRET ,
95+ } ) ;
96+
97+ try {
98+ const startTime = Date . now ( ) ;
99+
100+ const { client : loggedClient , accessToken, refreshToken, expiresIn } =
101+ await client . loginWithOAuth2 ( {
102+ code : authCode ,
103+ codeVerifier,
104+ redirectUri : callbackUrl ,
105+ } ) ;
106+
107+ const duration = Date . now ( ) - startTime ;
108+
109+ console . log ( `\n🎉 SUCCESS! (${ duration } ms)` ) ;
110+ console . log ( '═' . repeat ( 50 ) ) ;
111+
112+ // Test the token immediately
113+ console . log ( '\n🧪 Testing access...' ) ;
114+ const user = await loggedClient . v2 . me ( {
115+ 'user.fields' : [ 'verified' , 'verified_type' , 'confirmed_email' ] ,
116+ } ) ;
117+
118+ console . log ( `✅ Authenticated as: @${ user . data . username } ` ) ;
119+ console . log ( `✅ Verified: ${ user . data . verified } (${ user . data . verified_type || 'none' } )` ) ;
120+
121+ if ( user . data . confirmed_email ) {
122+ console . log ( `✅ Email access: ${ user . data . confirmed_email } ` ) ;
123+ } else {
124+ console . log ( 'ℹ️ Email not returned (check app permissions)' ) ;
125+ }
126+
127+ console . log ( '\n📋 ADD TO YOUR .env FILE:' ) ;
128+ console . log ( '─' . repeat ( 40 ) ) ;
129+ console . log ( `OAUTH2_ACCESS_TOKEN=${ accessToken } ` ) ;
130+ if ( refreshToken ) {
131+ console . log ( `OAUTH2_REFRESH_TOKEN=${ refreshToken } ` ) ;
132+ }
133+ console . log ( '─' . repeat ( 40 ) ) ;
134+
135+ console . log ( `\n⏰ Token expires in: ${ Math . round ( expiresIn / 3600 ) } hours` ) ;
136+
137+ console . log ( '\n🚀 NEXT STEPS:' ) ;
138+ console . log ( '1. Add the tokens to your .env file' ) ;
139+ console . log ( '2. Run your OAuth 2.0 tests: npm run mocha \'test/oauth2.user.v2.test.ts\'' ) ;
140+ console . log ( '3. Start using OAuth 2.0 with email access in your app!' ) ;
141+
142+ } catch ( error ) {
143+ console . log ( '\n❌ TOKEN EXCHANGE FAILED:' , error . message ) ;
144+
145+ if ( error . message . includes ( 'authorization code' ) ) {
146+ console . log ( '\n💡 COMMON ISSUES:' ) ;
147+ console . log ( '• Code expired (they expire in ~30 seconds)' ) ;
148+ console . log ( '• Code already used (each code works only once)' ) ;
149+ console . log ( '• Wrong code copied' ) ;
150+ console . log ( '\n🔄 Try again: node setup-oauth2.mjs' ) ;
151+ }
152+ }
153+ }
0 commit comments