Auth Adapters refactoring (#3177)

* Moves all authentication providers to Adapter/Auth

* refactors specs

* Deprecates oauth option in favor of auth option

- Deprecates facebookAppIds option (in favor of auth.facebook.appIds)
- Adds warnings about the deprecated options

* nits
This commit is contained in:
Florent Vilmart
2016-12-06 17:09:43 -05:00
committed by Arthur Cinader
parent a9067260fc
commit c1dcaf1271
28 changed files with 407 additions and 267 deletions

View File

@@ -17,6 +17,7 @@ export function loadAdapter(adapter, defaultAdapter, options) {
}
}
} else if (typeof adapter === "string") {
/* eslint-disable */
adapter = require(adapter);
// If it's define as a module, get the default
if (adapter.default) {

View File

@@ -0,0 +1,22 @@
/*eslint no-unused-vars: "off"*/
export class AuthAdapter {
/*
@param appIds: the specified app ids in the configuration
@param authData: the client provided authData
@returns a promise that resolves if the applicationId is valid
*/
validateAppId(appIds, authData) {
return Promise.resolve({});
}
/*
@param authData: the client provided authData
@param options: additional options
*/
validateAuthData(authData, options) {
return Promise.resolve({});
}
}
export default AuthAdapter;

View File

@@ -0,0 +1,224 @@
var https = require('https'),
crypto = require('crypto');
var OAuth = function(options) {
this.consumer_key = options.consumer_key;
this.consumer_secret = options.consumer_secret;
this.auth_token = options.auth_token;
this.auth_token_secret = options.auth_token_secret;
this.host = options.host;
this.oauth_params = options.oauth_params || {};
};
OAuth.prototype.send = function(method, path, params, body){
var request = this.buildRequest(method, path, params, body);
// Encode the body properly, the current Parse Implementation don't do it properly
return new Promise(function(resolve, reject) {
var httpRequest = https.request(request, function(res) {
var data = '';
res.on('data', function(chunk) {
data += chunk;
});
res.on('end', function() {
data = JSON.parse(data);
resolve(data);
});
}).on('error', function() {
reject('Failed to make an OAuth request');
});
if (request.body) {
httpRequest.write(request.body);
}
httpRequest.end();
});
};
OAuth.prototype.buildRequest = function(method, path, params, body) {
if (path.indexOf("/") != 0) {
path = "/"+path;
}
if (params && Object.keys(params).length > 0) {
path += "?" + OAuth.buildParameterString(params);
}
var request = {
host: this.host,
path: path,
method: method.toUpperCase()
};
var oauth_params = this.oauth_params || {};
oauth_params.oauth_consumer_key = this.consumer_key;
if(this.auth_token){
oauth_params["oauth_token"] = this.auth_token;
}
request = OAuth.signRequest(request, oauth_params, this.consumer_secret, this.auth_token_secret);
if (body && Object.keys(body).length > 0) {
request.body = OAuth.buildParameterString(body);
}
return request;
}
OAuth.prototype.get = function(path, params) {
return this.send("GET", path, params);
}
OAuth.prototype.post = function(path, params, body) {
return this.send("POST", path, params, body);
}
/*
Proper string %escape encoding
*/
OAuth.encode = function(str) {
// discuss at: http://phpjs.org/functions/rawurlencode/
// original by: Brett Zamir (http://brett-zamir.me)
// input by: travc
// input by: Brett Zamir (http://brett-zamir.me)
// input by: Michael Grier
// input by: Ratheous
// bugfixed by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
// bugfixed by: Brett Zamir (http://brett-zamir.me)
// bugfixed by: Joris
// reimplemented by: Brett Zamir (http://brett-zamir.me)
// reimplemented by: Brett Zamir (http://brett-zamir.me)
// note: This reflects PHP 5.3/6.0+ behavior
// note: Please be aware that this function expects to encode into UTF-8 encoded strings, as found on
// note: pages served as UTF-8
// example 1: rawurlencode('Kevin van Zonneveld!');
// returns 1: 'Kevin%20van%20Zonneveld%21'
// example 2: rawurlencode('http://kevin.vanzonneveld.net/');
// returns 2: 'http%3A%2F%2Fkevin.vanzonneveld.net%2F'
// example 3: rawurlencode('http://www.google.nl/search?q=php.js&ie=utf-8&oe=utf-8&aq=t&rls=com.ubuntu:en-US:unofficial&client=firefox-a');
// returns 3: 'http%3A%2F%2Fwww.google.nl%2Fsearch%3Fq%3Dphp.js%26ie%3Dutf-8%26oe%3Dutf-8%26aq%3Dt%26rls%3Dcom.ubuntu%3Aen-US%3Aunofficial%26client%3Dfirefox-a'
str = (str + '')
.toString();
// Tilde should be allowed unescaped in future versions of PHP (as reflected below), but if you want to reflect current
// PHP behavior, you would need to add ".replace(/~/g, '%7E');" to the following.
return encodeURIComponent(str)
.replace(/!/g, '%21')
.replace(/'/g, '%27')
.replace(/\(/g, '%28')
.replace(/\)/g, '%29')
.replace(/\*/g, '%2A');
}
OAuth.signatureMethod = "HMAC-SHA1";
OAuth.version = "1.0";
/*
Generate a nonce
*/
OAuth.nonce = function(){
var text = "";
var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
for(var i=0; i < 30; i++)
text += possible.charAt(Math.floor(Math.random() * possible.length));
return text;
}
OAuth.buildParameterString = function(obj){
// Sort keys and encode values
if (obj) {
var keys = Object.keys(obj).sort();
// Map key=value, join them by &
return keys.map(function(key){
return key + "=" + OAuth.encode(obj[key]);
}).join("&");
}
return "";
}
/*
Build the signature string from the object
*/
OAuth.buildSignatureString = function(method, url, parameters){
return [method.toUpperCase(), OAuth.encode(url), OAuth.encode(parameters)].join("&");
}
/*
Retuns encoded HMAC-SHA1 from key and text
*/
OAuth.signature = function(text, key){
crypto = require("crypto");
return OAuth.encode(crypto.createHmac('sha1', key).update(text).digest('base64'));
}
OAuth.signRequest = function(request, oauth_parameters, consumer_secret, auth_token_secret){
oauth_parameters = oauth_parameters || {};
// Set default values
if (!oauth_parameters.oauth_nonce) {
oauth_parameters.oauth_nonce = OAuth.nonce();
}
if (!oauth_parameters.oauth_timestamp) {
oauth_parameters.oauth_timestamp = Math.floor(new Date().getTime()/1000);
}
if (!oauth_parameters.oauth_signature_method) {
oauth_parameters.oauth_signature_method = OAuth.signatureMethod;
}
if (!oauth_parameters.oauth_version) {
oauth_parameters.oauth_version = OAuth.version;
}
if(!auth_token_secret){
auth_token_secret="";
}
// Force GET method if unset
if (!request.method) {
request.method = "GET"
}
// Collect all the parameters in one signatureParameters object
var signatureParams = {};
var parametersToMerge = [request.params, request.body, oauth_parameters];
for(var i in parametersToMerge) {
var parameters = parametersToMerge[i];
for(var k in parameters) {
signatureParams[k] = parameters[k];
}
}
// Create a string based on the parameters
var parameterString = OAuth.buildParameterString(signatureParams);
// Build the signature string
var url = "https://"+request.host+""+request.path;
var signatureString = OAuth.buildSignatureString(request.method, url, parameterString);
// Hash the signature string
var signatureKey = [OAuth.encode(consumer_secret), OAuth.encode(auth_token_secret)].join("&");
var signature = OAuth.signature(signatureString, signatureKey);
// Set the signature in the params
oauth_parameters.oauth_signature = signature;
if(!request.headers){
request.headers = {};
}
// Set the authorization header
var authHeader = Object.keys(oauth_parameters).sort().map(function(key){
var value = oauth_parameters[key];
return key+'="'+value+'"';
}).join(", ")
request.headers.Authorization = 'OAuth ' + authHeader;
// Set the content type header
request.headers["Content-Type"] = "application/x-www-form-urlencoded";
return request;
}
module.exports = OAuth;

View File

@@ -0,0 +1,58 @@
// Helper functions for accessing the Facebook Graph API.
var https = require('https');
var Parse = require('parse/node').Parse;
// Returns a promise that fulfills iff this user id is valid.
function validateAuthData(authData) {
return graphRequest('me?fields=id&access_token=' + authData.access_token)
.then((data) => {
if (data && data.id == authData.id) {
return;
}
throw new Parse.Error(
Parse.Error.OBJECT_NOT_FOUND,
'Facebook auth is invalid for this user.');
});
}
// Returns a promise that fulfills iff this app id is valid.
function validateAppId(appIds, authData) {
var access_token = authData.access_token;
if (!appIds.length) {
throw new Parse.Error(
Parse.Error.OBJECT_NOT_FOUND,
'Facebook auth is not configured.');
}
return graphRequest('app?access_token=' + access_token)
.then((data) => {
if (data && appIds.indexOf(data.id) != -1) {
return;
}
throw new Parse.Error(
Parse.Error.OBJECT_NOT_FOUND,
'Facebook auth is invalid for this user.');
});
}
// A promisey wrapper for FB graph requests.
function graphRequest(path) {
return new Promise(function(resolve, reject) {
https.get('https://graph.facebook.com/v2.5/' + path, function(res) {
var data = '';
res.on('data', function(chunk) {
data += chunk;
});
res.on('end', function() {
data = JSON.parse(data);
resolve(data);
});
}).on('error', function() {
reject('Failed to validate this access token with Facebook.');
});
});
}
module.exports = {
validateAppId: validateAppId,
validateAuthData: validateAuthData
};

View File

@@ -0,0 +1,51 @@
// Helper functions for accessing the github API.
var https = require('https');
var Parse = require('parse/node').Parse;
// Returns a promise that fulfills iff this user id is valid.
function validateAuthData(authData) {
return request('user', authData.access_token)
.then((data) => {
if (data && data.id == authData.id) {
return;
}
throw new Parse.Error(
Parse.Error.OBJECT_NOT_FOUND,
'Github auth is invalid for this user.');
});
}
// Returns a promise that fulfills iff this app id is valid.
function validateAppId() {
return Promise.resolve();
}
// A promisey wrapper for api requests
function request(path, access_token) {
return new Promise(function(resolve, reject) {
https.get({
host: 'api.github.com',
path: '/' + path,
headers: {
'Authorization': 'bearer '+access_token,
'User-Agent': 'parse-server'
}
}, function(res) {
var data = '';
res.on('data', function(chunk) {
data += chunk;
});
res.on('end', function() {
data = JSON.parse(data);
resolve(data);
});
}).on('error', function() {
reject('Failed to validate this access token with Github.');
});
});
}
module.exports = {
validateAppId: validateAppId,
validateAuthData: validateAuthData
};

View File

@@ -0,0 +1,70 @@
// Helper functions for accessing the google API.
var https = require('https');
var Parse = require('parse/node').Parse;
function validateIdToken(id, token) {
return request("tokeninfo?id_token="+token)
.then((response) => {
if (response && (response.sub == id || response.user_id == id)) {
return;
}
throw new Parse.Error(
Parse.Error.OBJECT_NOT_FOUND,
'Google auth is invalid for this user.');
});
}
function validateAuthToken(id, token) {
return request("tokeninfo?access_token="+token)
.then((response) => {
if (response && (response.sub == id || response.user_id == id)) {
return;
}
throw new Parse.Error(
Parse.Error.OBJECT_NOT_FOUND,
'Google auth is invalid for this user.');
});
}
// Returns a promise that fulfills if this user id is valid.
function validateAuthData(authData) {
if (authData.id_token) {
return validateIdToken(authData.id, authData.id_token);
} else {
return validateAuthToken(authData.id, authData.access_token).then(() => {
// Validation with auth token worked
return;
}, () => {
// Try with the id_token param
return validateIdToken(authData.id, authData.access_token);
});
}
}
// Returns a promise that fulfills if this app id is valid.
function validateAppId() {
return Promise.resolve();
}
// A promisey wrapper for api requests
function request(path) {
return new Promise(function(resolve, reject) {
https.get("https://www.googleapis.com/oauth2/v3/" + path, function(res) {
var data = '';
res.on('data', function(chunk) {
data += chunk;
});
res.on('end', function() {
data = JSON.parse(data);
resolve(data);
});
}).on('error', function() {
reject('Failed to validate this access token with Google.');
});
});
}
module.exports = {
validateAppId: validateAppId,
validateAuthData: validateAuthData
};

99
src/Adapters/Auth/index.js Executable file
View File

@@ -0,0 +1,99 @@
import loadAdapter from '../AdapterLoader';
const facebook = require('./facebook');
const instagram = require("./instagram");
const linkedin = require("./linkedin");
const meetup = require("./meetup");
const google = require("./google");
const github = require("./github");
const twitter = require("./twitter");
const spotify = require("./spotify");
const digits = require("./twitter"); // digits tokens are validated by twitter
const janrainengage = require("./janrainengage");
const janraincapture = require("./janraincapture");
const vkontakte = require("./vkontakte");
const qq = require("./qq");
const wechat = require("./wechat");
const weibo = require("./weibo");
const anonymous = {
validateAuthData: () => {
return Promise.resolve();
},
validateAppId: () => {
return Promise.resolve();
}
}
const providers = {
facebook,
instagram,
linkedin,
meetup,
google,
github,
twitter,
spotify,
anonymous,
digits,
janrainengage,
janraincapture,
vkontakte,
qq,
wechat,
weibo
}
function authDataValidator(adapter, appIds, options) {
return function(authData) {
return adapter.validateAuthData(authData, options).then(() => {
if (appIds) {
return adapter.validateAppId(appIds, authData, options);
}
return Promise.resolve();
});
}
}
module.exports = function(authOptions = {}, enableAnonymousUsers = true) {
let _enableAnonymousUsers = enableAnonymousUsers;
let setEnableAnonymousUsers = function(enable) {
_enableAnonymousUsers = enable;
}
// To handle the test cases on configuration
let getValidatorForProvider = function(provider) {
if (provider === 'anonymous' && !_enableAnonymousUsers) {
return;
}
const defaultAdapter = providers[provider];
let adapter = defaultAdapter;
const providerOptions = authOptions[provider];
if (!defaultAdapter && !providerOptions) {
return;
}
const appIds = providerOptions ? providerOptions.appIds : undefined;
// Try the configuration methods
if (providerOptions) {
const optionalAdapter = loadAdapter(providerOptions, undefined, providerOptions);
if (optionalAdapter) {
adapter = optionalAdapter;
}
}
if (!adapter.validateAuthData || !adapter.validateAppId) {
return;
}
return authDataValidator(adapter, appIds, providerOptions);
}
return Object.freeze({
getValidatorForProvider,
setEnableAnonymousUsers,
})
}

View File

@@ -0,0 +1,44 @@
// Helper functions for accessing the instagram API.
var https = require('https');
var Parse = require('parse/node').Parse;
// Returns a promise that fulfills iff this user id is valid.
function validateAuthData(authData) {
return request("users/self/?access_token="+authData.access_token)
.then((response) => {
if (response && response.data && response.data.id == authData.id) {
return;
}
throw new Parse.Error(
Parse.Error.OBJECT_NOT_FOUND,
'Instagram auth is invalid for this user.');
});
}
// Returns a promise that fulfills iff this app id is valid.
function validateAppId() {
return Promise.resolve();
}
// A promisey wrapper for api requests
function request(path) {
return new Promise(function(resolve, reject) {
https.get("https://api.instagram.com/v1/" + path, function(res) {
var data = '';
res.on('data', function(chunk) {
data += chunk;
});
res.on('end', function() {
data = JSON.parse(data);
resolve(data);
});
}).on('error', function() {
reject('Failed to validate this access token with Instagram.');
});
});
}
module.exports = {
validateAppId: validateAppId,
validateAuthData: validateAuthData
};

View File

@@ -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() {
reject('Failed to validate this access token with Janrain capture.');
});
});
}
module.exports = {
validateAppId: validateAppId,
validateAuthData: validateAuthData
};

