Skip to content

Commit 9c25d17

Browse files
authored
Using HttpClient to fetch public keys (#352)
1 parent 8ebfd25 commit 9c25d17

File tree

2 files changed

+48
-51
lines changed

2 files changed

+48
-51
lines changed

src/auth/token-verifier.ts

Lines changed: 38 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {AuthClientErrorCode, FirebaseAuthError} from '../utils/error';
1818

1919
import * as validator from '../utils/validator';
2020
import * as jwt from 'jsonwebtoken';
21+
import { HttpClient, HttpRequestConfig, HttpError } from '../utils/api-request';
2122

2223
// Audience to use for Firebase Auth Custom tokens
2324
const FIREBASE_AUDIENCE = 'https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit';
@@ -264,46 +265,45 @@ export class FirebaseTokenVerifier {
264265
return Promise.resolve(this.publicKeys);
265266
}
266267

267-
return new Promise((resolve, reject) => {
268-
const https = require('https');
269-
https.get(this.clientCertUrl, (res) => {
270-
const buffers: Buffer[] = [];
271-
272-
res.on('data', (buffer) => buffers.push(buffer as Buffer));
273-
274-
res.on('end', () => {
275-
try {
276-
const response = JSON.parse(Buffer.concat(buffers).toString());
277-
if (response.error) {
278-
let errorMessage = 'Error fetching public keys for Google certs: ' + response.error;
279-
/* istanbul ignore else */
280-
if (response.error_description) {
281-
errorMessage += ' (' + response.error_description + ')';
282-
}
283-
reject(new FirebaseAuthError(AuthClientErrorCode.INTERNAL_ERROR, errorMessage));
284-
} else {
285-
/* istanbul ignore else */
286-
if (res.headers.hasOwnProperty('cache-control')) {
287-
const cacheControlHeader: string = res.headers['cache-control'] as string;
288-
const parts = cacheControlHeader.split(',');
289-
parts.forEach((part) => {
290-
const subParts = part.trim().split('=');
291-
if (subParts[0] === 'max-age') {
292-
const maxAge: number = +subParts[1];
293-
this.publicKeysExpireAt = Date.now() + (maxAge * 1000);
294-
}
295-
});
296-
}
297-
298-
this.publicKeys = response;
299-
resolve(response);
300-
}
301-
} catch (e) {
302-
/* istanbul ignore next */
303-
reject(e);
268+
const client = new HttpClient();
269+
const request: HttpRequestConfig = {
270+
method: 'GET',
271+
url: this.clientCertUrl,
272+
};
273+
return client.send(request).then((resp) => {
274+
if (!resp.isJson() || resp.data.error) {
275+
// Treat all non-json messages and messages with an 'error' field as
276+
// error responses.
277+
throw new HttpError(resp);
278+
}
279+
if (resp.headers.hasOwnProperty('cache-control')) {
280+
const cacheControlHeader: string = resp.headers['cache-control'];
281+
const parts = cacheControlHeader.split(',');
282+
parts.forEach((part) => {
283+
const subParts = part.trim().split('=');
284+
if (subParts[0] === 'max-age') {
285+
const maxAge: number = +subParts[1];
286+
this.publicKeysExpireAt = Date.now() + (maxAge * 1000);
304287
}
305288
});
306-
}).on('error', reject);
289+
}
290+
this.publicKeys = resp.data;
291+
return resp.data;
292+
}).catch((err) => {
293+
if (err instanceof HttpError) {
294+
let errorMessage = 'Error fetching public keys for Google certs: ';
295+
const resp = err.response;
296+
if (resp.isJson() && resp.data.error) {
297+
errorMessage += `${resp.data.error}`;
298+
if (resp.data.error_description) {
299+
errorMessage += ' (' + resp.data.error_description + ')';
300+
}
301+
} else {
302+
errorMessage += `${resp.text}`;
303+
}
304+
throw new FirebaseAuthError(AuthClientErrorCode.INTERNAL_ERROR, errorMessage);
305+
}
306+
throw err;
307307
});
308308
}
309309
}

test/unit/auth/token-verifier.spec.ts

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
import https = require('https');
2121

2222
import * as _ from 'lodash';
23-
import * as jwt from 'jsonwebtoken';
2423
import * as chai from 'chai';
2524
import * as nock from 'nock';
2625
import * as sinon from 'sinon';
@@ -33,8 +32,6 @@ import {FirebaseTokenGenerator, ServiceAccountSigner} from '../../../src/auth/to
3332
import * as verifier from '../../../src/auth/token-verifier';
3433

3534
import {Certificate} from '../../../src/auth/credential';
36-
import { FirebaseAuthError, AuthClientErrorCode } from '../../../src/utils/error';
37-
import { Auth } from '../../../src/auth/auth';
3835

3936
chai.should();
4037
chai.use(sinonChai);
@@ -121,7 +118,7 @@ describe('FirebaseTokenVerifier', () => {
121118
'project_id',
122119
verifier.ID_TOKEN_INFO,
123120
);
124-
httpsSpy = sinon.spy(https, 'get');
121+
httpsSpy = sinon.spy(https, 'request');
125122
});
126123

127124
afterEach(() => {
@@ -509,12 +506,12 @@ describe('FirebaseTokenVerifier', () => {
509506
'project_id',
510507
verifier.ID_TOKEN_INFO,
511508
);
512-
expect(https.get).not.to.have.been.called;
509+
expect(https.request).not.to.have.been.called;
513510

514511
const mockIdToken = mocks.generateIdToken();
515512

516513
return testTokenVerifier.verifyJWT(mockIdToken)
517-
.then(() => expect(https.get).to.have.been.calledOnce);
514+
.then(() => expect(https.request).to.have.been.calledOnce);
518515
});
519516

520517
it('should not re-fetch the Google cert public keys every time verifyJWT() is called', () => {
@@ -523,9 +520,9 @@ describe('FirebaseTokenVerifier', () => {
523520
const mockIdToken = mocks.generateIdToken();
524521

525522
return tokenVerifier.verifyJWT(mockIdToken).then(() => {
526-
expect(https.get).to.have.been.calledOnce;
523+
expect(https.request).to.have.been.calledOnce;
527524
return tokenVerifier.verifyJWT(mockIdToken);
528-
}).then(() => expect(https.get).to.have.been.calledOnce);
525+
}).then(() => expect(https.request).to.have.been.calledOnce);
529526
});
530527

531528
it('should refresh the Google cert public keys after the "max-age" on the request expires', () => {
@@ -538,25 +535,25 @@ describe('FirebaseTokenVerifier', () => {
538535
const mockIdToken = mocks.generateIdToken();
539536

540537
return tokenVerifier.verifyJWT(mockIdToken).then(() => {
541-
expect(https.get).to.have.been.calledOnce;
538+
expect(https.request).to.have.been.calledOnce;
542539
clock.tick(999);
543540
return tokenVerifier.verifyJWT(mockIdToken);
544541
}).then(() => {
545-
expect(https.get).to.have.been.calledOnce;
542+
expect(https.request).to.have.been.calledOnce;
546543
clock.tick(1);
547544
return tokenVerifier.verifyJWT(mockIdToken);
548545
}).then(() => {
549546
// One second has passed
550-
expect(https.get).to.have.been.calledTwice;
547+
expect(https.request).to.have.been.calledTwice;
551548
clock.tick(999);
552549
return tokenVerifier.verifyJWT(mockIdToken);
553550
}).then(() => {
554-
expect(https.get).to.have.been.calledTwice;
551+
expect(https.request).to.have.been.calledTwice;
555552
clock.tick(1);
556553
return tokenVerifier.verifyJWT(mockIdToken);
557554
}).then(() => {
558555
// Two seconds have passed
559-
expect(https.get).to.have.been.calledThrice;
556+
expect(https.request).to.have.been.calledThrice;
560557
});
561558
});
562559

0 commit comments

Comments
 (0)