@@ -20,12 +20,12 @@ import os = require('os');
20
20
import path = require( 'path' ) ;
21
21
22
22
import { AppErrorCodes , FirebaseAppError } from '../utils/error' ;
23
+ import { HttpClient , HttpRequestConfig } from '../utils/api-request' ;
23
24
24
25
25
26
const GOOGLE_TOKEN_AUDIENCE = 'https://accounts.google.com/o/oauth2/token' ;
26
27
const GOOGLE_AUTH_TOKEN_HOST = 'accounts.google.com' ;
27
28
const GOOGLE_AUTH_TOKEN_PATH = '/o/oauth2/token' ;
28
- const GOOGLE_AUTH_TOKEN_PORT = 443 ;
29
29
30
30
// NOTE: the Google Metadata Service uses HTTP over a vlan
31
31
const GOOGLE_METADATA_SERVICE_HOST = 'metadata.google.internal' ;
@@ -46,7 +46,6 @@ const GCLOUD_CREDENTIAL_SUFFIX = 'gcloud/application_default_credentials.json';
46
46
const GCLOUD_CREDENTIAL_PATH = configDir && path . resolve ( configDir , GCLOUD_CREDENTIAL_SUFFIX ) ;
47
47
48
48
const REFRESH_TOKEN_HOST = 'www.googleapis.com' ;
49
- const REFRESH_TOKEN_PORT = 443 ;
50
49
const REFRESH_TOKEN_PATH = '/oauth2/v4/token' ;
51
50
52
51
const ONE_HOUR_IN_SECONDS = 60 * 60 ;
@@ -186,79 +185,63 @@ export interface GoogleOAuthAccessToken {
186
185
}
187
186
188
187
/**
189
- * A wrapper around the http and https request libraries to simplify & promisify JSON requests.
190
- * TODO(inlined): Create a type for "transit".
188
+ * Obtain a new OAuth2 token by making a remote service call.
191
189
*/
192
- function requestAccessToken ( transit , options : object , data ?: any ) : Promise < GoogleOAuthAccessToken > {
193
- return new Promise ( ( resolve , reject ) => {
194
- const req = transit . request ( options , ( res ) => {
195
- const buffers : Buffer [ ] = [ ] ;
196
- res . on ( 'data' , ( buffer ) => buffers . push ( buffer ) ) ;
197
- res . on ( 'end' , ( ) => {
198
- try {
199
- const json = JSON . parse ( Buffer . concat ( buffers ) . toString ( ) ) ;
200
- if ( json . error ) {
201
- let errorMessage = 'Error fetching access token: ' + json . error ;
202
- if ( json . error_description ) {
203
- errorMessage += ' (' + json . error_description + ')' ;
204
- }
205
- reject ( new FirebaseAppError ( AppErrorCodes . INVALID_CREDENTIAL , errorMessage ) ) ;
206
- } else if ( ! json . access_token || ! json . expires_in ) {
207
- reject ( new FirebaseAppError (
208
- AppErrorCodes . INVALID_CREDENTIAL ,
209
- `Unexpected response while fetching access token: ${ JSON . stringify ( json ) } ` ,
210
- ) ) ;
211
- } else {
212
- resolve ( json ) ;
213
- }
214
- } catch ( err ) {
215
- reject ( new FirebaseAppError (
216
- AppErrorCodes . INVALID_CREDENTIAL ,
217
- `Failed to parse access token response: ${ err . toString ( ) } ` ,
218
- ) ) ;
219
- }
220
- } ) ;
221
- } ) ;
222
- req . on ( 'error' , reject ) ;
223
- if ( data ) {
224
- req . write ( data ) ;
190
+ function requestAccessToken ( client : HttpClient , request : HttpRequestConfig ) : Promise < GoogleOAuthAccessToken > {
191
+ return client . send ( request ) . then ( ( resp ) => {
192
+ const json = resp . data ;
193
+ if ( json . error ) {
194
+ let errorMessage = 'Error fetching access token: ' + json . error ;
195
+ if ( json . error_description ) {
196
+ errorMessage += ' (' + json . error_description + ')' ;
197
+ }
198
+ throw new FirebaseAppError ( AppErrorCodes . INVALID_CREDENTIAL , errorMessage ) ;
199
+ } else if ( ! json . access_token || ! json . expires_in ) {
200
+ throw new FirebaseAppError (
201
+ AppErrorCodes . INVALID_CREDENTIAL ,
202
+ `Unexpected response while fetching access token: ${ JSON . stringify ( json ) } ` ,
203
+ ) ;
204
+ } else {
205
+ return json ;
225
206
}
226
- req . end ( ) ;
207
+ } ) . catch ( ( err ) => {
208
+ throw new FirebaseAppError (
209
+ AppErrorCodes . INVALID_CREDENTIAL ,
210
+ `Failed to parse access token response: ${ err . toString ( ) } ` ,
211
+ ) ;
227
212
} ) ;
228
213
}
229
214
230
215
/**
231
216
* Implementation of Credential that uses a service account certificate.
232
217
*/
233
218
export class CertCredential implements Credential {
234
- private certificate_ : Certificate ;
219
+ private readonly certificate : Certificate ;
220
+ private readonly httpClient : HttpClient ;
235
221
236
222
constructor ( serviceAccountPathOrObject : string | object ) {
237
- this . certificate_ = ( typeof serviceAccountPathOrObject === 'string' ) ?
223
+ this . certificate = ( typeof serviceAccountPathOrObject === 'string' ) ?
238
224
Certificate . fromPath ( serviceAccountPathOrObject ) : new Certificate ( serviceAccountPathOrObject ) ;
225
+ this . httpClient = new HttpClient ( ) ;
239
226
}
240
227
241
228
public getAccessToken ( ) : Promise < GoogleOAuthAccessToken > {
242
229
const token = this . createAuthJwt_ ( ) ;
243
230
const postData = 'grant_type=urn%3Aietf%3Aparams%3Aoauth%3A' +
244
- 'grant-type%3Ajwt-bearer&assertion=' +
245
- token ;
246
- const options = {
231
+ 'grant-type%3Ajwt-bearer&assertion=' + token ;
232
+ const request : HttpRequestConfig = {
247
233
method : 'POST' ,
248
- host : GOOGLE_AUTH_TOKEN_HOST ,
249
- port : GOOGLE_AUTH_TOKEN_PORT ,
250
- path : GOOGLE_AUTH_TOKEN_PATH ,
234
+ url : `https://${ GOOGLE_AUTH_TOKEN_HOST } ${ GOOGLE_AUTH_TOKEN_PATH } ` ,
251
235
headers : {
252
236
'Content-Type' : 'application/x-www-form-urlencoded' ,
253
- 'Content-Length' : postData . length ,
254
237
} ,
238
+ data : postData ,
255
239
} ;
256
- const https = require ( 'https' ) ;
257
- return requestAccessToken ( https , options , postData ) ;
240
+ return requestAccessToken ( this . httpClient , request ) ;
258
241
}
259
242
260
243
public getCertificate ( ) : Certificate {
261
- return this . certificate_ ;
244
+ return this . certificate ;
262
245
}
263
246
264
247
private createAuthJwt_ ( ) : string {
@@ -274,10 +257,10 @@ export class CertCredential implements Credential {
274
257
275
258
const jwt = require ( 'jsonwebtoken' ) ;
276
259
// This method is actually synchronous so we can capture and return the buffer.
277
- return jwt . sign ( claims , this . certificate_ . privateKey , {
260
+ return jwt . sign ( claims , this . certificate . privateKey , {
278
261
audience : GOOGLE_TOKEN_AUDIENCE ,
279
262
expiresIn : ONE_HOUR_IN_SECONDS ,
280
- issuer : this . certificate_ . clientEmail ,
263
+ issuer : this . certificate . clientEmail ,
281
264
algorithm : JWT_ALGORITHM ,
282
265
} ) ;
283
266
}
@@ -295,32 +278,30 @@ export interface Credential {
295
278
* Implementation of Credential that gets access tokens from refresh tokens.
296
279
*/
297
280
export class RefreshTokenCredential implements Credential {
298
- private refreshToken_ : RefreshToken ;
281
+ private readonly refreshToken : RefreshToken ;
282
+ private readonly httpClient : HttpClient ;
299
283
300
284
constructor ( refreshTokenPathOrObject : string | object ) {
301
- this . refreshToken_ = ( typeof refreshTokenPathOrObject === 'string' ) ?
285
+ this . refreshToken = ( typeof refreshTokenPathOrObject === 'string' ) ?
302
286
RefreshToken . fromPath ( refreshTokenPathOrObject ) : new RefreshToken ( refreshTokenPathOrObject ) ;
287
+ this . httpClient = new HttpClient ( ) ;
303
288
}
304
289
305
290
public getAccessToken ( ) : Promise < GoogleOAuthAccessToken > {
306
291
const postData =
307
- 'client_id=' + this . refreshToken_ . clientId + '&' +
308
- 'client_secret=' + this . refreshToken_ . clientSecret + '&' +
309
- 'refresh_token=' + this . refreshToken_ . refreshToken + '&' +
292
+ 'client_id=' + this . refreshToken . clientId + '&' +
293
+ 'client_secret=' + this . refreshToken . clientSecret + '&' +
294
+ 'refresh_token=' + this . refreshToken . refreshToken + '&' +
310
295
'grant_type=refresh_token' ;
311
-
312
- const options = {
296
+ const request : HttpRequestConfig = {
313
297
method : 'POST' ,
314
- host : REFRESH_TOKEN_HOST ,
315
- port : REFRESH_TOKEN_PORT ,
316
- path : REFRESH_TOKEN_PATH ,
298
+ url : `https://${ REFRESH_TOKEN_HOST } ${ REFRESH_TOKEN_PATH } ` ,
317
299
headers : {
318
300
'Content-Type' : 'application/x-www-form-urlencoded' ,
319
- 'Content-Length' : postData . length ,
320
301
} ,
302
+ data : postData ,
321
303
} ;
322
- const https = require ( 'https' ) ;
323
- return requestAccessToken ( https , options , postData ) ;
304
+ return requestAccessToken ( this . httpClient , request ) ;
324
305
}
325
306
326
307
public getCertificate ( ) : Certificate {
@@ -335,17 +316,15 @@ export class RefreshTokenCredential implements Credential {
335
316
* of an App Engine instance or Google Compute Engine machine.
336
317
*/
337
318
export class MetadataServiceCredential implements Credential {
319
+
320
+ private readonly httpClient = new HttpClient ( ) ;
321
+
338
322
public getAccessToken ( ) : Promise < GoogleOAuthAccessToken > {
339
- const options = {
323
+ const request : HttpRequestConfig = {
340
324
method : 'GET' ,
341
- host : GOOGLE_METADATA_SERVICE_HOST ,
342
- path : GOOGLE_METADATA_SERVICE_PATH ,
343
- headers : {
344
- 'Content-Length' : 0 ,
345
- } ,
325
+ url : `http://${ GOOGLE_METADATA_SERVICE_HOST } ${ GOOGLE_METADATA_SERVICE_PATH } ` ,
346
326
} ;
347
- const http = require ( 'http' ) ;
348
- return requestAccessToken ( http , options ) ;
327
+ return requestAccessToken ( this . httpClient , request ) ;
349
328
}
350
329
351
330
public getCertificate ( ) : Certificate {
0 commit comments