View File

@@ -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) {
// 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
};

View File

@@ -0,0 +1,57 @@
// Helper functions for accessing the linkedin API.
var https = require('https');
var Parse = require('parse/node').Parse;
// Returns a promise that fulfills iff this user id is valid.
function validateAuthData(authData) {
return request('people/~:(id)', authData.access_token, authData.is_mobile_sdk)
.then((data) => {
if (data && data.id == authData.id) {
return;
}
throw new Parse.Error(
Parse.Error.OBJECT_NOT_FOUND,
'Linkedin auth is invalid for this user.');
});
}
// Returns a promise that fulfills iff this app id is valid.
function validateAppId() {
return Promise.resolve();
}
// A promisey wrapper for api requests
function request(path, access_token, is_mobile_sdk) {
var headers = {
'Authorization': 'Bearer ' + access_token,
'x-li-format': 'json',
}
if(is_mobile_sdk) {
headers['x-li-src'] = 'msdk';
}
return new Promise(function(resolve, reject) {
https.get({
host: 'api.linkedin.com',
path: '/v1/' + path,
headers: headers
}, function(res) {
var data = '';
res.on('data', function(chunk) {
data += chunk;
});
res.on('end', function() {
data = JSON.parse(data);
resolve(data);
});
}).on('error', function() {
reject('Failed to validate this access token with Linkedin.');
});
});
}
module.exports = {
validateAppId: validateAppId,
validateAuthData: validateAuthData
};

