From accb75e414e43ac8a9ed03d445343960907be171 Mon Sep 17 00:00:00 2001 From: Andy King Date: Mon, 17 Feb 2020 16:08:16 -0600 Subject: [PATCH 1/3] use token and algo from jwt header --- spec/AuthenticationAdapters.spec.js | 119 ++++++++++++++++++++++++++++ src/Adapters/Auth/apple.js | 27 ++++++- 2 files changed, 142 insertions(+), 4 deletions(-) diff --git a/spec/AuthenticationAdapters.spec.js b/spec/AuthenticationAdapters.spec.js index 2636405a07..fcabd6dc65 100644 --- a/spec/AuthenticationAdapters.spec.js +++ b/spec/AuthenticationAdapters.spec.js @@ -1136,6 +1136,7 @@ describe('oauth2 auth adapter', () => { describe('apple signin auth adapter', () => { const apple = require('../lib/Adapters/Auth/apple'); const jwt = require('jsonwebtoken'); + const httpsRequest = require('../lib/Adapters/Auth/httpsRequest'); it('should throw error with missing id_token', async () => { try { @@ -1146,7 +1147,89 @@ describe('apple signin auth adapter', () => { } }); + it('should not decode invalid id_token', async () => { + try { + await apple.validateAuthData( + { id: 'the_user_id', token: 'the_token' }, + { client_id: 'secret' } + ); + fail(); + } catch (e) { + expect(e.message).toBe('provided token does not decode as JWT'); + } + }); + + it('should throw error if public key used to encode token is not available', async () => { + try { + const fakeDecodedToken = { header: { kid: '789', alg: 'RS256' } }; + const fakeKeys = { + keys: [ + { + e: 'AQAB', + kid: '123', + n: 'ABCDEFGH', + }, + ], + }; + spyOn(jwt, 'decode').and.callFake(() => fakeDecodedToken); + spyOn(httpsRequest, 'get').and.callFake(() => fakeKeys); + + await apple.validateAuthData( + { id: 'the_user_id', token: 'the_token' }, + { client_id: 'secret' } + ); + fail(); + } catch (e) { + expect(e.message).toBe( + 'Public key with matching key ID to token not found' + ); + } + }); + + it('should use algorithm from key header to verify id_token', async () => { + const fakeClaim = { + iss: 'https://appleid.apple.com', + aud: 'secret', + exp: Date.now(), + sub: 'the_user_id', + }; + const fakeDecodedToken = { header: { kid: '123', alg: 'RS256' } }; + const fakeKeys = { + keys: [ + { + e: 'AQAB', + kid: '123', + n: 'ABCDEFGH', + }, + ], + }; + spyOn(jwt, 'decode').and.callFake(() => fakeDecodedToken); + spyOn(httpsRequest, 'get').and.callFake(() => fakeKeys); + spyOn(jwt, 'verify').and.callFake(() => fakeClaim); + + const result = await apple.validateAuthData( + { id: 'the_user_id', token: 'the_token' }, + { client_id: 'secret' } + ); + expect(result).toEqual(fakeClaim); + expect(jwt.verify.calls.first().args[2].algorithms).toEqual( + fakeDecodedToken.header.alg + ); + }); + it('should not verify invalid id_token', async () => { + const fakeDecodedToken = { header: { kid: '123', alg: 'RS256' } }; + const fakeKeys = { + keys: [ + { + e: 'AQAB', + kid: '123', + n: 'ABCDEFGH', + }, + ], + }; + spyOn(jwt, 'decode').and.callFake(() => fakeDecodedToken); + spyOn(httpsRequest, 'get').and.callFake(() => fakeKeys); try { await apple.validateAuthData( { id: 'the_user_id', token: 'the_token' }, @@ -1165,6 +1248,18 @@ describe('apple signin auth adapter', () => { exp: Date.now(), sub: 'the_user_id', }; + const fakeDecodedToken = { header: { kid: '123', alg: 'RS256' } }; + const fakeKeys = { + keys: [ + { + e: 'AQAB', + kid: '123', + n: 'ABCDEFGH', + }, + ], + }; + spyOn(jwt, 'decode').and.callFake(() => fakeDecodedToken); + spyOn(httpsRequest, 'get').and.callFake(() => fakeKeys); spyOn(jwt, 'verify').and.callFake(() => fakeClaim); const result = await apple.validateAuthData( @@ -1179,6 +1274,18 @@ describe('apple signin auth adapter', () => { iss: 'https://not.apple.com', sub: 'the_user_id', }; + const fakeDecodedToken = { header: { kid: '123', alg: 'RS256' } }; + const fakeKeys = { + keys: [ + { + e: 'AQAB', + kid: '123', + n: 'ABCDEFGH', + }, + ], + }; + spyOn(jwt, 'decode').and.callFake(() => fakeDecodedToken); + spyOn(httpsRequest, 'get').and.callFake(() => fakeKeys); spyOn(jwt, 'verify').and.callFake(() => fakeClaim); try { @@ -1200,6 +1307,18 @@ describe('apple signin auth adapter', () => { aud: 'invalid_client_id', sub: 'the_user_id', }; + const fakeDecodedToken = { header: { kid: '123', alg: 'RS256' } }; + const fakeKeys = { + keys: [ + { + e: 'AQAB', + kid: '123', + n: 'ABCDEFGH', + }, + ], + }; + spyOn(jwt, 'decode').and.callFake(() => fakeDecodedToken); + spyOn(httpsRequest, 'get').and.callFake(() => fakeKeys); spyOn(jwt, 'verify').and.callFake(() => fakeClaim); try { diff --git a/src/Adapters/Auth/apple.js b/src/Adapters/Auth/apple.js index 1d487d0c96..a5b0e8e974 100644 --- a/src/Adapters/Auth/apple.js +++ b/src/Adapters/Auth/apple.js @@ -10,7 +10,7 @@ const TOKEN_ISSUER = 'https://appleid.apple.com'; let currentKey; -const getApplePublicKey = async () => { +const getApplePublicKey = async keyId => { let data; try { data = await httpsRequest.get('https://appleid.apple.com/auth/keys'); @@ -21,7 +21,11 @@ const getApplePublicKey = async () => { throw e; } - const key = data.keys[0]; + const key = data.keys.find(key => key.kid === keyId); + + if (!key) { + throw Error('Public key with matching key ID to token not found'); + } const pubKey = new NodeRSA(); pubKey.importKey( @@ -32,6 +36,17 @@ const getApplePublicKey = async () => { return currentKey; }; +const getKeyAndAlgoFromToken = token => { + const decodedToken = jwt.decode(token, { complete: true }); + if (!decodedToken) { + throw Error('provided token does not decode as JWT'); + } + const keyId = decodedToken.header.kid; + const algo = decodedToken.header.alg; + + return { keyId, algo }; +}; + const verifyIdToken = async ({ token, id }, clientID) => { if (!token) { throw new Parse.Error( @@ -39,8 +54,12 @@ const verifyIdToken = async ({ token, id }, clientID) => { 'id token is invalid for this user.' ); } - const applePublicKey = await getApplePublicKey(); - const jwtClaims = jwt.verify(token, applePublicKey, { algorithms: 'RS256' }); + + const decodedToken = getKeyAndAlgoFromToken(token); + const applePublicKey = await getApplePublicKey(decodedToken.keyId); + const jwtClaims = jwt.verify(token, applePublicKey, { + algorithms: decodedToken.algo, + }); if (jwtClaims.iss !== TOKEN_ISSUER) { throw new Parse.Error( From 9f1af7236326fb75be96e44869ac9e41e2ba26f2 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" <23040076+greenkeeper[bot]@users.noreply.github.com> Date: Tue, 3 Mar 2020 22:22:57 +0000 Subject: [PATCH 2/3] =?UTF-8?q?Update=20cross-env=20to=20the=20latest=20ve?= =?UTF-8?q?rsion=20=F0=9F=9A=80=20(#6461)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore(package): update cross-env to version 7.0.1 * chore(package): update lockfile package-lock.json --- package-lock.json | 6 +++--- package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 10e584cb66..e8ba0ddadd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4300,9 +4300,9 @@ } }, "cross-env": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.0.tgz", - "integrity": "sha512-rV6M9ldNgmwP7bx5u6rZsTbYidzwvrwIYZnT08hSGLcQCcggofgFW+sNe7IhA1SRauPS0QuLbbX+wdNtpqE5CQ==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.1.tgz", + "integrity": "sha512-1+DmLosu38kC4s1H4HzNkcolwdANifu9+5bE6uKQCV4L6jvVdV9qdRAk8vV3GoWRe0x4z+K2fFhgoDMqwNsPqQ==", "dev": true, "requires": { "cross-spawn": "^7.0.1" diff --git a/package.json b/package.json index 57d530cac8..6c0345fedb 100644 --- a/package.json +++ b/package.json @@ -73,7 +73,7 @@ "apollo-utilities": "1.3.3", "babel-eslint": "10.1.0", "bcrypt-nodejs": "0.0.3", - "cross-env": "7.0.0", + "cross-env": "7.0.1", "deep-diff": "1.0.2", "eslint": "6.8.0", "eslint-plugin-flowtype": "4.5.0", From 209d6f7744d4b055a4af39a664396014e71f78b0 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" <23040076+greenkeeper[bot]@users.noreply.github.com> Date: Tue, 3 Mar 2020 22:41:01 +0000 Subject: [PATCH 3/3] =?UTF-8?q?Update=20apollo-server=20to=20the=20latest?= =?UTF-8?q?=20version=20=F0=9F=9A=80=20(#6460)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(package): update apollo-server-express to version 2.11.0 * chore(package): update lockfile package-lock.json --- package-lock.json | 106 +++++++++++++++++++++++----------------------- package.json | 2 +- 2 files changed, 54 insertions(+), 54 deletions(-) diff --git a/package-lock.json b/package-lock.json index e8ba0ddadd..36d463bbb9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,9 +25,9 @@ }, "dependencies": { "@types/node": { - "version": "10.17.15", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.15.tgz", - "integrity": "sha512-daFGV9GSs6USfPgxceDA8nlSe48XrVCJfDeYm7eokxq/ye7iuOH87hKXgMtEAVLFapkczbZsx868PMDT1Y0a6A==" + "version": "10.17.17", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.17.tgz", + "integrity": "sha512-gpNnRnZP3VWzzj5k3qrpRC6Rk3H/uclhAVo1aIvwzK5p5cOrs9yEyQ8H/HBsBY0u5rrWxXEiVPQ0dEB6pkjE8Q==" } } }, @@ -2538,9 +2538,9 @@ } }, "@types/body-parser": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.17.1.tgz", - "integrity": "sha512-RoX2EZjMiFMjZh9lmYrwgoP9RTpAjSHiJxdp4oidAQVO02T7HER3xj9UKue5534ULWeqVEkujhWcyvUce+d68w==", + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.0.tgz", + "integrity": "sha512-W98JrE0j2K78swW4ukqMleo8R7h/pFETjM2DQ90MF6XK2i4LO4W3gQ71Lt4w3bfm2EvVSyWHplECvB5sK22yFQ==", "requires": { "@types/connect": "*", "@types/node": "*" @@ -2628,9 +2628,9 @@ "integrity": "sha512-GJhpTepz2udxGexqos8wgaBx4I/zWIDPh/KOGEwAqtuGDkOUJu5eFvwmdBX4AmB8Odsr+9pHCQqiAqDL/yKMKw==" }, "@types/koa": { - "version": "2.11.1", - "resolved": "https://registry.npmjs.org/@types/koa/-/koa-2.11.1.tgz", - "integrity": "sha512-/kqQs+8Qd9GL0cdl39HEhK91k7xq6+Zci76RUdqtTLj1Mg1aVG7zwJm3snkeyFUeAvY8noY27eMXgqg1wHZgwA==", + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/@types/koa/-/koa-2.11.2.tgz", + "integrity": "sha512-2UPelagNNW6bnc1I5kIzluCaheXRA9S+NyOdXEFFj9Az7jc15ek5V03kb8OTbb3tdZ5i2BIJObe86PhHvpMolg==", "requires": { "@types/accepts": "*", "@types/cookies": "*", @@ -2881,12 +2881,12 @@ } }, "apollo-cache-control": { - "version": "0.8.11", - "resolved": "https://registry.npmjs.org/apollo-cache-control/-/apollo-cache-control-0.8.11.tgz", - "integrity": "sha512-8yz4qbRBIFDWRHdT8uPh0HHh+VbQXxoFGJQRAG8hyMRvR+EuURXX1ltXYkn5J3YJ3MKEqgsvwGaq60dFZq63UQ==", + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/apollo-cache-control/-/apollo-cache-control-0.9.0.tgz", + "integrity": "sha512-iLT6IT4Ul5cMfBcJAvhpk3a7AD6fXqvFxNmJEPVapVJHbSKYIjra4PTis13sOyN5Y3WQS6a+NRFxAW8+hL3q3Q==", "requires": { "apollo-server-env": "^2.4.3", - "graphql-extensions": "^0.10.10" + "graphql-extensions": "^0.11.0" } }, "apollo-cache-inmemory": { @@ -2928,18 +2928,18 @@ } }, "apollo-engine-reporting": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/apollo-engine-reporting/-/apollo-engine-reporting-1.6.0.tgz", - "integrity": "sha512-prA17Tp/WYBJdCd4ey1CnGX8d4Xis1n9PsFmT7x8PV/oNpxG21/x3yNw5kPBZuKAoKz8yEggYtHhkYie1ZBjPQ==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/apollo-engine-reporting/-/apollo-engine-reporting-1.7.0.tgz", + "integrity": "sha512-jsjSnoHrRmk4XXK4aFU17YSJILmWsilKRwIeN74QJsSxjn5SCVF4EI/ebf/MNrTHpft8EhShx+wdkAcOD9ivqA==", "requires": { "apollo-engine-reporting-protobuf": "^0.4.4", "apollo-graphql": "^0.4.0", "apollo-server-caching": "^0.5.1", "apollo-server-env": "^2.4.3", - "apollo-server-errors": "^2.3.4", - "apollo-server-types": "^0.2.10", + "apollo-server-errors": "^2.4.0", + "apollo-server-types": "^0.3.0", "async-retry": "^1.2.1", - "graphql-extensions": "^0.10.10" + "graphql-extensions": "^0.11.0" } }, "apollo-engine-reporting-protobuf": { @@ -3022,25 +3022,25 @@ } }, "apollo-server-core": { - "version": "2.10.1", - "resolved": "https://registry.npmjs.org/apollo-server-core/-/apollo-server-core-2.10.1.tgz", - "integrity": "sha512-BVITSJRMnj+CWFkjt7FMcaoqg/Ni9gfyVE9iu8bUc1IebBfFDcQj652Iolr7dTqyUziN2jbf0wfcybKYJLQHQQ==", + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/apollo-server-core/-/apollo-server-core-2.11.0.tgz", + "integrity": "sha512-jHLOqwTRlyWzqWNRlwr2M/xfrt+lw2pHtKYyxUGRjWFo8EM5TX1gDcTKtbtvx9p5m+ZBDAhcWp/rpq0vSz4tqg==", "requires": { "@apollographql/apollo-tools": "^0.4.3", "@apollographql/graphql-playground-html": "1.6.24", "@types/graphql-upload": "^8.0.0", "@types/ws": "^6.0.0", - "apollo-cache-control": "^0.8.11", + "apollo-cache-control": "^0.9.0", "apollo-datasource": "^0.7.0", - "apollo-engine-reporting": "^1.6.0", + "apollo-engine-reporting": "^1.7.0", "apollo-server-caching": "^0.5.1", "apollo-server-env": "^2.4.3", - "apollo-server-errors": "^2.3.4", - "apollo-server-plugin-base": "^0.6.10", - "apollo-server-types": "^0.2.10", - "apollo-tracing": "^0.8.11", + "apollo-server-errors": "^2.4.0", + "apollo-server-plugin-base": "^0.7.0", + "apollo-server-types": "^0.3.0", + "apollo-tracing": "^0.9.0", "fast-json-stable-stringify": "^2.0.0", - "graphql-extensions": "^0.10.10", + "graphql-extensions": "^0.11.0", "graphql-tag": "^2.9.2", "graphql-tools": "^4.0.0", "graphql-upload": "^8.0.2", @@ -3085,23 +3085,23 @@ } }, "apollo-server-errors": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/apollo-server-errors/-/apollo-server-errors-2.3.4.tgz", - "integrity": "sha512-Y0PKQvkrb2Kd18d1NPlHdSqmlr8TgqJ7JQcNIfhNDgdb45CnqZlxL1abuIRhr8tiw8OhVOcFxz2KyglBi8TKdA==" + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/apollo-server-errors/-/apollo-server-errors-2.4.0.tgz", + "integrity": "sha512-ZouZfr2sGavvI18rgdRcyY2ausRAlVtWNOax9zca8ZG2io86dM59jXBmUVSNlVZSmBsIh45YxYC0eRvr2vmRdg==" }, "apollo-server-express": { - "version": "2.10.1", - "resolved": "https://registry.npmjs.org/apollo-server-express/-/apollo-server-express-2.10.1.tgz", - "integrity": "sha512-NkuWGBOCTiju/aDjfvDImm+4yzfrM0dwiRxu9fKwwh2h1oYBUKJNqjQ1mzJRi0ks6Sn1egwl/fQkTBTkWwGx7Q==", + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/apollo-server-express/-/apollo-server-express-2.11.0.tgz", + "integrity": "sha512-9bbiD+zFAx+xyurc9lxYmNa9y79k/gsA1vEyPFVcv7jxzCFC5wc0tcbV7NPX2qi1Nn7K76fxo2fPNYbPFX/y0g==", "requires": { "@apollographql/graphql-playground-html": "1.6.24", "@types/accepts": "^1.3.5", - "@types/body-parser": "1.17.1", + "@types/body-parser": "1.19.0", "@types/cors": "^2.8.4", "@types/express": "4.17.2", "accepts": "^1.3.5", - "apollo-server-core": "^2.10.1", - "apollo-server-types": "^0.2.10", + "apollo-server-core": "^2.11.0", + "apollo-server-types": "^0.3.0", "body-parser": "^1.18.3", "cors": "^2.8.4", "express": "^4.17.1", @@ -3113,17 +3113,17 @@ } }, "apollo-server-plugin-base": { - "version": "0.6.10", - "resolved": "https://registry.npmjs.org/apollo-server-plugin-base/-/apollo-server-plugin-base-0.6.10.tgz", - "integrity": "sha512-/xT7UT/tbCDIoTQ4lcEQsJ0ACh7h7QG0BDmeSlDXjwDuENRI50bQ2QoluCMPitZXGe+FCQfLhvzFgzbsZGT0IA==", + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/apollo-server-plugin-base/-/apollo-server-plugin-base-0.7.0.tgz", + "integrity": "sha512-//xgYrBYLQSr92W0z3mYsFGoVz3wxKNsv3KcOUBhbOCGTbjZgP7vHOE1vhHhRcpZKKXmjXTVONdrnNJ+XVGi6A==", "requires": { - "apollo-server-types": "^0.2.10" + "apollo-server-types": "^0.3.0" } }, "apollo-server-types": { - "version": "0.2.10", - "resolved": "https://registry.npmjs.org/apollo-server-types/-/apollo-server-types-0.2.10.tgz", - "integrity": "sha512-ke9ViPEWfW+2XLe66CaKGVZdS7duSLbamSKSprmmeMBd8s6tmjf0FumUVxV7X4quxPZi0OPo8x0LoLU7GWsmaA==", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/apollo-server-types/-/apollo-server-types-0.3.0.tgz", + "integrity": "sha512-FMo7kbTkhph9dfIQ3xDbRLObqmdQH9mwSjxhGsX+JxGMRPPXgd3+GZvCeVKOi/udxh//w1otSeAqItjvbj0tfQ==", "requires": { "apollo-engine-reporting-protobuf": "^0.4.4", "apollo-server-caching": "^0.5.1", @@ -3131,12 +3131,12 @@ } }, "apollo-tracing": { - "version": "0.8.11", - "resolved": "https://registry.npmjs.org/apollo-tracing/-/apollo-tracing-0.8.11.tgz", - "integrity": "sha512-Z0wDZ5QOBmpGoajB74ZKGTM7GzG6rqZRzAph4kxud6axcyNqUDKiKZ3Eere+NSLwvvt8M3qnPW4UJSUy/wwOXg==", + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/apollo-tracing/-/apollo-tracing-0.9.0.tgz", + "integrity": "sha512-oqspTrf4BLGbKkIk1vF+I31C2v7PPJmF36TFpT/+zJxNvJw54ji4ZMhtytgVqbVldQEintJmdHQIidYBGKmu+g==", "requires": { "apollo-server-env": "^2.4.3", - "graphql-extensions": "^0.10.10" + "graphql-extensions": "^0.11.0" } }, "apollo-upload-client": { @@ -6832,13 +6832,13 @@ } }, "graphql-extensions": { - "version": "0.10.10", - "resolved": "https://registry.npmjs.org/graphql-extensions/-/graphql-extensions-0.10.10.tgz", - "integrity": "sha512-pNb1DmUk6vsGtCjCRecpKoXadKNMyKxyLyE9IX65N9aKSmLL+AF7dJOOc4MWhdaAXlzxaDDhe54GpaOfoH7AOw==", + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/graphql-extensions/-/graphql-extensions-0.11.0.tgz", + "integrity": "sha512-zd4qfUiJoYBx2MwJusM36SEJ+YmJ1ki8YF8nlm9mgaPDUzsnmFq4lxULxUfhLAXFwZw7MbEN2vV4V6WiNgSJLg==", "requires": { "@apollographql/apollo-tools": "^0.4.3", "apollo-server-env": "^2.4.3", - "apollo-server-types": "^0.2.10" + "apollo-server-types": "^0.3.0" } }, "graphql-list-fields": { diff --git a/package.json b/package.json index 6c0345fedb..d5e58a5103 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "@parse/push-adapter": "3.2.0", "@parse/s3-files-adapter": "1.4.0", "@parse/simple-mailgun-adapter": "1.1.0", - "apollo-server-express": "2.10.1", + "apollo-server-express": "2.11.0", "bcryptjs": "2.4.3", "body-parser": "1.19.0", "commander": "4.1.1",