From 80de86c8ae41982bd948ac4c7826fe11d546c55b Mon Sep 17 00:00:00 2001 From: Andrew Lane Date: Fri, 12 Aug 2016 13:34:24 -0400 Subject: [PATCH] Implement Janrain Capture and Janrain Engage auth provider (#2436) * Janrain engage auth provider * Modeled after the existing providers in /src/authDataManager/ and also after https://github.com/janrain/Janrain-Sample-Code/blob/master/widget-examples/server.js * See also: http://developers.janrain.com/overview/social-login/identity-providers/user-profile-data/#normalized-user-profile-data * Janrain capture auth provider * Modeled after the existing providers in /src/authDataManager/ * See also: https://docs.janrain.com/api/registration/entity/#entity * Janrain engage auth provider * Modeled after the existing providers in /src/authDataManager/ and also after https://github.com/janrain/Janrain-Sample-Code/blob/master/widget-examples/server.js * See also: http://developers.janrain.com/overview/social-login/identity-providers/user-profile-data/#normalized-user-profile-data * Janrain capture auth provider * Modeled after the existing providers in /src/authDataManager/ * See also: https://docs.janrain.com/api/registration/entity/#entity * Adding missing newlines at EOF --- spec/OAuth.spec.js | 2 +- src/authDataManager/index.js | 6 ++- src/authDataManager/janraincapture.js | 54 +++++++++++++++++++++ src/authDataManager/janrainengage.js | 67 +++++++++++++++++++++++++++ 4 files changed, 127 insertions(+), 2 deletions(-) create mode 100644 src/authDataManager/janraincapture.js create mode 100644 src/authDataManager/janrainengage.js diff --git a/spec/OAuth.spec.js b/spec/OAuth.spec.js index 60cb7d91..01e714e3 100644 --- a/spec/OAuth.spec.js +++ b/spec/OAuth.spec.js @@ -137,7 +137,7 @@ describe('OAuth', function() { }) }); - ["facebook", "github", "instagram", "google", "linkedin", "meetup", "twitter"].map(function(providerName){ + ["facebook", "github", "instagram", "google", "linkedin", "meetup", "twitter", "janrainengage", "janraincapture"].map(function(providerName){ it("Should validate structure of "+providerName, (done) => { var provider = require("../src/authDataManager/"+providerName); jequal(typeof provider.validateAuthData, "function"); diff --git a/src/authDataManager/index.js b/src/authDataManager/index.js index 0478ffd7..22c08aaf 100644 --- a/src/authDataManager/index.js +++ b/src/authDataManager/index.js @@ -7,6 +7,8 @@ let github = require("./github"); let twitter = require("./twitter"); let spotify = require("./spotify"); let digits = require("./twitter"); // digits tokens are validated by twitter +let janrainengage = require("./janrainengage"); +let janraincapture = require("./janraincapture"); let anonymous = { validateAuthData: () => { @@ -27,7 +29,9 @@ let providers = { twitter, spotify, anonymous, - digits + digits, + janrainengage, + janraincapture } module.exports = function(oauthOptions = {}, enableAnonymousUsers = true) { diff --git a/src/authDataManager/janraincapture.js b/src/authDataManager/janraincapture.js new file mode 100644 index 00000000..992e62fe --- /dev/null +++ b/src/authDataManager/janraincapture.js @@ -0,0 +1,54 @@ +// Helper functions for accessing the Janrain Capture API. +var https = require('https'); +var Parse = require('parse/node').Parse; +var querystring = require('querystring'); + +// Returns a promise that fulfills iff this user id is valid. +function validateAuthData(authData, options) { + return request(options.janrain_capture_host, authData.access_token) + .then((data) => { + //successful response will have a "stat" (status) of 'ok' and a result node that stores the uuid, because that's all we asked for + //see: https://docs.janrain.com/api/registration/entity/#entity + if (data && data.stat == 'ok' && data.result == authData.id) { + return; + } + throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Janrain capture auth is invalid for this user.'); + }); +} + +// Returns a promise that fulfills iff this app id is valid. +function validateAppId() { + //no-op + return Promise.resolve(); +} + +// A promisey wrapper for api requests +function request(host, access_token) { + + var query_string_data = querystring.stringify({ + 'access_token': access_token, + 'attribute_name': 'uuid' // we only need to pull the uuid for this access token to make sure it matches + }); + + return new Promise(function(resolve, reject) { + https.get({ + host: host, + path: '/entity?' + query_string_data + }, function(res) { + var data = ''; + res.on('data', function(chunk) { + data += chunk; + }); + res.on('end', function () { + resolve(JSON.parse(data)); + }); + }).on('error', function(e) { + reject('Failed to validate this access token with Janrain capture.'); + }); + }); +} + +module.exports = { + validateAppId: validateAppId, + validateAuthData: validateAuthData +}; diff --git a/src/authDataManager/janrainengage.js b/src/authDataManager/janrainengage.js new file mode 100644 index 00000000..5064ccb7 --- /dev/null +++ b/src/authDataManager/janrainengage.js @@ -0,0 +1,67 @@ +// Helper functions for accessing the Janrain Engage API. +var https = require('https'); +var Parse = require('parse/node').Parse; +var querystring = require('querystring'); + +// Returns a promise that fulfills iff this user id is valid. +function validateAuthData(authData, options) { + return request(options.api_key, authData.auth_token) + .then((data) => { + //successful response will have a "stat" (status) of 'ok' and a profile node with an identifier + //see: http://developers.janrain.com/overview/social-login/identity-providers/user-profile-data/#normalized-user-profile-data + if (data && data.stat == 'ok' && data.profile.identifier == authData.id) { + return; + } + throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Janrain engage auth is invalid for this user.'); + }); +} + +// Returns a promise that fulfills iff this app id is valid. +function validateAppId() { + //no-op + return Promise.resolve(); +} + +// A promisey wrapper for api requests +function request(api_key, auth_token) { + + var post_data = querystring.stringify({ + 'token': auth_token, + 'apiKey': api_key, + 'format': 'json' + }); + + var post_options = { + host: 'rpxnow.com', + path: '/api/v2/auth_info', + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + 'Content-Length': post_data.length + } + }; + + return new Promise(function (resolve, reject) { + // Create the post request. + var post_req = https.request(post_options, function (res) { + var data = ''; + res.setEncoding('utf8'); + // Append data as we receive it from the Janrain engage server. + res.on('data', function (d) { + data += d; + }); + // Once we have all the data, we can parse it and return the data we want. + res.on('end', function () { + resolve(JSON.parse(data)); + }); + }); + + post_req.write(post_data); + post_req.end(); + }); +} + +module.exports = { + validateAppId: validateAppId, + validateAuthData: validateAuthData +};