View File

@@ -0,0 +1,50 @@
// Helper functions for accessing the meetup API.
var https = require('https');
var Parse = require('parse/node').Parse;
// Returns a promise that fulfills iff this user id is valid.
function validateAuthData(authData) {
return request('member/self', authData.access_token)
.then((data) => {
if (data && data.id == authData.id) {
return;
}
throw new Parse.Error(
Parse.Error.OBJECT_NOT_FOUND,
'Meetup auth is invalid for this user.');
});
}
// Returns a promise that fulfills iff this app id is valid.
function validateAppId() {
return Promise.resolve();
}
// A promisey wrapper for api requests
function request(path, access_token) {
return new Promise(function(resolve, reject) {
https.get({
host: 'api.meetup.com',
path: '/2/' + path,
headers: {
'Authorization': 'bearer '+access_token
}
}, function(res) {
var data = '';
res.on('data', function(chunk) {
data += chunk;
});
res.on('end', function() {
data = JSON.parse(data);
resolve(data);
});
}).on('error', function() {
reject('Failed to validate this access token with Meetup.');
});
});
}
module.exports = {
validateAppId: validateAppId,
validateAuthData: validateAuthData
};

47
src/Adapters/Auth/qq.js Normal file
View File

@@ -0,0 +1,47 @@
// Helper functions for accessing the qq Graph API.
var https = require('https');
var Parse = require('parse/node').Parse;
// Returns a promise that fulfills iff this user id is valid.
function validateAuthData(authData) {
return graphRequest('me?access_token=' + authData.access_token).then(function (data) {
if (data && data.openid == authData.id) {
return;
}
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'qq auth is invalid for this user.');
});
}
// Returns a promise that fulfills if this app id is valid.
function validateAppId() {
return Promise.resolve();
}
// A promisey wrapper for qq graph requests.
function graphRequest(path) {
return new Promise(function (resolve, reject) {
https.get('https://graph.qq.com/oauth2.0/' + path, function (res) {
var data = '';
res.on('data', function (chunk) {
data += chunk;
});
res.on('end', function () {
var starPos=data.indexOf("(");
var endPos=data.indexOf(")");
if(starPos==-1||endPos==-1){
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'qq auth is invalid for this user.');
}
data=data.substring(starPos+1,endPos-1);
data = JSON.parse(data);
resolve(data);
});
}).on('error', function () {
reject('Failed to validate this access token with qq.');
});
});
}
module.exports = {
validateAppId,
validateAuthData
};

