-
-
Notifications
You must be signed in to change notification settings - Fork 4.8k
New Auth Method Added. #4484
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
New Auth Method Added. #4484
Changes from all commits
90e24b9
4fc7e2c
065f2a3
934b7b2
6173067
5feca46
f29099b
4f9cf55
7de2a97
bfd7aec
867dbc5
143c94e
fdfeaa9
18d6e58
10cccc9
76b2be1
faa596a
6026c75
0fee82b
cf74430
f66c4b6
9596b8c
4173c84
9925f3d
0fbeed1
ae1f02c
4848161
f47e438
0e39a0e
c6974cb
a570d72
2f954c1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
'use strict'; | ||
|
||
// Helper functions for accessing the google API. | ||
//var Parse = require('parse/node').Parse; | ||
var crypto = require('crypto'); | ||
var request = require('request'); | ||
var url = require('url'); | ||
|
||
// Returns a promise that fulfills if this user id is valid. | ||
function validateAuthData(authData) | ||
{ | ||
return new Promise(function (resolve, reject) | ||
{ | ||
var identity = { | ||
publicKeyUrl: authData.pKeyUrl, | ||
timestamp: authData.timeStamp, | ||
signature: authData.sig, | ||
salt: authData.salt, | ||
playerId: authData.id, | ||
bundleId: authData.bid | ||
}; | ||
|
||
return verify(identity, function (err, token) | ||
{ | ||
if(err) | ||
return reject('Failed to validate this access token with Game Center.'); | ||
else | ||
return resolve(token); | ||
}); | ||
}); | ||
} | ||
|
||
// Returns a promise that fulfills if this app id is valid. | ||
function validateAppId() { | ||
return Promise.resolve(); | ||
} | ||
|
||
function verifyPublicKeyUrl(publicKeyUrl) { | ||
var parsedUrl = url.parse(publicKeyUrl); | ||
if (parsedUrl.protocol !== 'https:') { | ||
return false; | ||
} | ||
|
||
var hostnameParts = parsedUrl.hostname.split('.'); | ||
var domain = hostnameParts[hostnameParts.length - 2] + "." + hostnameParts[hostnameParts.length - 1]; | ||
if (domain !== 'apple.com') { | ||
return false; | ||
} | ||
|
||
return true; | ||
} | ||
|
||
function convertX509CertToPEM(X509Cert) { | ||
var pemPreFix = '-----BEGIN CERTIFICATE-----\n'; | ||
var pemPostFix = '-----END CERTIFICATE-----'; | ||
|
||
var base64 = X509Cert.toString('base64'); | ||
var certBody = base64.match(new RegExp('.{0,64}', 'g')).join('\n'); | ||
|
||
return pemPreFix + certBody + pemPostFix; | ||
} | ||
|
||
function getAppleCertificate(publicKeyUrl, callback) { | ||
if (!verifyPublicKeyUrl(publicKeyUrl)) { | ||
callback(new Error('Invalid publicKeyUrl'), null); | ||
return; | ||
} | ||
|
||
var options = { | ||
uri: publicKeyUrl, | ||
encoding: null | ||
}; | ||
request.get(options, function (error, response, body) { | ||
if (!error && response.statusCode === 200) { | ||
var cert = convertX509CertToPEM(body); | ||
callback(null, cert); | ||
} else { | ||
callback(error, null); | ||
} | ||
}); | ||
} | ||
|
||
/* jslint bitwise:true */ | ||
function convertTimestampToBigEndian(timestamp) { | ||
// The timestamp parameter in Big-Endian UInt-64 format | ||
var buffer = new Buffer(8); | ||
buffer.fill(0); | ||
|
||
var high = ~~(timestamp / 0xffffffff); // jshint ignore:line | ||
var low = timestamp % (0xffffffff + 0x1); // jshint ignore:line | ||
|
||
buffer.writeUInt32BE(parseInt(high, 10), 0); | ||
buffer.writeUInt32BE(parseInt(low, 10), 4); | ||
|
||
return buffer; | ||
} | ||
/* jslint bitwise:false */ | ||
|
||
function verifySignature(publicKey, idToken) { | ||
var verifier = crypto.createVerify('sha256'); | ||
verifier.update(idToken.playerId, 'utf8'); | ||
verifier.update(idToken.bundleId, 'utf8'); | ||
verifier.update(convertTimestampToBigEndian(idToken.timestamp)); | ||
verifier.update(idToken.salt, 'base64'); | ||
|
||
if (!verifier.verify(publicKey, idToken.signature, 'base64')) { | ||
throw new Error('Invalid Signature'); | ||
} | ||
} | ||
|
||
function verify(idToken, callback) { | ||
getAppleCertificate(idToken.publicKeyUrl, function (err, publicKey) { | ||
if (!err) { | ||
try { | ||
verifySignature(publicKey, idToken); | ||
callback(null, idToken); | ||
} catch (e) { | ||
callback(e, null); | ||
} | ||
} else { | ||
callback(err, null); | ||
} | ||
}); | ||
} | ||
|
||
module.exports = { | ||
validateAppId: validateAppId, | ||
validateAuthData: validateAuthData | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
'use strict'; | ||
|
||
// Helper functions for accessing the google API. | ||
var https = require('https'); | ||
var Parse = require('parse/node').Parse; | ||
var request = require('request'); | ||
|
||
// Returns a promise that fulfills if this user id is valid. | ||
function validateAuthData(authData, authOptions) | ||
{ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. again, keep the curly braces up on the same line. It's inconsistent throughout this file as well if you can amend that. |
||
var postUrl = { | ||
url: 'https://www.googleapis.com/oauth2/v3/token', | ||
method: 'POST', | ||
headers: { | ||
'Content-Type': 'application/x-www-form-urlencoded', | ||
}, | ||
form: { | ||
'client_id': authOptions.client_id, | ||
'client_secret': authOptions.client_secret, | ||
'code': authData.access_token, | ||
'grant_type': 'authorization_code' | ||
} | ||
}; | ||
return exchangeAccessToken(postUrl).then((authRes)=> | ||
{ | ||
if(authRes.error) | ||
{ | ||
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, authRes.error); | ||
} | ||
else | ||
{ | ||
return requestHere("https://www.googleapis.com/games/v1/players/" + authData.id + "?access_token=" + authRes.access_token).then(response => { | ||
if (response && (response.playerId == authData.id)) | ||
return; | ||
else | ||
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Google auth is invalid for this user.'); | ||
}); | ||
} | ||
}).catch(error=>{ | ||
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, error); | ||
}); | ||
} | ||
|
||
// Returns a promise that fulfills if this app id is valid. | ||
function validateAppId() { | ||
return Promise.resolve(); | ||
} | ||
|
||
function exchangeAccessToken(postOptions) | ||
{ | ||
return new Promise(function (resolve, reject) { | ||
request(postOptions, function (error, response, body) | ||
{ | ||
if (!error && response.statusCode == 200) | ||
{ | ||
try { | ||
body = JSON.parse(body); | ||
} catch (e) { | ||
return reject(e); | ||
} | ||
resolve(body); | ||
} | ||
else | ||
reject("Fail to Exchange Access Token for GPGames"); | ||
}); | ||
}); | ||
} | ||
|
||
// A promisey wrapper for api requests | ||
function requestHere(path) { | ||
return new Promise(function (resolve, reject) { | ||
https.get(path, function (res) { | ||
var data = ''; | ||
res.on('data', function (chunk) { | ||
data += chunk; | ||
}); | ||
res.on('end', function () { | ||
try { | ||
data = JSON.parse(data); | ||
} catch (e) { | ||
return reject(e); | ||
} | ||
resolve(data); | ||
}); | ||
}).on('error', function () { | ||
reject('Failed to validate this access token with Google.'); | ||
}); | ||
}); | ||
} | ||
|
||
module.exports = { | ||
validateAppId: validateAppId, | ||
validateAuthData: validateAuthData | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,6 +5,8 @@ const instagram = require("./instagram"); | |
const linkedin = require("./linkedin"); | ||
const meetup = require("./meetup"); | ||
const google = require("./google"); | ||
const gcenter = require("./gcenter"); | ||
const gpgames = require("./gpgames"); | ||
const github = require("./github"); | ||
const twitter = require("./twitter"); | ||
const spotify = require("./spotify"); | ||
|
@@ -15,22 +17,24 @@ const vkontakte = require("./vkontakte"); | |
const qq = require("./qq"); | ||
const wechat = require("./wechat"); | ||
const weibo = require("./weibo"); | ||
|
||
const xiaomi = require("./xiaomi"); | ||
const anonymous = { | ||
validateAuthData: () => { | ||
return Promise.resolve(); | ||
}, | ||
validateAppId: () => { | ||
return Promise.resolve(); | ||
} | ||
} | ||
}; | ||
|
||
const providers = { | ||
facebook, | ||
instagram, | ||
linkedin, | ||
meetup, | ||
google, | ||
gcenter, | ||
gpgames, | ||
github, | ||
twitter, | ||
spotify, | ||
|
@@ -41,18 +45,22 @@ const providers = { | |
vkontakte, | ||
qq, | ||
wechat, | ||
} | ||
weibo, | ||
xiaomi | ||
}; | ||
|
||
function authDataValidator(adapter, appIds, options) { | ||
return function(authData) { | ||
return adapter.validateAuthData(authData, options).then(() => { | ||
if (appIds) { | ||
return function (authData) | ||
{ | ||
return adapter.validateAuthData(authData, options).then(() => | ||
{ | ||
if (appIds) | ||
{ | ||
return adapter.validateAppId(appIds, authData, options); | ||
} | ||
return Promise.resolve(); | ||
}); | ||
} | ||
}; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you revert these style changes? Generally you should avoid unnecessary stylistic changes when adding a feature unless it's part of the change itself (i.e. specifically a stylistic change PR). In this case it's superfluous and adds to what we have to review. It'll make it much easier for us to go through and review your changes if they're only functional changes. |
||
} | ||
|
||
function loadAuthAdapter(provider, authOptions) { | ||
|
@@ -70,7 +78,7 @@ function loadAuthAdapter(provider, authOptions) { | |
if (providerOptions) { | ||
const optionalAdapter = loadAdapter(providerOptions, undefined, providerOptions); | ||
if (optionalAdapter) { | ||
['validateAuthData', 'validateAppId'].forEach((key) => { | ||
['validateAuthData', 'validateAppId'].forEach(key => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here. This can and should be suggested separately. |
||
if (optionalAdapter[key]) { | ||
adapter[key] = optionalAdapter[key]; | ||
} | ||
|
@@ -82,18 +90,26 @@ function loadAuthAdapter(provider, authOptions) { | |
return; | ||
} | ||
|
||
return {adapter, appIds, providerOptions}; | ||
return { adapter, appIds, providerOptions }; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. and here |
||
} | ||
|
||
module.exports = function(authOptions = {}, enableAnonymousUsers = true) { | ||
module.exports = function (authOptions = {}, enableAnonymousUsers = true) | ||
{ | ||
let _enableAnonymousUsers = enableAnonymousUsers; | ||
const setEnableAnonymousUsers = function(enable) { | ||
const setEnableAnonymousUsers = function (enable) | ||
{ | ||
_enableAnonymousUsers = enable; | ||
} | ||
}; | ||
// To handle the test cases on configuration | ||
const getValidatorForProvider = function(provider) { | ||
const getValidatorForProvider = function (provider) | ||
{ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. and above here |
||
if (provider === 'anonymous' && !_enableAnonymousUsers) | ||
return; | ||
|
||
if (provider === 'anonymous' && !_enableAnonymousUsers) { | ||
if(!providers.hasOwnProperty(provider) && | ||
provider !== 'myoauth' && | ||
provider !== 'customAuthentication' && | ||
provider !== 'shortLivedAuth') { | ||
return; | ||
} | ||
|
||
|
@@ -104,12 +120,12 @@ module.exports = function(authOptions = {}, enableAnonymousUsers = true) { | |
} = loadAuthAdapter(provider, authOptions); | ||
|
||
return authDataValidator(adapter, appIds, providerOptions); | ||
} | ||
}; | ||
|
||
return Object.freeze({ | ||
getValidatorForProvider, | ||
setEnableAnonymousUsers | ||
}) | ||
} | ||
}); | ||
}; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not needed for this PR, let's kept it focused on what you're actually introducing. |
||
|
||
module.exports.loadAuthAdapter = loadAuthAdapter; |
Uh oh!
There was an error while loading. Please reload this page.