Sign in with Apple Auth Provider (#5694)
* Sign in with Apple Auth Provider Closes: https://github.com/parse-community/parse-server/issues/5632 Should work out of the box. * remove required options
This commit is contained in:
53
package-lock.json
generated
53
package-lock.json
generated
@@ -4292,8 +4292,7 @@
|
|||||||
"ansi-regex": {
|
"ansi-regex": {
|
||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true
|
||||||
"optional": true
|
|
||||||
},
|
},
|
||||||
"aproba": {
|
"aproba": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
@@ -4314,14 +4313,12 @@
|
|||||||
"balanced-match": {
|
"balanced-match": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true
|
||||||
"optional": true
|
|
||||||
},
|
},
|
||||||
"brace-expansion": {
|
"brace-expansion": {
|
||||||
"version": "1.1.11",
|
"version": "1.1.11",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"optional": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"balanced-match": "^1.0.0",
|
"balanced-match": "^1.0.0",
|
||||||
"concat-map": "0.0.1"
|
"concat-map": "0.0.1"
|
||||||
@@ -4336,20 +4333,17 @@
|
|||||||
"code-point-at": {
|
"code-point-at": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true
|
||||||
"optional": true
|
|
||||||
},
|
},
|
||||||
"concat-map": {
|
"concat-map": {
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true
|
||||||
"optional": true
|
|
||||||
},
|
},
|
||||||
"console-control-strings": {
|
"console-control-strings": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true
|
||||||
"optional": true
|
|
||||||
},
|
},
|
||||||
"core-util-is": {
|
"core-util-is": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
@@ -4466,8 +4460,7 @@
|
|||||||
"inherits": {
|
"inherits": {
|
||||||
"version": "2.0.3",
|
"version": "2.0.3",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true
|
||||||
"optional": true
|
|
||||||
},
|
},
|
||||||
"ini": {
|
"ini": {
|
||||||
"version": "1.3.5",
|
"version": "1.3.5",
|
||||||
@@ -4479,7 +4472,6 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"optional": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"number-is-nan": "^1.0.0"
|
"number-is-nan": "^1.0.0"
|
||||||
}
|
}
|
||||||
@@ -4494,7 +4486,6 @@
|
|||||||
"version": "3.0.4",
|
"version": "3.0.4",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"optional": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"brace-expansion": "^1.1.7"
|
"brace-expansion": "^1.1.7"
|
||||||
}
|
}
|
||||||
@@ -4502,14 +4493,12 @@
|
|||||||
"minimist": {
|
"minimist": {
|
||||||
"version": "0.0.8",
|
"version": "0.0.8",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true
|
||||||
"optional": true
|
|
||||||
},
|
},
|
||||||
"minipass": {
|
"minipass": {
|
||||||
"version": "2.3.5",
|
"version": "2.3.5",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"optional": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"safe-buffer": "^5.1.2",
|
"safe-buffer": "^5.1.2",
|
||||||
"yallist": "^3.0.0"
|
"yallist": "^3.0.0"
|
||||||
@@ -4528,7 +4517,6 @@
|
|||||||
"version": "0.5.1",
|
"version": "0.5.1",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"optional": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"minimist": "0.0.8"
|
"minimist": "0.0.8"
|
||||||
}
|
}
|
||||||
@@ -4609,8 +4597,7 @@
|
|||||||
"number-is-nan": {
|
"number-is-nan": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true
|
||||||
"optional": true
|
|
||||||
},
|
},
|
||||||
"object-assign": {
|
"object-assign": {
|
||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
@@ -4622,7 +4609,6 @@
|
|||||||
"version": "1.4.0",
|
"version": "1.4.0",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"optional": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"wrappy": "1"
|
"wrappy": "1"
|
||||||
}
|
}
|
||||||
@@ -4708,8 +4694,7 @@
|
|||||||
"safe-buffer": {
|
"safe-buffer": {
|
||||||
"version": "5.1.2",
|
"version": "5.1.2",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true
|
||||||
"optional": true
|
|
||||||
},
|
},
|
||||||
"safer-buffer": {
|
"safer-buffer": {
|
||||||
"version": "2.1.2",
|
"version": "2.1.2",
|
||||||
@@ -4745,7 +4730,6 @@
|
|||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"optional": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"code-point-at": "^1.0.0",
|
"code-point-at": "^1.0.0",
|
||||||
"is-fullwidth-code-point": "^1.0.0",
|
"is-fullwidth-code-point": "^1.0.0",
|
||||||
@@ -4765,7 +4749,6 @@
|
|||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"optional": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"ansi-regex": "^2.0.0"
|
"ansi-regex": "^2.0.0"
|
||||||
}
|
}
|
||||||
@@ -4809,14 +4792,12 @@
|
|||||||
"wrappy": {
|
"wrappy": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true
|
||||||
"optional": true
|
|
||||||
},
|
},
|
||||||
"yallist": {
|
"yallist": {
|
||||||
"version": "3.0.3",
|
"version": "3.0.3",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true
|
||||||
"optional": true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -6977,7 +6958,6 @@
|
|||||||
"version": "2.3.5",
|
"version": "2.3.5",
|
||||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-2.3.5.tgz",
|
"resolved": "https://registry.npmjs.org/minipass/-/minipass-2.3.5.tgz",
|
||||||
"integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==",
|
"integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==",
|
||||||
"optional": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"safe-buffer": "^5.1.2",
|
"safe-buffer": "^5.1.2",
|
||||||
"yallist": "^3.0.0"
|
"yallist": "^3.0.0"
|
||||||
@@ -6986,8 +6966,7 @@
|
|||||||
"yallist": {
|
"yallist": {
|
||||||
"version": "3.0.3",
|
"version": "3.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz",
|
||||||
"integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==",
|
"integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A=="
|
||||||
"optional": true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -7511,6 +7490,14 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node-rsa": {
|
||||||
|
"version": "1.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-rsa/-/node-rsa-1.0.5.tgz",
|
||||||
|
"integrity": "sha512-9o51yfV167CtQANnuAf+5owNs7aIMsAKVLhNaKuRxihsUUnfoBMN5OTVOK/2mHSOWaWq9zZBiRM3bHORbTZqrg==",
|
||||||
|
"requires": {
|
||||||
|
"asn1": "^0.2.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"nopt": {
|
"nopt": {
|
||||||
"version": "4.0.1",
|
"version": "4.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz",
|
||||||
|
|||||||
@@ -30,10 +30,12 @@
|
|||||||
"express": "4.17.1",
|
"express": "4.17.1",
|
||||||
"follow-redirects": "1.7.0",
|
"follow-redirects": "1.7.0",
|
||||||
"intersect": "1.0.1",
|
"intersect": "1.0.1",
|
||||||
|
"jsonwebtoken": "8.5.1",
|
||||||
"lodash": "4.17.11",
|
"lodash": "4.17.11",
|
||||||
"lru-cache": "5.1.1",
|
"lru-cache": "5.1.1",
|
||||||
"mime": "2.4.4",
|
"mime": "2.4.4",
|
||||||
"mongodb": "3.2.7",
|
"mongodb": "3.2.7",
|
||||||
|
"node-rsa": "1.0.5",
|
||||||
"parse": "2.4.0",
|
"parse": "2.4.0",
|
||||||
"pg-promise": "8.7.2",
|
"pg-promise": "8.7.2",
|
||||||
"redis": "2.8.0",
|
"redis": "2.8.0",
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ const responses = {
|
|||||||
|
|
||||||
describe('AuthenticationProviders', function() {
|
describe('AuthenticationProviders', function() {
|
||||||
[
|
[
|
||||||
|
'apple-signin',
|
||||||
'facebook',
|
'facebook',
|
||||||
'facebookaccountkit',
|
'facebookaccountkit',
|
||||||
'github',
|
'github',
|
||||||
@@ -50,7 +51,7 @@ describe('AuthenticationProviders', function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it(`should provide the right responses for adapter ${providerName}`, async () => {
|
it(`should provide the right responses for adapter ${providerName}`, async () => {
|
||||||
if (providerName === 'twitter') {
|
if (providerName === 'twitter' || providerName === 'apple-signin') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
spyOn(require('../lib/Adapters/Auth/httpsRequest'), 'get').and.callFake(
|
spyOn(require('../lib/Adapters/Auth/httpsRequest'), 'get').and.callFake(
|
||||||
@@ -1033,3 +1034,83 @@ describe('oauth2 auth adapter', () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('apple signin auth adapter', () => {
|
||||||
|
const apple = require('../lib/Adapters/Auth/apple-signin');
|
||||||
|
const jwt = require('jsonwebtoken');
|
||||||
|
|
||||||
|
it('should throw error with missing id_token', async () => {
|
||||||
|
try {
|
||||||
|
await apple.validateAuthData({}, { client_id: 'secret' });
|
||||||
|
fail();
|
||||||
|
} catch (e) {
|
||||||
|
expect(e.message).toBe('id_token is invalid for this user.');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not verify invalid id_token', async () => {
|
||||||
|
try {
|
||||||
|
await apple.validateAuthData(
|
||||||
|
{ id_token: 'the_token' },
|
||||||
|
{ client_id: 'secret' }
|
||||||
|
);
|
||||||
|
fail();
|
||||||
|
} catch (e) {
|
||||||
|
expect(e.message).toBe('jwt malformed');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should verify id_token', async () => {
|
||||||
|
const fakeClaim = {
|
||||||
|
iss: 'https://appleid.apple.com',
|
||||||
|
aud: 'secret',
|
||||||
|
exp: Date.now(),
|
||||||
|
};
|
||||||
|
spyOn(jwt, 'verify').and.callFake(() => fakeClaim);
|
||||||
|
|
||||||
|
const result = await apple.validateAuthData(
|
||||||
|
{ id_token: 'the_token' },
|
||||||
|
{ client_id: 'secret' }
|
||||||
|
);
|
||||||
|
expect(result).toEqual(fakeClaim);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw error with with invalid jwt issuer', async () => {
|
||||||
|
const fakeClaim = {
|
||||||
|
iss: 'https://not.apple.com',
|
||||||
|
};
|
||||||
|
spyOn(jwt, 'verify').and.callFake(() => fakeClaim);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await apple.validateAuthData(
|
||||||
|
{ id_token: 'the_token' },
|
||||||
|
{ client_id: 'secret' }
|
||||||
|
);
|
||||||
|
fail();
|
||||||
|
} catch (e) {
|
||||||
|
expect(e.message).toBe(
|
||||||
|
'id_token not issued by correct OpenID provider - expected: https://appleid.apple.com | from: https://not.apple.com'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw error with with invalid jwt client_id', async () => {
|
||||||
|
const fakeClaim = {
|
||||||
|
iss: 'https://appleid.apple.com',
|
||||||
|
aud: 'invalid_client_id',
|
||||||
|
};
|
||||||
|
spyOn(jwt, 'verify').and.callFake(() => fakeClaim);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await apple.validateAuthData(
|
||||||
|
{ id_token: 'the_token' },
|
||||||
|
{ client_id: 'secret' }
|
||||||
|
);
|
||||||
|
fail();
|
||||||
|
} catch (e) {
|
||||||
|
expect(e.message).toBe(
|
||||||
|
'jwt aud parameter does not include this client - is: invalid_client_id | expected: secret'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
58
src/Adapters/Auth/apple-signin.js
Normal file
58
src/Adapters/Auth/apple-signin.js
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
const Parse = require('parse/node').Parse;
|
||||||
|
const httpsRequest = require('./httpsRequest');
|
||||||
|
const NodeRSA = require('node-rsa');
|
||||||
|
const jwt = require('jsonwebtoken');
|
||||||
|
|
||||||
|
const TOKEN_ISSUER = 'https://appleid.apple.com';
|
||||||
|
|
||||||
|
const getApplePublicKey = async () => {
|
||||||
|
const data = await httpsRequest.get('https://appleid.apple.com/auth/keys');
|
||||||
|
const key = data.keys[0];
|
||||||
|
|
||||||
|
const pubKey = new NodeRSA();
|
||||||
|
pubKey.importKey(
|
||||||
|
{ n: Buffer.from(key.n, 'base64'), e: Buffer.from(key.e, 'base64') },
|
||||||
|
'components-public'
|
||||||
|
);
|
||||||
|
return pubKey.exportKey(['public']);
|
||||||
|
};
|
||||||
|
|
||||||
|
const verifyIdToken = async (token, clientID) => {
|
||||||
|
if (!token) {
|
||||||
|
throw new Parse.Error(
|
||||||
|
Parse.Error.OBJECT_NOT_FOUND,
|
||||||
|
'id_token is invalid for this user.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const applePublicKey = await getApplePublicKey();
|
||||||
|
const jwtClaims = jwt.verify(token, applePublicKey, { algorithms: 'RS256' });
|
||||||
|
|
||||||
|
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 (clientID !== undefined && jwtClaims.aud !== clientID) {
|
||||||
|
throw new Parse.Error(
|
||||||
|
Parse.Error.OBJECT_NOT_FOUND,
|
||||||
|
`jwt aud parameter does not include this client - is: ${jwtClaims.aud} | expected: ${clientID}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return jwtClaims;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Returns a promise that fulfills if this id_token is valid
|
||||||
|
function validateAuthData(authData, options = {}) {
|
||||||
|
return verifyIdToken(authData.id_token, options.client_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a promise that fulfills if this app id is valid.
|
||||||
|
function validateAppId() {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
validateAppId: validateAppId,
|
||||||
|
validateAuthData: validateAuthData,
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user