2019-10-18 19:04:01 -05:00
|
|
|
// Apple SignIn Auth
|
|
|
|
|
// https://developer.apple.com/documentation/signinwithapplerestapi
|
|
|
|
|
|
2019-06-19 16:05:09 -05:00
|
|
|
const Parse = require('parse/node').Parse;
|
2020-03-11 15:29:20 -05:00
|
|
|
const jwksClient = require('jwks-rsa');
|
|
|
|
|
const util = require('util');
|
2019-06-19 16:05:09 -05:00
|
|
|
const jwt = require('jsonwebtoken');
|
|
|
|
|
|
|
|
|
|
const TOKEN_ISSUER = 'https://appleid.apple.com';
|
|
|
|
|
|
2020-03-11 15:29:20 -05:00
|
|
|
const getAppleKeyByKeyId = async (keyId, cacheMaxEntries, cacheMaxAge) => {
|
|
|
|
|
const client = jwksClient({
|
|
|
|
|
jwksUri: `${TOKEN_ISSUER}/auth/keys`,
|
|
|
|
|
cache: true,
|
|
|
|
|
cacheMaxEntries,
|
|
|
|
|
cacheMaxAge,
|
|
|
|
|
});
|
2019-07-25 10:20:28 -07:00
|
|
|
|
2020-03-11 15:29:20 -05:00
|
|
|
const asyncGetSigningKeyFunction = util.promisify(client.getSigningKey);
|
|
|
|
|
|
|
|
|
|
let key;
|
2019-07-25 10:20:28 -07:00
|
|
|
try {
|
2020-03-11 15:29:20 -05:00
|
|
|
key = await asyncGetSigningKeyFunction(keyId);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
throw new Parse.Error(
|
|
|
|
|
Parse.Error.OBJECT_NOT_FOUND,
|
|
|
|
|
`Unable to find matching key for Key ID: ${keyId}`
|
|
|
|
|
);
|
2019-07-25 10:20:28 -07:00
|
|
|
}
|
2020-03-11 15:29:20 -05:00
|
|
|
return key;
|
|
|
|
|
};
|
2019-07-25 10:20:28 -07:00
|
|
|
|
2020-03-11 15:29:20 -05:00
|
|
|
const getHeaderFromToken = token => {
|
|
|
|
|
const decodedToken = jwt.decode(token, { complete: true });
|
|
|
|
|
if (!decodedToken) {
|
2020-03-21 17:04:10 -07:00
|
|
|
throw new Parse.Error(
|
|
|
|
|
Parse.Error.OBJECT_NOT_FOUND,
|
|
|
|
|
`provided token does not decode as JWT`
|
|
|
|
|
);
|
2020-03-11 15:29:20 -05:00
|
|
|
}
|
2020-03-21 17:04:10 -07:00
|
|
|
|
2020-03-11 15:29:20 -05:00
|
|
|
return decodedToken.header;
|
2019-06-19 16:05:09 -05:00
|
|
|
};
|
|
|
|
|
|
2020-03-11 15:29:20 -05:00
|
|
|
const verifyIdToken = async (
|
|
|
|
|
{ token, id },
|
|
|
|
|
{ clientId, cacheMaxEntries, cacheMaxAge }
|
|
|
|
|
) => {
|
2019-06-19 16:05:09 -05:00
|
|
|
if (!token) {
|
|
|
|
|
throw new Parse.Error(
|
|
|
|
|
Parse.Error.OBJECT_NOT_FOUND,
|
2020-03-21 17:04:10 -07:00
|
|
|
`id token is invalid for this user.`
|
2019-06-19 16:05:09 -05:00
|
|
|
);
|
|
|
|
|
}
|
2020-03-11 15:29:20 -05:00
|
|
|
|
|
|
|
|
const { kid: keyId, alg: algorithm } = getHeaderFromToken(token);
|
|
|
|
|
const ONE_HOUR_IN_MS = 3600000;
|
2020-03-21 17:04:10 -07:00
|
|
|
let jwtClaims;
|
|
|
|
|
|
2020-03-11 15:29:20 -05:00
|
|
|
cacheMaxAge = cacheMaxAge || ONE_HOUR_IN_MS;
|
|
|
|
|
cacheMaxEntries = cacheMaxEntries || 5;
|
|
|
|
|
|
|
|
|
|
const appleKey = await getAppleKeyByKeyId(
|
|
|
|
|
keyId,
|
|
|
|
|
cacheMaxEntries,
|
|
|
|
|
cacheMaxAge
|
|
|
|
|
);
|
|
|
|
|
const signingKey = appleKey.publicKey || appleKey.rsaPublicKey;
|
|
|
|
|
|
2020-03-21 17:04:10 -07:00
|
|
|
try {
|
|
|
|
|
jwtClaims = jwt.verify(token, signingKey, {
|
|
|
|
|
algorithms: algorithm,
|
|
|
|
|
// the audience can be checked against a string, a regular expression or a list of strings and/or regular expressions.
|
|
|
|
|
audience: clientId,
|
|
|
|
|
});
|
|
|
|
|
} catch (exception) {
|
|
|
|
|
const message = exception.message;
|
|
|
|
|
|
|
|
|
|
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, `${message}`);
|
|
|
|
|
}
|
2019-06-19 16:05:09 -05:00
|
|
|
|
|
|
|
|
if (jwtClaims.iss !== TOKEN_ISSUER) {
|
|
|
|
|
throw new Parse.Error(
|
|
|
|
|
Parse.Error.OBJECT_NOT_FOUND,
|
2019-07-03 16:28:29 -05:00
|
|
|
`id token not issued by correct OpenID provider - expected: ${TOKEN_ISSUER} | from: ${jwtClaims.iss}`
|
2019-06-19 16:05:09 -05:00
|
|
|
);
|
|
|
|
|
}
|
2020-03-21 17:04:10 -07:00
|
|
|
|
2019-08-08 01:08:14 +02:00
|
|
|
if (jwtClaims.sub !== id) {
|
|
|
|
|
throw new Parse.Error(
|
|
|
|
|
Parse.Error.OBJECT_NOT_FOUND,
|
|
|
|
|
`auth data is invalid for this user.`
|
|
|
|
|
);
|
|
|
|
|
}
|
2019-06-19 16:05:09 -05:00
|
|
|
return jwtClaims;
|
|
|
|
|
};
|
|
|
|
|
|
2019-07-03 16:28:29 -05:00
|
|
|
// Returns a promise that fulfills if this id token is valid
|
2019-06-19 16:05:09 -05:00
|
|
|
function validateAuthData(authData, options = {}) {
|
2020-03-11 15:29:20 -05:00
|
|
|
return verifyIdToken(authData, options);
|
2019-06-19 16:05:09 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Returns a promise that fulfills if this app id is valid.
|
|
|
|
|
function validateAppId() {
|
|
|
|
|
return Promise.resolve();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
module.exports = {
|
2019-07-03 16:28:29 -05:00
|
|
|
validateAppId,
|
|
|
|
|
validateAuthData,
|
2019-06-19 16:05:09 -05:00
|
|
|
};
|