@@ -26,13 +26,30 @@ export type OAuth2Scopes =
26
26
| "bookmark.read"
27
27
| "bookmark.write" ;
28
28
29
- export type OAuth2UserOptions = {
29
+ export interface OAuth2UserOptions {
30
30
client_id : string ;
31
- client_secret : string ;
31
+ client_secret ? : string ;
32
32
callback : string ;
33
33
scopes : OAuth2Scopes [ ] ;
34
34
request_options ?: Partial < RequestOptions > ;
35
- } ;
35
+ }
36
+
37
+ export type GenerateAuthUrlOptions =
38
+ | {
39
+ state : string ;
40
+ code_challenge_method : "s256" ;
41
+ }
42
+ | {
43
+ state : string ;
44
+ code_challenge : string ;
45
+ code_challenge_method ?: "plain" ;
46
+ } ;
47
+
48
+ export interface RevokeAccessTokenParams {
49
+ token_type_hint : string ;
50
+ token : string ;
51
+ client_id : string ;
52
+ }
36
53
37
54
function sha256 ( buffer : string ) {
38
55
return crypto . createHash ( "sha256" ) . update ( buffer ) . digest ( ) ;
@@ -46,42 +63,64 @@ function base64URLEncode(str: Buffer) {
46
63
. replace ( / = / g, "" ) ;
47
64
}
48
65
66
+ interface RevokeAccessTokenResponse {
67
+ revoked : boolean ;
68
+ }
69
+
70
+ /**
71
+ * Twitter OAuth2 Authentication Client
72
+ *
73
+ * TypeScript Authentication Client for use with the Twitter API OAuth2
74
+ *
75
+ */
49
76
export class OAuth2User implements AuthClient {
50
77
#access_token?: string ;
51
78
token_type ?: string ;
52
79
expires_at ?: Date ;
53
80
scope ?: string ;
54
81
refresh_token ?: string ;
55
- #configuration : OAuth2UserOptions ;
82
+ #options : OAuth2UserOptions ;
56
83
#code_verifier?: string ;
57
84
#code_challenge?: string ;
58
- constructor ( configuration : OAuth2UserOptions ) {
59
- this . #configuration = configuration ;
85
+ constructor ( options : OAuth2UserOptions ) {
86
+ this . #options = options ;
60
87
}
61
88
62
- async refreshAccessToken ( ) {
89
+ /**
90
+ * Refresh the access token
91
+ */
92
+ async refreshAccessToken ( ) : Promise < void > {
63
93
const refresh_token = this . refresh_token ;
64
- const credentials = this . #configuration;
94
+ const { client_id, client_secret, request_options } = this . #options;
95
+ if ( ! client_id ) {
96
+ throw new Error ( "client_id is required" ) ;
97
+ }
98
+ if ( ! refresh_token ) {
99
+ throw new Error ( "refresh_token is required" ) ;
100
+ }
65
101
const data = await rest ( {
66
- ...this . #configuration . request_options ,
102
+ ...request_options ,
67
103
endpoint : `/2/oauth2/token` ,
68
104
params : {
105
+ client_id,
69
106
grant_type : "refresh_token" ,
70
107
refresh_token,
71
108
} ,
72
109
method : "POST" ,
73
110
headers : {
74
- ...this . #configuration . request_options ?. headers ,
111
+ ...request_options ?. headers ,
75
112
"Content-type" : "application/x-www-form-urlencoded" ,
76
- Authorization : basicAuthHeader (
77
- credentials . client_id ,
78
- credentials . client_secret
79
- ) ,
113
+ ...( ! ! client_secret && {
114
+ Authorization : basicAuthHeader ( client_id , client_secret ) ,
115
+ } ) ,
80
116
} ,
81
117
} ) ;
82
118
this . updateToken ( data ) ;
83
119
}
84
120
121
+ /**
122
+ * Update token information
123
+ */
85
124
updateToken ( data : Record < string , any > ) {
86
125
this . refresh_token = data . refresh_token ;
87
126
this . #access_token = data . access_token ;
@@ -90,7 +129,10 @@ export class OAuth2User implements AuthClient {
90
129
this . scope = data . scope ;
91
130
}
92
131
93
- isAccessTokenExpired ( ) {
132
+ /**
133
+ * Check if an access token is expired
134
+ */
135
+ isAccessTokenExpired ( ) : boolean {
94
136
const refresh_token = this . refresh_token ;
95
137
const expires_at = this . expires_at ;
96
138
return (
@@ -99,92 +141,103 @@ export class OAuth2User implements AuthClient {
99
141
) ;
100
142
}
101
143
144
+ /**
145
+ * Request an access token
146
+ */
102
147
async requestAccessToken ( code ?: string ) : Promise < void > {
103
- const credentials = this . #configuration;
104
- const code_verifier = this . #code_verifier || this . #code_challenge;
148
+ const { client_id, client_secret, callback, request_options } =
149
+ this . #options;
150
+ const code_verifier = this . #code_verifier;
151
+ if ( ! client_id ) {
152
+ throw new Error ( "client_id is required" ) ;
153
+ }
154
+ if ( ! callback ) {
155
+ throw new Error ( "callback is required" ) ;
156
+ }
105
157
const params = {
106
158
code,
107
159
grant_type : "authorization_code" ,
108
- code_verifier : code_verifier ,
109
- client_id : credentials . client_id ,
110
- redirect_uri : credentials . callback ,
160
+ code_verifier,
161
+ client_id,
162
+ redirect_uri : callback ,
111
163
} ;
112
164
const data = await rest ( {
113
- ...this . #configuration . request_options ,
165
+ ...request_options ,
114
166
endpoint : `/2/oauth2/token` ,
115
- params : params ,
167
+ params,
116
168
method : "POST" ,
117
169
headers : {
118
- ...this . #configuration . request_options ?. headers ,
170
+ ...request_options ?. headers ,
119
171
"Content-type" : "application/x-www-form-urlencoded" ,
120
- Authorization : basicAuthHeader (
121
- credentials . client_id ,
122
- credentials . client_secret
123
- ) ,
172
+ ...( ! ! client_secret && {
173
+ Authorization : basicAuthHeader ( client_id , client_secret ) ,
174
+ } ) ,
124
175
} ,
125
176
} ) ;
126
177
this . updateToken ( data ) ;
127
178
}
128
179
129
- async revokeAccessToken ( ) : Promise < any > {
130
- const credentials = this . #configuration;
180
+ /**
181
+ * Revoke an access token
182
+ */
183
+ async revokeAccessToken ( ) : Promise < RevokeAccessTokenResponse > {
184
+ const { client_id, client_secret, request_options } = this . #options;
131
185
const access_token = this . #access_token;
132
186
const refresh_token = this . refresh_token ;
133
- const configuration = this . #configuration;
134
- if ( ! access_token || ! refresh_token )
135
- throw new Error ( "No access_token or refresh_token found" ) ;
136
- const useAccessToken = ! ! this . #access_token;
137
- const params = {
138
- token_type_hint : useAccessToken ? "access_token" : "refresh_token" ,
139
- token : useAccessToken ? access_token : refresh_token ,
140
- client_id : configuration . client_id ,
141
- } ;
187
+ if ( ! client_id ) {
188
+ throw new Error ( "client_id is required" ) ;
189
+ }
190
+ let params : RevokeAccessTokenParams ;
191
+ if ( ! ! access_token ) {
192
+ params = {
193
+ token_type_hint : "access_token" ,
194
+ token : access_token ,
195
+ client_id,
196
+ } ;
197
+ } else if ( ! ! refresh_token ) {
198
+ params = {
199
+ token_type_hint : "refresh_token" ,
200
+ token : refresh_token ,
201
+ client_id,
202
+ } ;
203
+ } else {
204
+ throw new Error ( "access_token or refresh_token required" ) ;
205
+ }
142
206
return rest ( {
143
- ...this . #configuration . request_options ,
207
+ ...request_options ,
144
208
endpoint : `/2/oauth2/revoke` ,
145
- params : params ,
209
+ params,
146
210
method : "POST" ,
147
211
headers : {
148
- ...this . #configuration . request_options ?. headers ,
212
+ ...request_options ?. headers ,
149
213
"Content-Type" : "application/x-www-form-urlencoded" ,
150
- Authorization : basicAuthHeader (
151
- credentials . client_id ,
152
- credentials . client_secret
153
- ) ,
214
+ ...( ! ! client_secret && {
215
+ Authorization : basicAuthHeader ( client_id , client_secret ) ,
216
+ } ) ,
154
217
} ,
155
218
} ) ;
156
219
}
157
220
158
- generateAuthURL (
159
- options :
160
- | {
161
- state : string ;
162
- code_challenge : string ;
163
- code_challenge_method ?: "plain" ;
164
- }
165
- | {
166
- state : string ;
167
- code_challenge_method : "s256" ;
168
- }
169
- ) : string {
170
- const credentials = this . #configuration;
171
- if ( ! ( "callback" in credentials ) )
172
- throw new Error ( "You need to provide a callback and scopes" ) ;
221
+ generateAuthURL ( options : GenerateAuthUrlOptions ) : string {
222
+ const { client_id, callback, scopes } = this . #options;
223
+ if ( ! callback ) throw new Error ( "callback required" ) ;
224
+ if ( ! scopes ) throw new Error ( "scopes required" ) ;
173
225
if ( options . code_challenge_method === "s256" ) {
174
226
const code_verifier = base64URLEncode ( crypto . randomBytes ( 32 ) ) ;
175
227
this . #code_verifier = code_verifier ;
176
228
this . #code_challenge = base64URLEncode ( sha256 ( code_verifier ) ) ;
177
229
} else {
178
230
this . #code_challenge = options . code_challenge ;
231
+ this . #code_verifier = options . code_challenge ;
179
232
}
180
233
const code_challenge = this . #code_challenge;
181
234
const url = new URL ( "https://twitter.com/i/oauth2/authorize" ) ;
182
235
url . search = buildQueryString ( {
183
236
...options ,
184
- client_id : credentials . client_id ,
185
- scope : credentials . scopes . join ( " " ) ,
237
+ client_id,
238
+ scope : scopes . join ( " " ) ,
186
239
response_type : "code" ,
187
- redirect_uri : credentials . callback ,
240
+ redirect_uri : callback ,
188
241
code_challenge_method : options . code_challenge_method || "plain" ,
189
242
code_challenge,
190
243
} ) ;
0 commit comments