Skip to content

Commit 213801c

Browse files
6thfdwpflovilmart
authored andcommitted
auth: add adapter for Facebook accountkit login (#4434)
* Integrate auth adapter for Facebook accountkit login * Also verify Facebook app id associated with account kit login * Add appsecret_proof as extra graph request parameter * Specific error message for Account kit and more test coverage * One more test to cover when AppIds for Facebook account kit not configured properly
1 parent 55f4b0f commit 213801c

File tree

3 files changed

+144
-2
lines changed

3 files changed

+144
-2
lines changed

spec/AuthenticationAdapters.spec.js

+67-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ const authenticationLoader = require('../src/Adapters/Auth');
55
const path = require('path');
66

77
describe('AuthenticationProviders', function() {
8-
["facebook", "github", "instagram", "google", "linkedin", "meetup", "twitter", "janrainengage", "janraincapture", "vkontakte"].map(function(providerName){
8+
["facebook", "facebookaccountkit", "github", "instagram", "google", "linkedin", "meetup", "twitter", "janrainengage", "janraincapture", "vkontakte"].map(function(providerName){
99
it("Should validate structure of " + providerName, (done) => {
1010
const provider = require("../src/Adapters/Auth/" + providerName);
1111
jequal(typeof provider.validateAuthData, "function");
@@ -345,4 +345,70 @@ describe('AuthenticationProviders', function() {
345345
expect(appIds).toEqual(['a', 'b']);
346346
expect(providerOptions).toEqual(options.custom);
347347
});
348+
349+
it('properly loads Facebook accountkit adapter with options', () => {
350+
const options = {
351+
facebookaccountkit: {
352+
appIds: ['a', 'b'],
353+
appSecret: 'secret'
354+
}
355+
};
356+
const {adapter, appIds, providerOptions} = authenticationLoader.loadAuthAdapter('facebookaccountkit', options);
357+
validateAuthenticationAdapter(adapter);
358+
expect(appIds).toEqual(['a', 'b']);
359+
expect(providerOptions.appSecret).toEqual('secret');
360+
});
361+
362+
it('should fail if Facebook appIds is not configured properly', (done) => {
363+
const options = {
364+
facebookaccountkit: {
365+
appIds: []
366+
}
367+
};
368+
const {adapter, appIds} = authenticationLoader.loadAuthAdapter('facebookaccountkit', options);
369+
adapter.validateAppId(appIds)
370+
.then(done.fail, err => {
371+
expect(err.code).toBe(Parse.Error.OBJECT_NOT_FOUND);
372+
done();
373+
})
374+
});
375+
376+
it('should fail to validate Facebook accountkit auth with bad token', (done) => {
377+
const options = {
378+
facebookaccountkit: {
379+
appIds: ['a', 'b']
380+
}
381+
};
382+
const authData = {
383+
id: 'fakeid',
384+
access_token: 'badtoken'
385+
};
386+
const {adapter} = authenticationLoader.loadAuthAdapter('facebookaccountkit', options);
387+
adapter.validateAuthData(authData)
388+
.then(done.fail, err => {
389+
expect(err.code).toBe(190);
390+
expect(err.type).toBe('OAuthException');
391+
done();
392+
})
393+
});
394+
395+
it('should fail to validate Facebook accountkit auth with bad token regardless of app secret proof', (done) => {
396+
const options = {
397+
facebookaccountkit: {
398+
appIds: ['a', 'b'],
399+
appSecret: 'badsecret'
400+
}
401+
};
402+
const authData = {
403+
id: 'fakeid',
404+
access_token: 'badtoken'
405+
};
406+
const {adapter, providerOptions} = authenticationLoader.loadAuthAdapter('facebookaccountkit', options);
407+
adapter.validateAuthData(authData, providerOptions)
408+
.then(done.fail, err => {
409+
expect(err.code).toBe(190);
410+
expect(err.type).toBe('OAuthException');
411+
done();
412+
})
413+
});
348414
});
+75
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
const crypto = require('crypto');
2+
const https = require('https');
3+
const Parse = require('parse/node').Parse;
4+
5+
const graphRequest = (path) => {
6+
return new Promise((resolve, reject) => {
7+
https.get(`https://graph.accountkit.com/v1.1/${path}`, (res) => {
8+
var data = '';
9+
res.on('data', (chunk) => {
10+
data += chunk;
11+
});
12+
res.on('end', () => {
13+
try {
14+
data = JSON.parse(data);
15+
if (data.error) {
16+
// when something wrong with fb graph request (token corrupted etc.)
17+
// instead of network issue
18+
reject(data.error);
19+
} else {
20+
resolve(data);
21+
}
22+
} catch (e) {
23+
reject(e);
24+
}
25+
});
26+
}).on('error', function () {
27+
reject('Failed to validate this access token with Facebook Account Kit.');
28+
});
29+
});
30+
};
31+
32+
function getRequestPath(authData, options) {
33+
const access_token = authData.access_token, appSecret = options && options.appSecret;
34+
if (appSecret) {
35+
const appsecret_proof = crypto.createHmac("sha256", appSecret).update(access_token).digest('hex');
36+
return `me?access_token=${access_token}&appsecret_proof=${appsecret_proof}`
37+
}
38+
return `me?access_token=${access_token}`;
39+
}
40+
41+
function validateAppId(appIds, authData, options) {
42+
if (!appIds.length) {
43+
return Promise.reject(
44+
new Parse.Error(
45+
Parse.Error.OBJECT_NOT_FOUND,
46+
'Facebook app id for Account Kit is not configured.')
47+
)
48+
}
49+
return graphRequest(getRequestPath(authData, options))
50+
.then(data => {
51+
if (data && data.application && appIds.indexOf(data.application.id) != -1) {
52+
return;
53+
}
54+
throw new Parse.Error(
55+
Parse.Error.OBJECT_NOT_FOUND,
56+
'Facebook app id for Account Kit is invalid for this user.');
57+
})
58+
}
59+
60+
function validateAuthData(authData, options) {
61+
return graphRequest(getRequestPath(authData, options))
62+
.then(data => {
63+
if (data && data.id == authData.id) {
64+
return;
65+
}
66+
throw new Parse.Error(
67+
Parse.Error.OBJECT_NOT_FOUND,
68+
'Facebook Account Kit auth is invalid for this user.');
69+
})
70+
}
71+
72+
module.exports = {
73+
validateAppId,
74+
validateAuthData
75+
};

src/Adapters/Auth/index.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import loadAdapter from '../AdapterLoader';
22

33
const facebook = require('./facebook');
4+
const facebookaccountkit = require('./facebookaccountkit');
45
const instagram = require("./instagram");
56
const linkedin = require("./linkedin");
67
const meetup = require("./meetup");
@@ -27,6 +28,7 @@ const anonymous = {
2728

2829
const providers = {
2930
facebook,
31+
facebookaccountkit,
3032
instagram,
3133
linkedin,
3234
meetup,
@@ -43,7 +45,6 @@ const providers = {
4345
wechat,
4446
weibo
4547
}
46-
4748
function authDataValidator(adapter, appIds, options) {
4849
return function(authData) {
4950
return adapter.validateAuthData(authData, options).then(() => {

0 commit comments

Comments
 (0)