View File

@@ -0,0 +1,64 @@
// Helper functions for accessing the Spotify API.
var https = require('https');
var Parse = require('parse/node').Parse;
// Returns a promise that fulfills iff this user id is valid.
function validateAuthData(authData) {
return request('me', authData.access_token)
.then((data) => {
if (data && data.id == authData.id) {
return;
}
throw new Parse.Error(
Parse.Error.OBJECT_NOT_FOUND,
'Spotify auth is invalid for this user.');
});
}
// Returns a promise that fulfills if this app id is valid.
function validateAppId(appIds, authData) {
var access_token = authData.access_token;
if (!appIds.length) {
throw new Parse.Error(
Parse.Error.OBJECT_NOT_FOUND,
'Spotify auth is not configured.');
}
return request('me', access_token)
.then((data) => {
if (data && appIds.indexOf(data.id) != -1) {
return;
}
throw new Parse.Error(
Parse.Error.OBJECT_NOT_FOUND,
'Spotify auth is invalid for this user.');
});
}
// A promisey wrapper for Spotify API requests.
function request(path, access_token) {
return new Promise(function(resolve, reject) {
https.get({
host: 'api.spotify.com',
path: '/v1/' + path,
headers: {
'Authorization': 'Bearer '+access_token
}
}, function(res) {
var data = '';
res.on('data', function(chunk) {
data += chunk;
});
res.on('end', function() {
data = JSON.parse(data);
resolve(data);
});
}).on('error', function() {
reject('Failed to validate this access token with Spotify.');
});
});
}
module.exports = {
validateAppId: validateAppId,
validateAuthData: validateAuthData
};

