GraphQL: Optimize queries, fixes some null returns (on object), fix stitched GraphQLUpload (#6709)

* Optimize query, fixes some null returns, fix stitched GraphQLUpload

* Fix authData key selection

* Prefer Iso string since other GraphQL solutions use this format

* fix tests

Co-authored-by: Antonio Davi Macedo Coelho de Castro <adavimacedo@gmail.com>
This commit is contained in:
Antoine Cormouls
2020-10-02 00:19:26 +02:00
committed by GitHub
parent 929c4e1b0d
commit 62048260c9
32 changed files with 1533 additions and 1161 deletions

View File

@@ -1,4 +1,4 @@
"use strict";
'use strict';
// Helper functions for accessing the google API.
var Parse = require('parse/node').Parse;
@@ -11,7 +11,6 @@ const HTTPS_TOKEN_ISSUER = 'https://accounts.google.com';
let cache = {};
// Retrieve Google Signin Keys (with cache control)
function getGoogleKeyByKeyId(keyId) {
if (cache[keyId] && cache.expiresAt > new Date()) {
@@ -19,42 +18,60 @@ function getGoogleKeyByKeyId(keyId) {
}
return new Promise((resolve, reject) => {
https.get(`https://www.googleapis.com/oauth2/v3/certs`, res => {
let data = '';
res.on('data', chunk => {
data += chunk.toString('utf8');
});
res.on('end', () => {
const {keys} = JSON.parse(data);
const pems = keys.reduce((pems, {n: modulus, e: exposant, kid}) => Object.assign(pems, {[kid]: rsaPublicKeyToPEM(modulus, exposant)}), {});
https
.get(`https://www.googleapis.com/oauth2/v3/certs`, res => {
let data = '';
res.on('data', chunk => {
data += chunk.toString('utf8');
});
res.on('end', () => {
const { keys } = JSON.parse(data);
const pems = keys.reduce(
(pems, { n: modulus, e: exposant, kid }) =>
Object.assign(pems, {
[kid]: rsaPublicKeyToPEM(modulus, exposant),
}),
{}
);
if (res.headers['cache-control']) {
var expire = res.headers['cache-control'].match(/max-age=([0-9]+)/);
if (res.headers['cache-control']) {
var expire = res.headers['cache-control'].match(/max-age=([0-9]+)/);
if (expire) {
cache = Object.assign({}, pems, {expiresAt: new Date((new Date()).getTime() + Number(expire[1]) * 1000)});
if (expire) {
cache = Object.assign({}, pems, {
expiresAt: new Date(
new Date().getTime() + Number(expire[1]) * 1000
),
});
}
}
}
resolve(pems[keyId]);
});
}).on('error', reject);
resolve(pems[keyId]);
});
})
.on('error', reject);
});
}
function getHeaderFromToken(token) {
const decodedToken = jwt.decode(token, {complete: true});
const decodedToken = jwt.decode(token, { complete: true });
if (!decodedToken) {
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, `provided token does not decode as JWT`);
throw new Parse.Error(
Parse.Error.OBJECT_NOT_FOUND,
`provided token does not decode as JWT`
);
}
return decodedToken.header;
}
async function verifyIdToken({id_token: token, id}, {clientId}) {
async function verifyIdToken({ id_token: token, id }, { clientId }) {
if (!token) {
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, `id token is invalid for this user.`);
throw new Parse.Error(
Parse.Error.OBJECT_NOT_FOUND,
`id token is invalid for this user.`
);
}
const { kid: keyId, alg: algorithm } = getHeaderFromToken(token);
@@ -62,22 +79,34 @@ async function verifyIdToken({id_token: token, id}, {clientId}) {
const googleKey = await getGoogleKeyByKeyId(keyId);
try {
jwtClaims = jwt.verify(token, googleKey, { algorithms: algorithm, audience: clientId });
jwtClaims = jwt.verify(token, googleKey, {
algorithms: algorithm,
audience: clientId,
});
} catch (exception) {
const message = exception.message;
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, `${message}`);
}
if (jwtClaims.iss !== TOKEN_ISSUER && jwtClaims.iss !== HTTPS_TOKEN_ISSUER) {
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, `id token not issued by correct provider - expected: ${TOKEN_ISSUER} or ${HTTPS_TOKEN_ISSUER} | from: ${jwtClaims.iss}`);
throw new Parse.Error(
Parse.Error.OBJECT_NOT_FOUND,
`id token not issued by correct provider - expected: ${TOKEN_ISSUER} or ${HTTPS_TOKEN_ISSUER} | from: ${jwtClaims.iss}`
);
}
if (jwtClaims.sub !== id) {
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, `auth data is invalid for this user.`);
throw new Parse.Error(
Parse.Error.OBJECT_NOT_FOUND,
`auth data is invalid for this user.`
);
}
if (clientId && jwtClaims.aud !== clientId) {
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, `id token not authorized for this clientId.`);
throw new Parse.Error(
Parse.Error.OBJECT_NOT_FOUND,
`id token not authorized for this clientId.`
);
}
return jwtClaims;
@@ -95,10 +124,9 @@ function validateAppId() {
module.exports = {
validateAppId: validateAppId,
validateAuthData: validateAuthData
validateAuthData: validateAuthData,
};
// Helpers functions to convert the RSA certs to PEM (from jwks-rsa)
function rsaPublicKeyToPEM(modulusB64, exponentB64) {
const modulus = new Buffer(modulusB64, 'base64');
@@ -110,13 +138,19 @@ function rsaPublicKeyToPEM(modulusB64, exponentB64) {
const encodedModlen = encodeLengthHex(modlen);
const encodedExplen = encodeLengthHex(explen);
const encodedPubkey = '30' +
encodeLengthHex(modlen + explen + encodedModlen.length / 2 + encodedExplen.length / 2 + 2) +
'02' + encodedModlen + modulusHex +
'02' + encodedExplen + exponentHex;
const encodedPubkey =
'30' +
encodeLengthHex(
modlen + explen + encodedModlen.length / 2 + encodedExplen.length / 2 + 2
) +
'02' +
encodedModlen +
modulusHex +
'02' +
encodedExplen +
exponentHex;
const der = new Buffer(encodedPubkey, 'hex')
.toString('base64');
const der = new Buffer(encodedPubkey, 'hex').toString('base64');
let pem = '-----BEGIN RSA PUBLIC KEY-----\n';
pem += `${der.match(/.{1,64}/g).join('\n')}`;