* updated 2 files for allowing multiple client ids * updated tests that fail due to user inputting data in code, added todo comment to them stating what we need to do to fix them
112 lines
2.8 KiB
JavaScript
112 lines
2.8 KiB
JavaScript
// Apple SignIn Auth
|
|
// https://developer.apple.com/documentation/signinwithapplerestapi
|
|
|
|
const Parse = require('parse/node').Parse;
|
|
const jwksClient = require('jwks-rsa');
|
|
const util = require('util');
|
|
const jwt = require('jsonwebtoken');
|
|
|
|
const TOKEN_ISSUER = 'https://appleid.apple.com';
|
|
|
|
const getAppleKeyByKeyId = async (keyId, cacheMaxEntries, cacheMaxAge) => {
|
|
const client = jwksClient({
|
|
jwksUri: `${TOKEN_ISSUER}/auth/keys`,
|
|
cache: true,
|
|
cacheMaxEntries,
|
|
cacheMaxAge,
|
|
});
|
|
|
|
const asyncGetSigningKeyFunction = util.promisify(client.getSigningKey);
|
|
|
|
let key;
|
|
try {
|
|
key = await asyncGetSigningKeyFunction(keyId);
|
|
} catch (error) {
|
|
throw new Parse.Error(
|
|
Parse.Error.OBJECT_NOT_FOUND,
|
|
`Unable to find matching key for Key ID: ${keyId}`
|
|
);
|
|
}
|
|
return key;
|
|
};
|
|
|
|
const getHeaderFromToken = token => {
|
|
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`
|
|
);
|
|
}
|
|
|
|
return decodedToken.header;
|
|
};
|
|
|
|
const verifyIdToken = async (
|
|
{ token, id },
|
|
{ clientId, cacheMaxEntries, cacheMaxAge }
|
|
) => {
|
|
if (!token) {
|
|
throw new Parse.Error(
|
|
Parse.Error.OBJECT_NOT_FOUND,
|
|
`id token is invalid for this user.`
|
|
);
|
|
}
|
|
|
|
const { kid: keyId, alg: algorithm } = getHeaderFromToken(token);
|
|
const ONE_HOUR_IN_MS = 3600000;
|
|
let jwtClaims;
|
|
|
|
cacheMaxAge = cacheMaxAge || ONE_HOUR_IN_MS;
|
|
cacheMaxEntries = cacheMaxEntries || 5;
|
|
|
|
const appleKey = await getAppleKeyByKeyId(
|
|
keyId,
|
|
cacheMaxEntries,
|
|
cacheMaxAge
|
|
);
|
|
const signingKey = appleKey.publicKey || appleKey.rsaPublicKey;
|
|
|
|
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}`);
|
|
}
|
|
|
|
if (jwtClaims.iss !== TOKEN_ISSUER) {
|
|
throw new Parse.Error(
|
|
Parse.Error.OBJECT_NOT_FOUND,
|
|
`id token not issued by correct OpenID provider - expected: ${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.`
|
|
);
|
|
}
|
|
return jwtClaims;
|
|
};
|
|
|
|
// Returns a promise that fulfills if this id token is valid
|
|
function validateAuthData(authData, options = {}) {
|
|
return verifyIdToken(authData, options);
|
|
}
|
|
|
|
// Returns a promise that fulfills if this app id is valid.
|
|
function validateAppId() {
|
|
return Promise.resolve();
|
|
}
|
|
|
|
module.exports = {
|
|
validateAppId,
|
|
validateAuthData,
|
|
};
|