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