View File

@@ -0,0 +1,53 @@
// Helper functions for accessing the twitter API.
var OAuth = require('./OAuth1Client');
var Parse = require('parse/node').Parse;
var logger = require('../../logger').default;
// Returns a promise that fulfills iff this user id is valid.
function validateAuthData(authData, options) {
options = handleMultipleConfigurations(authData, options);
var client = new OAuth(options);
client.host = "api.twitter.com";
client.auth_token = authData.auth_token;
client.auth_token_secret = authData.auth_token_secret;
return client.get("/1.1/account/verify_credentials.json").then((data) => {
if (data && data.id_str == ''+authData.id) {
return;
}
throw new Parse.Error(
Parse.Error.OBJECT_NOT_FOUND,
'Twitter auth is invalid for this user.');
});
}
// Returns a promise that fulfills iff this app id is valid.
function validateAppId() {
return Promise.resolve();
}
function handleMultipleConfigurations(authData, options) {
if (Array.isArray(options)) {
let consumer_key = authData.consumer_key;
if (!consumer_key) {
logger.error('Twitter Auth', 'Multiple twitter configurations are available, by no consumer_key was sent by the client.');
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Twitter auth is invalid for this user.');
}
options = options.filter((option) => {
return option.consumer_key == consumer_key;
});
if (options.length == 0) {
logger.error('Twitter Auth','Cannot find a configuration for the provided consumer_key');
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Twitter auth is invalid for this user.');
}
options = options[0];
}
return options;
}
module.exports = {
validateAppId,
validateAuthData,
handleMultipleConfigurations
};

