Skip to content

Integrate auth adapter for Facebook accountkit login #4434

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Feb 23, 2018
Merged
68 changes: 67 additions & 1 deletion spec/AuthenticationAdapters.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ var authenticationLoader = require('../src/Adapters/Auth');
var path = require('path');

describe('AuthenticationProviders', function() {
["facebook", "github", "instagram", "google", "linkedin", "meetup", "twitter", "janrainengage", "janraincapture", "vkontakte"].map(function(providerName){
["facebook", "facebookaccountkit", "github", "instagram", "google", "linkedin", "meetup", "twitter", "janrainengage", "janraincapture", "vkontakte"].map(function(providerName){
it("Should validate structure of " + providerName, (done) => {
var provider = require("../src/Adapters/Auth/" + providerName);
jequal(typeof provider.validateAuthData, "function");
Expand Down Expand Up @@ -345,4 +345,70 @@ describe('AuthenticationProviders', function() {
expect(appIds).toEqual(['a', 'b']);
expect(providerOptions).toEqual(options.custom);
});

it('properly loads Facebook accountkit adapter with options', () => {
const options = {
facebookaccountkit: {
appIds: ['a', 'b'],
appSecret: 'secret'
}
};
const {adapter, appIds, providerOptions} = authenticationLoader.loadAuthAdapter('facebookaccountkit', options);
validateAuthenticationAdapter(adapter);
expect(appIds).toEqual(['a', 'b']);
expect(providerOptions.appSecret).toEqual('secret');
});

it('should fail if Facebook appIds is not configured properly', (done) => {
const options = {
facebookaccountkit: {
appIds: []
}
};
const {adapter, appIds} = authenticationLoader.loadAuthAdapter('facebookaccountkit', options);
adapter.validateAppId(appIds)
.then(done.fail, err => {
expect(err.code).toBe(Parse.Error.OBJECT_NOT_FOUND);
done();
})
});

it('should fail to validate Facebook accountkit auth with bad token', (done) => {
const options = {
facebookaccountkit: {
appIds: ['a', 'b']
}
};
const authData = {
id: 'fakeid',
access_token: 'badtoken'
};
const {adapter} = authenticationLoader.loadAuthAdapter('facebookaccountkit', options);
adapter.validateAuthData(authData)
.then(done.fail, err => {
expect(err.code).toBe(190);
expect(err.type).toBe('OAuthException');
done();
})
});

it('should fail to validate Facebook accountkit auth with bad token regardless of app secret proof', (done) => {
const options = {
facebookaccountkit: {
appIds: ['a', 'b'],
appSecret: 'badsecret'
}
};
const authData = {
id: 'fakeid',
access_token: 'badtoken'
};
const {adapter, providerOptions} = authenticationLoader.loadAuthAdapter('facebookaccountkit', options);
adapter.validateAuthData(authData, providerOptions)
.then(done.fail, err => {
expect(err.code).toBe(190);
expect(err.type).toBe('OAuthException');
done();
})
});
});
75 changes: 75 additions & 0 deletions src/Adapters/Auth/facebookaccountkit.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
const crypto = require('crypto');
const https = require('https');
const Parse = require('parse/node').Parse;

const graphRequest = (path) => {
return new Promise((resolve, reject) => {
https.get(`https://graph.accountkit.com/v1.1/${path}`, (res) => {
var data = '';
res.on('data', (chunk) => {
data += chunk;
});
res.on('end', () => {
try {
data = JSON.parse(data);
if (data.error) {
// when something wrong with fb graph request (token corrupted etc.)
// instead of network issue
reject(data.error);
} else {
resolve(data);
}
} catch (e) {
reject(e);
}
});
}).on('error', function () {
reject('Failed to validate this access token with Facebook Account Kit.');
});
});
};

function getRequestPath(authData, options) {
const access_token = authData.access_token, appSecret = options && options.appSecret;
if (appSecret) {
const appsecret_proof = crypto.createHmac("sha256", appSecret).update(access_token).digest('hex');
return `me?access_token=${access_token}&appsecret_proof=${appsecret_proof}`
}
return `me?access_token=${access_token}`;
}

function validateAppId(appIds, authData, options) {
if (!appIds.length) {
return Promise.reject(
new Parse.Error(
Parse.Error.OBJECT_NOT_FOUND,
'Facebook app id for Account Kit is not configured.')
)
}
return graphRequest(getRequestPath(authData, options))
.then(data => {
if (data && data.application && appIds.indexOf(data.application.id) != -1) {
return;
}
throw new Parse.Error(
Parse.Error.OBJECT_NOT_FOUND,
'Facebook app id for Account Kit is invalid for this user.');
})
}

function validateAuthData(authData, options) {
return graphRequest(getRequestPath(authData, options))
.then(data => {
if (data && data.id == authData.id) {
return;
}
throw new Parse.Error(
Parse.Error.OBJECT_NOT_FOUND,
'Facebook Account Kit auth is invalid for this user.');
})
}

module.exports = {
validateAppId,
validateAuthData
};
3 changes: 2 additions & 1 deletion src/Adapters/Auth/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import loadAdapter from '../AdapterLoader';

const facebook = require('./facebook');
const facebookaccountkit = require('./facebookaccountkit');
const instagram = require("./instagram");
const linkedin = require("./linkedin");
const meetup = require("./meetup");
Expand All @@ -27,6 +28,7 @@ const anonymous = {

const providers = {
facebook,
facebookaccountkit,
instagram,
linkedin,
meetup,
Expand All @@ -43,7 +45,6 @@ const providers = {
wechat,
weibo
}

function authDataValidator(adapter, appIds, options) {
return function(authData) {
return adapter.validateAuthData(authData, options).then(() => {
Expand Down