diff --git a/package-lock.json b/package-lock.json index 4f4cf7951d..34718c0630 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "firebase-admin", - "version": "11.3.0", + "version": "11.4.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -5962,20 +5962,14 @@ } }, "jsonwebtoken": { - "version": "8.5.1", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", - "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz", + "integrity": "sha512-tuGfYXxkQGDPnLJ7SibiQgVgeDgfbPq2k2ICcbgqW8WxWLBAxKQM/ZCu/IT8SOSwmaYl4dpTFCW5xZv7YbbWUw==", "requires": { "jws": "^3.2.2", - "lodash.includes": "^4.3.0", - "lodash.isboolean": "^3.0.3", - "lodash.isinteger": "^4.0.4", - "lodash.isnumber": "^3.0.3", - "lodash.isplainobject": "^4.0.6", - "lodash.isstring": "^4.0.1", - "lodash.once": "^4.0.0", + "lodash": "^4.17.21", "ms": "^2.1.1", - "semver": "^5.6.0" + "semver": "^7.3.8" }, "dependencies": { "jwa": { @@ -5998,9 +5992,12 @@ } }, "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "requires": { + "lru-cache": "^6.0.0" + } } } }, @@ -6305,53 +6302,18 @@ "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", "dev": true }, - "lodash.includes": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", - "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==" - }, - "lodash.isboolean": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", - "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==" - }, "lodash.isequal": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", "dev": true }, - "lodash.isinteger": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", - "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==" - }, - "lodash.isnumber": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", - "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==" - }, - "lodash.isplainobject": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" - }, - "lodash.isstring": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", - "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==" - }, "lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, - "lodash.once": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", - "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" - }, "lodash.template": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.5.0.tgz", diff --git a/package.json b/package.json index ef6ff93026..dc8009b2b9 100644 --- a/package.json +++ b/package.json @@ -200,7 +200,7 @@ "@firebase/database-compat": "^0.2.6", "@firebase/database-types": "^0.9.13", "@types/node": ">=12.12.47", - "jsonwebtoken": "^8.5.1", + "jsonwebtoken": "^9.0.0", "jwks-rsa": "^2.1.4", "node-forge": "^1.3.1", "uuid": "^9.0.0" diff --git a/src/utils/jwt.ts b/src/utils/jwt.ts index 6e28fcb455..acd1038d2f 100644 --- a/src/utils/jwt.ts +++ b/src/utils/jwt.ts @@ -244,7 +244,7 @@ export class PublicKeySignatureVerifier implements SignatureVerifier { export class EmulatorSignatureVerifier implements SignatureVerifier { public verify(token: string): Promise { // Signature checks skipped for emulator; no need to fetch public keys. - return verifyJwtSignature(token, ''); + return verifyJwtSignature(token, undefined as any, { algorithms:['none'] }); } } diff --git a/test/integration/auth.spec.ts b/test/integration/auth.spec.ts index 506cddd9af..68e66aaf06 100644 --- a/test/integration/auth.spec.ts +++ b/test/integration/auth.spec.ts @@ -1065,12 +1065,14 @@ describe('admin.auth', () => { audience: projectId, issuer: 'https://securetoken.google.com/' + projectId, subject: uid, - }); + }, undefined, 'secret'); return getAuth().verifyIdToken(unsignedToken); }); it('verifyIdToken() fails when called with a token with wrong project', () => { - const unsignedToken = mocks.generateIdToken({ algorithm: 'none', audience: 'nosuch' }); + const unsignedToken = mocks.generateIdToken( + { algorithm: 'none', audience: 'nosuch' }, + undefined, 'secret'); return getAuth().verifyIdToken(unsignedToken) .should.eventually.be.rejected.and.have.property('code', 'auth/argument-error'); }); @@ -1081,7 +1083,7 @@ describe('admin.auth', () => { audience: projectId, issuer: 'https://securetoken.google.com/' + projectId, subject: 'nosuch', - }); + }, undefined, 'secret'); return getAuth().verifyIdToken(unsignedToken) .should.eventually.be.rejected.and.have.property('code', 'auth/user-not-found'); }); diff --git a/test/resources/mocks.ts b/test/resources/mocks.ts index f4ae055bbc..4239f6eebf 100644 --- a/test/resources/mocks.ts +++ b/test/resources/mocks.ts @@ -206,9 +206,10 @@ export const jwksKeyPair = { * * @param {object} overrides Overrides for the generated token's attributes. * @param {object} claims Extra claims to add to the token. + * @param {string} secret Custom key to sign the token with. * @return {string} A mocked Firebase ID token with any provided overrides included. */ -export function generateIdToken(overrides?: object, claims?: object): string { +export function generateIdToken(overrides?: object, claims?: object, secret?: string): string { const options = _.assign({ audience: projectId, expiresIn: ONE_HOUR_IN_SECONDS, @@ -225,7 +226,7 @@ export function generateIdToken(overrides?: object, claims?: object): string { ...claims, }; - return jwt.sign(payload, certificateObject.private_key, options); + return jwt.sign(payload, secret ?? certificateObject.private_key, options); } /** @@ -233,9 +234,10 @@ export function generateIdToken(overrides?: object, claims?: object): string { * * @param overrides Overrides for the generated token's attributes. * @param claims Extra claims to add to the token. + * @param {string} secret Custom key to sign the token with. * @return A mocked Auth Blocking token with any provided overrides included. */ -export function generateAuthBlockingToken(overrides?: object, claims?: object): string { +export function generateAuthBlockingToken(overrides?: object, claims?: object, secret?: string): string { const options = _.assign({ audience: `https://us-central1-${projectId}.cloudfunctions.net/functionName`, expiresIn: TEN_MINUTES_IN_SECONDS, @@ -252,7 +254,7 @@ export function generateAuthBlockingToken(overrides?: object, claims?: object): ...claims, }; - return jwt.sign(payload, certificateObject.private_key, options); + return jwt.sign(payload, secret ?? certificateObject.private_key, options); } /** diff --git a/test/unit/app-check/token-verifier.spec.ts b/test/unit/app-check/token-verifier.spec.ts index 462fcfc43d..c6cf897c05 100644 --- a/test/unit/app-check/token-verifier.spec.ts +++ b/test/unit/app-check/token-verifier.spec.ts @@ -102,7 +102,7 @@ describe('AppCheckTokenVerifier', () => { it('should be rejected given an App Check token with an incorrect algorithm', () => { const mockAppCheckToken = mocks.generateAppCheckToken({ - algorithm: 'HS256', + algorithm: 'PS256', }); return tokenVerifier.verifyToken(mockAppCheckToken) .should.eventually.be.rejectedWith('The provided App Check token has incorrect algorithm'); diff --git a/test/unit/auth/auth.spec.ts b/test/unit/auth/auth.spec.ts index 0995a310d4..4924cb5b12 100644 --- a/test/unit/auth/auth.spec.ts +++ b/test/unit/auth/auth.spec.ts @@ -3895,7 +3895,7 @@ AUTH_CONFIGS.forEach((testConfig) => { expect(decoded).to.have.property('payload').that.has.property('uid', 'uid1'); // Make sure this doesn't throw - jwt.verify(token, '', { algorithms: ['none'] }); + jwt.verify(token, undefined as any, { algorithms: ['none'] }); }); it('verifyIdToken() should reject revoked ID tokens', () => { @@ -3909,10 +3909,11 @@ AUTH_CONFIGS.forEach((testConfig) => { const unsignedToken = mocks.generateIdToken({ algorithm: 'none', subject: uid, + header: {}, }, { iat: oneSecBeforeValidSince, auth_time: oneSecBeforeValidSince, - }); + }, 'secret'); // verifyIdToken should force checking revocation in emulator mode, // even if checkRevoked=false. @@ -3942,7 +3943,7 @@ AUTH_CONFIGS.forEach((testConfig) => { }, { iat: oneSecBeforeValidSince, auth_time: oneSecBeforeValidSince, - }); + }, 'secret'); // verifySessionCookie should force checking revocation in emulator // mode, even if checkRevoked=false. @@ -3960,7 +3961,7 @@ AUTH_CONFIGS.forEach((testConfig) => { it('verifyIdToken() rejects an unsigned token if auth emulator is unreachable', async () => { const unsignedToken = mocks.generateIdToken({ algorithm: 'none' - }); + }, undefined, 'secret'); const errorMessage = 'Error while making request: connect ECONNREFUSED 127.0.0.1. Error code: ECONNREFUSED'; const getUserStub = sinon.stub(testConfig.Auth.prototype, 'getUser').rejects(new Error(errorMessage)); diff --git a/test/unit/auth/token-generator.spec.ts b/test/unit/auth/token-generator.spec.ts index 17f0dfd985..acb89b13e8 100644 --- a/test/unit/auth/token-generator.spec.ts +++ b/test/unit/auth/token-generator.spec.ts @@ -130,7 +130,7 @@ describe('FirebaseTokenGenerator', () => { // Check that verify doesn't throw // Note: the types for jsonwebtoken are wrong so we have to disguise the 'null' - jwt.verify(token, '', { algorithms: ['none'] }); + jwt.verify(token, undefined as any, { algorithms: ['none'] }); // Decode and check all three segments const { header, payload, signature } = jwt.decode(token, { complete: true }) as { [key: string]: any }; diff --git a/test/unit/auth/token-verifier.spec.ts b/test/unit/auth/token-verifier.spec.ts index 8496bb735c..7abda8eb08 100644 --- a/test/unit/auth/token-verifier.spec.ts +++ b/test/unit/auth/token-verifier.spec.ts @@ -281,7 +281,7 @@ describe('FirebaseTokenVerifier', () => { it('should be rejected given a Firebase JWT token with an incorrect algorithm', () => { const mockIdToken = mocks.generateIdToken({ - algorithm: 'HS256', + algorithm: 'PS256', }); return tokenVerifier.verifyJWT(mockIdToken) .should.eventually.be.rejectedWith('Firebase ID token has incorrect algorithm'); @@ -494,7 +494,7 @@ describe('FirebaseTokenVerifier', () => { const mockIdToken = mocks.generateIdToken({ algorithm: 'none', header: {} - }); + }, undefined, 'secret'); const isEmulator = true; const decoded = await emulatorVerifier.verifyJWT(mockIdToken, isEmulator); @@ -515,14 +515,14 @@ describe('FirebaseTokenVerifier', () => { const idTokenNoAlg = mocks.generateIdToken({ algorithm: 'none', - }); + }, undefined, 'secret'); await tokenVerifier.verifyJWT(idTokenNoAlg) .should.eventually.be.rejectedWith('Firebase ID token has incorrect algorithm.'); const idTokenNoHeader = mocks.generateIdToken({ algorithm: 'none', header: {} - }); + }, undefined, 'secret'); await tokenVerifier.verifyJWT(idTokenNoHeader) .should.eventually.be.rejectedWith('Firebase ID token has no "kid" claim.'); }); @@ -589,7 +589,7 @@ describe('FirebaseTokenVerifier', () => { it('should be rejected given a Auth Blocking JWT token with an incorrect algorithm', () => { const mockAuthBlockingToken = mocks.generateAuthBlockingToken({ - algorithm: 'HS256', + algorithm: 'PS256', }); return authBlockingTokenVerifier._verifyAuthBlockingToken(mockAuthBlockingToken, false, undefined) .should.eventually.be.rejectedWith('Firebase Auth Blocking token has incorrect algorithm'); @@ -748,7 +748,7 @@ describe('FirebaseTokenVerifier', () => { const mockAuthBlockingToken = mocks.generateAuthBlockingToken({ algorithm: 'none', header: {} - }); + }, undefined, 'secret'); const isEmulator = true; const decoded = await emulatorVerifier._verifyAuthBlockingToken(mockAuthBlockingToken, isEmulator, undefined); @@ -769,14 +769,14 @@ describe('FirebaseTokenVerifier', () => { const idTokenNoAlg = mocks.generateAuthBlockingToken({ algorithm: 'none', - }); + }, undefined, 'secret'); await authBlockingTokenVerifier._verifyAuthBlockingToken(idTokenNoAlg, false, undefined) .should.eventually.be.rejectedWith('Firebase Auth Blocking token has incorrect algorithm.'); const idTokenNoHeader = mocks.generateAuthBlockingToken({ algorithm: 'none', header: {} - }); + }, undefined, 'secret'); await authBlockingTokenVerifier._verifyAuthBlockingToken(idTokenNoHeader, false, undefined) .should.eventually.be.rejectedWith('Firebase Auth Blocking token has no "kid" claim.'); }); diff --git a/test/unit/utils/jwt.spec.ts b/test/unit/utils/jwt.spec.ts index 8af5fa9c01..18cff06ff7 100644 --- a/test/unit/utils/jwt.spec.ts +++ b/test/unit/utils/jwt.spec.ts @@ -199,7 +199,7 @@ describe('decodeJwt', () => { const mockIdToken = mocks.generateIdToken({ algorithm: 'none', header: {} - }); + }, undefined, 'secret'); return decodeJwt(mockIdToken) .should.eventually.be.fulfilled.and.deep.equal(DECODED_UNSIGNED_TOKEN); @@ -247,9 +247,9 @@ describe('verifyJwtSignature', () => { const mockIdToken = mocks.generateIdToken({ algorithm: 'none', header: {} - }); + }, undefined, 'secret'); - return verifyJwtSignature(mockIdToken, '') + return verifyJwtSignature(mockIdToken, undefined as any, { algorithms: ['none'] }) .should.eventually.be.fulfilled; }); @@ -448,7 +448,7 @@ describe('PublicKeySignatureVerifier', () => { .resolves(VALID_PUBLIC_KEYS_RESPONSE); stubs.push(keyFetcherStub); const mockIdToken = mocks.generateIdToken({ - algorithm: 'HS256', + algorithm: 'RS384', }); return verifier.verify(mockIdToken).should.eventually.be @@ -485,11 +485,11 @@ describe('EmulatorSignatureVerifier', () => { const emulatorVerifier = new EmulatorSignatureVerifier(); describe('verify', () => { - it('should be fullfilled given a valid unsigned (emulator) token', () => { + it('should be fulfilled given a valid unsigned (emulator) token', () => { const mockIdToken = mocks.generateIdToken({ algorithm: 'none', header: {} - }); + }, undefined, 'secret'); return emulatorVerifier.verify(mockIdToken).should.eventually.be.fulfilled; });