View File

@@ -0,0 +1,62 @@
'use strict';
// Helper functions for accessing the vkontakte API.
var https = require('https');
var Parse = require('parse/node').Parse;
var logger = require('../../logger').default;
// Returns a promise that fulfills iff this user id is valid.
function validateAuthData(authData, params) {
return vkOAuth2Request(params).then(function (response) {
if (response && response && response.access_token) {
return request("api.vk.com", "method/secure.checkToken?token=" + authData.access_token + "&client_secret=" + params.appSecret + "&access_token=" + response.access_token).then(function (response) {
if (response && response.response && response.response.user_id == authData.id) {
return;
}
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Vk auth is invalid for this user.');
});
}
logger.error('Vk Auth', 'Vk appIds or appSecret is incorrect.');
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Vk appIds or appSecret is incorrect.');
});
}
function vkOAuth2Request(params) {
var promise = new Parse.Promise();
return promise.then(function(){
if (!params || !params.appIds || !params.appIds.length || !params.appSecret || !params.appSecret.length) {
logger.error('Vk Auth', 'Vk auth is not configured. Missing appIds or appSecret.');
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Vk auth is not configured. Missing appIds or appSecret.');
}
return request("oauth.vk.com", "access_token?client_id=" + params.appIds + "&client_secret=" + params.appSecret + "&v=5.59&grant_type=client_credentials")
})
}
// Returns a promise that fulfills iff this app id is valid.
function validateAppId() {
return Promise.resolve();
}
// A promisey wrapper for api requests
function request(host, path) {
return new Promise(function (resolve, reject) {
https.get("https://" + host + "/" + path, function (res) {
var data = '';
res.on('data', function (chunk) {
data += chunk;
});
res.on('end', function () {
data = JSON.parse(data);
resolve(data);
});
}).on('error', function () {
reject('Failed to validate this access token with Vk.');
});
});
}
module.exports = {
validateAppId: validateAppId,
validateAuthData: validateAuthData
};

View File

@@ -0,0 +1,41 @@
// Helper functions for accessing the WeChat Graph API.
var https = require('https');
var Parse = require('parse/node').Parse;
// Returns a promise that fulfills iff this user id is valid.
function validateAuthData(authData) {
return graphRequest('auth?access_token=' + authData.access_token +'&openid=' +authData.id).then(function (data) {
if (data.errcode == 0) {
return;
}
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'qq auth is invalid for this user.');
});
}
// Returns a promise that fulfills if this app id is valid.
function validateAppId() {
return Promise.resolve();
}
// A promisey wrapper for WeChat graph requests.
function graphRequest(path) {
return new Promise(function (resolve, reject) {
https.get('https://api.weixin.qq.com/sns/' + path, function (res) {
var data = '';
res.on('data', function (chunk) {
data += chunk;
});
res.on('end', function () {
data = JSON.parse(data);
resolve(data);
});
}).on('error', function () {
reject('Failed to validate this access token with weixin.');
});
});
}
module.exports = {
validateAppId,
validateAuthData
};

View File

@@ -0,0 +1,60 @@
// Helper functions for accessing the weibo Graph 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) {
return graphRequest(authData.access_token).then(function (data) {
if (data && data.uid == authData.id) {
return;
}
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'weibo auth is invalid for this user.');
});
}
// Returns a promise that fulfills if this app id is valid.
function validateAppId() {
return Promise.resolve();
}
// A promisey wrapper for weibo graph requests.
function graphRequest(access_token) {
return new Promise(function (resolve, reject) {
var postData = querystring.stringify({
"access_token":access_token
});
var options = {
hostname: 'api.weibo.com',
path: '/oauth2/get_token_info',
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Content-Length': Buffer.byteLength(postData)
}
};
var req = https.request(options, function(res){
var data = '';
res.on('data', function (chunk) {
data += chunk;
});
res.on('end', function () {
data = JSON.parse(data);
resolve(data);
});
res.on('error', function () {
reject('Failed to validate this access token with weibo.');
});
});
req.on('error', function () {
reject('Failed to validate this access token with weibo.');
});
req.write(postData);
req.end();
});
}
module.exports = {
validateAppId,
validateAuthData
};