diff --git a/foursquare/foursquare.html b/foursquare/foursquare.html new file mode 100644 index 00000000..8156f682 --- /dev/null +++ b/foursquare/foursquare.html @@ -0,0 +1,155 @@ + + + + + + + + + diff --git a/swarm/swarm.js b/foursquare/foursquare.js similarity index 56% rename from swarm/swarm.js rename to foursquare/foursquare.js index 6a4c4328..51246ae4 100644 --- a/swarm/swarm.js +++ b/foursquare/foursquare.js @@ -32,12 +32,11 @@ module.exports = function(RED) { ); } - - function SwarmNode(n) { + function FoursquareNode(n) { RED.nodes.createNode(this,n); } - RED.nodes.registerType("swarm-credentials", SwarmNode, { + RED.nodes.registerType("foursquare-credentials", FoursquareNode, { credentials: { displayname: {type: "text"}, clientid: {type: "password"}, @@ -45,113 +44,8 @@ module.exports = function(RED) { accesstoken: {type: "password"} } }); - - /** - * Swarm input node - will return the most recent check-in since - * the node has been initialized. The node will only populate msg.payload - * with the JSON of the check-in if a new check-in has been made within - * the polling interval. - */ - function SwarmInNode(n) { - RED.nodes.createNode(this, n); - var credentials = RED.nodes.getCredentials(n.swarm); - var node = this; - var credentialsOk = checkCredentials(node, credentials); - if (credentialsOk) { - var repeat = 900000; // 15 mins - var now = Math.floor(((new Date()).getTime())/1000); // time now in seconds since epoch - var afterTimestamp = now; - var lastcheckin = null; - - var interval = setInterval(function() { - node.emit("input", {}); - }, repeat ); - - this.on("input", function(msg) { - getCheckinsAfterTimestamp(node, "self", afterTimestamp, credentials, msg, function(msg) { - var latestcheckin = JSON.stringify(msg); - if (latestcheckin != lastcheckin) { - lastcheckin = latestcheckin; - afterTimestamp = msg.payload.createdAt; - node.send(msg); - } - }); - }); - - this.on("close", function() { - if (interval != null) { - clearInterval(interval); - } - }); - - } - } - - RED.nodes.registerType("swarm in", SwarmInNode); - - /** - * Swarm query node - will return the most recent check-in since - * the node has been initialized. If a check-in exists the node will always return - * the most recent even if no new check-ins have happened since the previous query. - * The node only populates msg.payload when a check-in is found. - */ - function SwarmQueryNode(n) { - RED.nodes.createNode(this, n); - var node = this; - this.request = n.request || "get-most-recent-checkin"; - var credentials = RED.nodes.getCredentials(n.swarm); - var credentialsOk = checkCredentials(node, credentials); - if (credentialsOk) { - var now = Math.floor(((new Date()).getTime())/1000); // time now in seconds since epoch (rounded down) - var afterTimestamp = now; - - this.on("input", function(msg) { - if (node.request === "get-most-recent-checkin") { - getCheckinsAfterTimestamp(node, "self", afterTimestamp, credentials, msg, function(msg) { - afterTimestamp = msg.payload.createdAt - 2; - node.send(msg); - }); - } - }); - } - } - - RED.nodes.registerType("swarm", SwarmQueryNode); - - function checkCredentials(node, credentials) { - if (credentials && credentials.clientid && credentials.clientsecret && credentials.accesstoken) { - return true; - } else { - node.error("problem with credentials being set: " + credentials + ", "); - node.status({fill:"red",shape:"ring",text:"failed"}); - return false; - } - } - - function getCheckinsAfterTimestamp(node, userid, afterTimestamp, credentials, msg, callback) { - var apiUrl = "https://api.foursquare.com/v2/users/" + userid + "/checkins?oauth_token=" + credentials.accesstoken + "&v=20141016&afterTimestamp=" + afterTimestamp+"&sort=newestfirst"; - request.get(apiUrl,function(err, httpResponse, body) { - if (err) { - node.error(err.toString()); - node.status({fill:"red",shape:"ring",text:"failed"}); - } else { - var result = JSON.parse(body); - if (result.meta.code != 200) { - node.error("Error code: " + result.meta.code + ", errorDetail: " + result.meta.errorDetail); - node.status({fill:"red",shape:"ring",text:"failed"}); - } else { - if (result.response.checkins.items.length !== 0) { - var latest = result.response.checkins.items[0]; - msg.payload = {}; - msg.payload = latest; - callback(msg); - } - } - } - }); - } - RED.httpAdmin.get('/swarm-credentials/auth', function(req, res){ + RED.httpAdmin.get('/foursquare-credentials/auth', function(req, res){ if (!req.query.clientid || !req.query.clientsecret || !req.query.id || !req.query.callback) { return res.status(400).send('ERROR: request does not contain the required parameters'); } @@ -175,7 +69,7 @@ module.exports = function(RED) { res.redirect(url); }); - RED.httpAdmin.get('/swarm-credentials/auth/callback', function(req, res){ + RED.httpAdmin.get('/foursquare-credentials/auth/callback', function(req, res){ if (req.query.error) { return res.send("ERROR: " + req.query.error + ": " + req.query.error_description); } diff --git a/swarm/icons/swarm.png b/foursquare/icons/swarm.png similarity index 100% rename from swarm/icons/swarm.png rename to foursquare/icons/swarm.png diff --git a/foursquare/swarm.html b/foursquare/swarm.html new file mode 100644 index 00000000..dbf4b353 --- /dev/null +++ b/foursquare/swarm.html @@ -0,0 +1,107 @@ + + + + + + + + + + + + + + + + + + + diff --git a/foursquare/swarm.js b/foursquare/swarm.js new file mode 100644 index 00000000..c9380cd6 --- /dev/null +++ b/foursquare/swarm.js @@ -0,0 +1,130 @@ +/** + * Copyright 2014 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +module.exports = function(RED) { + + "use strict"; + var request = require('request'); + + /** + * Swarm input node - will return the most recent check-in since + * the node has been initialized. The node will only populate msg.payload + * with the JSON of the check-in if a new check-in has been made within + * the polling interval. + */ + function SwarmInNode(n) { + RED.nodes.createNode(this, n); + var credentials = RED.nodes.getCredentials(n.foursquare); + var node = this; + var credentialsOk = checkCredentials(node, credentials); + if (credentialsOk) { + var repeat = 900000; // 15 mins + var now = Math.floor(((new Date()).getTime())/1000); // time now in seconds since epoch + var afterTimestamp = now; + var lastcheckin = null; + + var interval = setInterval(function() { + node.emit("input", {}); + }, repeat ); + + this.on("input", function(msg) { + getCheckinsAfterTimestamp(node, "self", afterTimestamp, credentials, msg, function(msg) { + var latestcheckin = JSON.stringify(msg); + if (latestcheckin != lastcheckin) { + lastcheckin = latestcheckin; + afterTimestamp = msg.payload.createdAt; + node.send(msg); + } + }); + }); + + this.on("close", function() { + if (interval != null) { + clearInterval(interval); + } + }); + + } + } + + RED.nodes.registerType("swarm in", SwarmInNode); + + /** + * Swarm query node - will return the most recent check-in since + * the node has been initialized. If a check-in exists the node will always return + * the most recent even if no new check-ins have happened since the previous query. + * The node only populates msg.payload when a check-in is found. + */ + function SwarmQueryNode(n) { + RED.nodes.createNode(this, n); + var node = this; + this.request = n.request || "get-most-recent-checkin"; + var credentials = RED.nodes.getCredentials(n.foursquare); + var c = RED.nodes.getCredentials(n.swarm); + var credentialsOk = checkCredentials(node, credentials); + if (credentialsOk) { + var now = Math.floor(((new Date()).getTime())/1000); // time now in seconds since epoch (rounded down) + var afterTimestamp = now; + + this.on("input", function(msg) { + if (node.request === "get-most-recent-checkin") { + getCheckinsAfterTimestamp(node, "self", afterTimestamp, credentials, msg, function(msg) { + afterTimestamp = msg.payload.createdAt - 2; + node.send(msg); + }); + } + }); + } + } + + RED.nodes.registerType("swarm", SwarmQueryNode); + + function checkCredentials(node, credentials) { + if (credentials && credentials.clientid && credentials.clientsecret && credentials.accesstoken) { + return true; + } else { + node.error("problem with credentials being set: " + credentials + ", "); + node.status({fill:"red",shape:"ring",text:"failed"}); + return false; + } + } + + function getCheckinsAfterTimestamp(node, userid, afterTimestamp, credentials, msg, callback) { + var apiUrl = "https://api.foursquare.com/v2/users/" + userid + "/checkins?oauth_token=" + credentials.accesstoken + "&v=20141016&afterTimestamp=" + afterTimestamp+"&sort=newestfirst&m=swarm"; + request.get(apiUrl,function(err, httpResponse, body) { + if (err) { + node.error(err.toString()); + node.status({fill:"red",shape:"ring",text:"failed"}); + } else { + var result = JSON.parse(body); + if (result.meta.code != 200) { + node.error("Error code: " + result.meta.code + ", errorDetail: " + result.meta.errorDetail); + node.status({fill:"red",shape:"ring",text:"failed"}); + } else { + if (result.response.checkins.items.length !== 0) { + var latest = result.response.checkins.items[0]; + msg.payload = {}; + msg.payload = latest; + callback(msg); + } + } + } + }); + } + + + +} diff --git a/package.json b/package.json index 86d8934b..52436d2e 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,8 @@ "fitbit": "fitbit/fitbit.js", "flickr": "flickr/flickr.js", "dropbox": "dropbox/dropbox.js", - "swarm":"swarm/swarm.js", + "foursquare":"foursquare/foursquare.js", + "swarm":"foursquare/swarm.js", "instagram" : "instagram/instagram.js", "google": "google/google.js", "googlecalendar": "google/calendar.js" diff --git a/swarm/swarm.html b/swarm/swarm.html deleted file mode 100644 index d257750a..00000000 --- a/swarm/swarm.html +++ /dev/null @@ -1,251 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - diff --git a/test/foursquare/foursquare_spec.js b/test/foursquare/foursquare_spec.js new file mode 100644 index 00000000..c035d575 --- /dev/null +++ b/test/foursquare/foursquare_spec.js @@ -0,0 +1,175 @@ +/** + * Copyright 2014 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +var foursquareNode = require("../../foursquare/foursquare.js"); +var helper = require('../helper.js'); +var nock = helper.nock; + +describe('foursquare nodes', function() { + + before(function(done) { + helper.startServer(done); + }); + + afterEach(function() { + helper.unload(); + }); + + describe('oauth2 authentication', function() { + + if (nock) { + + it('can do oauth dance', function(done) { + helper.load(foursquareNode, + [{id:"n4", type:"foursquare-credentials"}], + function() { + + var scope = nock('https://foursquare.com:443') + .filteringRequestBody(/redirect_uri=[^&]*/g, 'redirect_uri=XXX') + .post('/oauth2/access_token', "redirect_uri=XXX&" + + "grant_type=authorization_code&client_id=abcdefg&client_secret=mnopqrs&code=123456") + .reply(200, {"access_token":"2468abc"}); + + var apiscope = nock('https://api.foursquare.com:443') + .get('/v2/users/self?oauth_token=2468abc&v=20141016') + .reply(200, {"meta":{"code":200},"response":{"user":{"id":"987654321","firstName":"John","lastName":"Smith"}}}); + + helper.request() + .get('/foursquare-credentials/auth?id=n4&clientid=abcdefg&clientsecret=mnopqrs&response_type=code&callback=http://localhost:1880:/foursquare-credentials') + .expect(302) + .end(function(err, res) { + if (err) return done(err); + var state = res.text.split("state=n4%253A"); + helper.request() + .get('/foursquare-credentials/auth/callback?code=123456&state=n4:'+state[1]) + .expect(200) + .end(function(err, res) { + if (err) return done(err); + helper.credentials.get("n4") + .should.have.property('displayname', + 'John Smith'); + done(); + }); + }); + }); + }); + + + it(' fails oauth dance if request is missing required parameter', function(done) { + helper.load(foursquareNode, + [{id:"n4", type:"foursquare-credentials"}], + function() { + helper.request() + .get('/foursquare-credentials/auth?id=n4&clientsecret=mnopqrs&response_type=code&callback=http://localhost:1880:/foursquare-credentials') + .expect(400, 'ERROR: request does not contain the required parameters') + .end(function(err, res) { + done(); + }); + }); + }); + + + it('fails oauth dance if client id is invalid', function(done) { + helper.load(foursquareNode, + [{id:"n4", type:"foursquare-credentials"}], + function() { + var scope = nock('https://foursquare.com:443') + .post('/oauth/access_token') + .reply(401, '{"errors":[{"errorType":"oauth","fieldName":"oauth_consumer_key","message":"Cause of error: Value abcdefg is invalid for consumer key"}],"success":false}'); + helper.request() + .get('/foursquare-credentials/auth?id=n4&clientid=abcdefg&clientsecret=mnopqrs&response_type=code&callback=http://localhost:1880:/foursquare-credentials') + .expect(302) + .end(function(err, res) { + if (err) return done(err); + var state = res.text.split("state=n4%253A"); + helper.request() + .get('/foursquare-credentials/auth/callback?code=123456&state=n4:'+state[1]) + .expect(200) + .end(function(err, res) { + if (err) return done(err); + res.text.should.containEql('Oh no'); + done(); + }); + }); + }); + }); + + it(' fails if profile can\'t be retrieved', function(done) { + helper.load(foursquareNode, + [{id:"n4", type:"foursquare-credentials"}], + function() { + + var scope = nock('https://foursquare.com:443') + .filteringRequestBody(/redirect_uri=[^&]*/g, 'redirect_uri=XXX') + .post('/oauth2/access_token', "redirect_uri=XXX&" + + "grant_type=authorization_code&client_id=abcdefg&client_secret=mnopqrs&code=123456") + .reply(200, {"access_token":"2468abc"}); + + var apiscope = nock('https://api.foursquare.com:443') + .get('/v2/users/self?oauth_token=2468abc&v=20141016') + .reply(401, '{"meta":[{"code":"401"}]}'); + + helper.request() + .get('/foursquare-credentials/auth?id=n4&clientid=abcdefg&clientsecret=mnopqrs&response_type=code&callback=http://localhost:1880:/foursquare-credentials') + .expect(302) + .end(function(err, res) { + if (err) return done(err); + var state = res.text.split("state=n4%253A"); + helper.request() + .get('/foursquare-credentials/auth/callback?code=123456&state=n4:'+state[1]) + .expect(200) + .end(function(err, res) { + if (err) return done(err); + res.text.should.containEql('Http return code'); + done(); + }); + }); + }); + }); + + it(' fails if CSRF token mismatch', function(done) { + helper.load(foursquareNode, + [{id:"n4", type:"foursquare-credentials"}], + function() { + + var scope = nock('https://foursquare.com:443') + .filteringRequestBody(/redirect_uri=[^&]*/g, 'redirect_uri=XXX') + .post('/oauth2/access_token', "redirect_uri=XXX&" + + "grant_type=authorization_code&client_id=abcdefg&client_secret=mnopqrs&code=123456") + .reply(200, {"access_token":"2468abc"}); + + helper.request() + .get('/foursquare-credentials/auth?id=n4&clientid=abcdefg&clientsecret=mnopqrs&response_type=code&callback=http://localhost:1880:/foursquare-credentials') + .expect(302) + .end(function(err, res) { + if (err) return done(err); + helper.request() + .get('/foursquare-credentials/auth/callback?code=123456&state=n4:13579') + .expect(401) + .end(function(err, res) { + if (err) return done(err); + res.text.should.containEql('CSRF token mismatch, possible cross-site request forgery attempt'); + done(); + }); + }); + }); + }); + + } + + }); + +}); diff --git a/test/foursquare/swarm_spec.js b/test/foursquare/swarm_spec.js new file mode 100644 index 00000000..11eae4a2 --- /dev/null +++ b/test/foursquare/swarm_spec.js @@ -0,0 +1,151 @@ +/** + * Copyright 2014 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +var should = require("should"); +var swarmNode = require("../../foursquare/swarm.js"); +var foursquareNode = require("../../foursquare/foursquare.js"); +var helper = require('../helper.js'); +var sinon = require('sinon'); +var nock = helper.nock; + +describe('swarm nodes', function() { + + before(function(done) { + helper.startServer(done); + }); + + afterEach(function() { + helper.unload(); + }); + + describe('query node', function() { + + if (nock) { + + it(' can fetch check-in information', function(done) { + helper.load([foursquareNode, swarmNode], + [ {id:"n1", type:"helper", wires:[["n2"]]}, + {id:"n4", type:"foursquare-credentials"}, + {id:"n2", type:"swarm", foursquare: "n4", wires:[["n3"]]}, + {id:"n3", type:"helper"}], + { + "n4": { + displayname : "John", + clientid: "987654321", + clientsecret:"123456789", + accesstoken:"abcd1234", + }, + }, + function() { + var scope = nock('https://api.foursquare.com:443') + .filteringPath(/afterTimestamp=[^&]*/g, 'afterTimestamp=foo') + .get('/v2/users/self/checkins?oauth_token=abcd1234&v=20141016&afterTimestamp=foo&sort=newestfirst&m=swarm') + .reply(200, {"meta":{"code":200},"response":{"checkins":{"count":1, "items":[{"id":"b695edf5ewc2","createdAt":1412861751,"type":"checkin","timeZoneOffset":60,"venue":{"id":"49a8b774","name":"Bobs House"}}]}}}); + + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + var n3 = helper.getNode("n3"); + n2.should.have.property('id','n2'); + n1.send({payload:"foo"}); + n3.on('input', function(msg){ + var venue = msg.payload.venue; + venue.should.have.property('name', "Bobs House"); + done(); + }); + }); + }); + + it(' fails if error fetching check-in information', function(done) { + helper.load([foursquareNode, swarmNode], + [ {id:"n1", type:"helper", wires:[["n2"]]}, + {id:"n4", type:"foursquare-credentials"}, + {id:"n2", type:"swarm", foursquare: "n4", wires:[["n3"]]}, + {id:"n3", type:"helper"}], + { + "n4": { + displayname : "John", + clientid: "987654321", + clientsecret:"123456789", + accesstoken:"abcd1234", + }, + }, + function() { + var scope = nock('https://api.foursquare.com:443') + .filteringPath(/afterTimestamp=[^&]*/g, 'afterTimestamp=foo') + .get('/v2/users/self/checkins?oauth_token=abcd1234&v=20141016&afterTimestamp=foo&sort=newestfirst&m=swarm') + .reply(200, {"meta":{"code":400, "errorDetail":'test forced failure'}}); + + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + var n3 = helper.getNode("n3"); + n2.should.have.property('id','n2'); + + sinon.stub(n2, 'status', function(status){ + var expected = {fill:"red",shape:"ring",text:"failed"}; + should.deepEqual(status, expected); + done(); + }); + + n1.send({payload:"foo"}); + }); + }); + + }} + ); + + + describe('in node', function() { + + if (nock) { + + it(' can fetch check-in information', function(done) { + helper.load([foursquareNode, swarmNode], + [ {id:"n1", type:"helper", wires:[["n2"]]}, + {id:"n4", type:"foursquare-credentials"}, + {id:"n2", type:"swarm", foursquare: "n4", wires:[["n3"]]}, + {id:"n3", type:"helper"}], + { + "n4": { + displayname : "John", + clientid: "987654321", + clientsecret:"123456789", + accesstoken:"abcd1234", + }, + }, + function() { + var scope = nock('https://api.foursquare.com:443') + .filteringPath(/afterTimestamp=[^&]*/g, 'afterTimestamp=foo') + .get('/v2/users/self/checkins?oauth_token=abcd1234&v=20141016&afterTimestamp=foo&sort=newestfirst&m=swarm') + .reply(200, {"meta":{"code":200},"response":{"checkins":{"count":1, "items":[{"id":"b695edf5ewc2","createdAt":1412861751,"type":"checkin","timeZoneOffset":60,"venue":{"id":"49a8b774","name":"Bobs House"}}]}}}); + + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + var n3 = helper.getNode("n3"); + n2.should.have.property('id','n2'); + n2.emit("input", {}); + n3.on('input', function(msg){ + var venue = msg.payload.venue; + venue.should.have.property('name', "Bobs House"); + done(); + }); + }); + }); + + } + + }); + +}); diff --git a/test/swarm/swarm_spec.js b/test/swarm/swarm_spec.js deleted file mode 100644 index 8f27a661..00000000 --- a/test/swarm/swarm_spec.js +++ /dev/null @@ -1,301 +0,0 @@ -/** - * Copyright 2014 IBM Corp. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - **/ - -var should = require("should"); -var swarmNode = require("../../swarm/swarm.js"); -var helper = require('../helper.js'); -var sinon = require('sinon'); -var nock = helper.nock; - -describe('swarm nodes', function() { - - before(function(done) { - helper.startServer(done); - }); - - afterEach(function() { - helper.unload(); - }); - - describe('query node', function() { - - if (nock) { - - it('can do oauth dance', function(done) { - helper.load(swarmNode, - [ {id:"n1", type:"helper", wires:[["n2"]]}, - {id:"n4", type:"swarm-credentials"}, - {id:"n2", type:"swarm", swarm: "n4", wires:[["n3"]]}, - {id:"n3", type:"helper"}], - function() { - - var scope = nock('https://foursquare.com:443') - .filteringRequestBody(/redirect_uri=[^&]*/g, 'redirect_uri=XXX') - .post('/oauth2/access_token', "redirect_uri=XXX&" + - "grant_type=authorization_code&client_id=abcdefg&client_secret=mnopqrs&code=123456") - .reply(200, {"access_token":"2468abc"}); - - var apiscope = nock('https://api.foursquare.com:443') - .get('/v2/users/self?oauth_token=2468abc&v=20141016') - .reply(200, {"meta":{"code":200},"response":{"user":{"id":"987654321","firstName":"John","lastName":"Smith"}}}); - - helper.request() - .get('/swarm-credentials/auth?id=n4&clientid=abcdefg&clientsecret=mnopqrs&response_type=code&callback=http://localhost:1880:/swarm-credentials') - .expect(302) - .end(function(err, res) { - if (err) return done(err); - var state = res.text.split("state=n4%253A"); - helper.request() - .get('/swarm-credentials/auth/callback?code=123456&state=n4:'+state[1]) - .expect(200) - .end(function(err, res) { - if (err) return done(err); - helper.credentials.get("n4") - .should.have.property('displayname', - 'John Smith'); - done(); - }); - }); - }); - }); - - it(' fails oauth dance if request is missing required parameter', function(done) { - helper.load(swarmNode, - [ {id:"n1", type:"helper", wires:[["n2"]]}, - {id:"n4", type:"swarm-credentials"}, - {id:"n2", type:"swarm", swarm: "n4", wires:[["n3"]]}, - {id:"n3", type:"helper"}], - function() { - helper.request() - .get('/swarm-credentials/auth?id=n4&clientsecret=mnopqrs&response_type=code&callback=http://localhost:1880:/swarm-credentials') - .expect(400, 'ERROR: request does not contain the required parameters') - .end(function(err, res) { - done(); - }); - }); - }); - - - it('fails oauth dance if client id is invalid', function(done) { - helper.load(swarmNode, - [ {id:"n1", type:"helper", wires:[["n2"]]}, - {id:"n4", type:"swarm-credentials"}, - {id:"n2", type:"swarm", swarm: "n4", wires:[["n3"]]}, - {id:"n3", type:"helper"}], - function() { - var scope = nock('https://foursquare.com:443') - .post('/oauth/access_token') - .reply(401, '{"errors":[{"errorType":"oauth","fieldName":"oauth_consumer_key","message":"Cause of error: Value abcdefg is invalid for consumer key"}],"success":false}'); - helper.request() - .get('/swarm-credentials/auth?id=n4&clientid=abcdefg&clientsecret=mnopqrs&response_type=code&callback=http://localhost:1880:/swarm-credentials') - .expect(302) - .end(function(err, res) { - if (err) return done(err); - var state = res.text.split("state=n4%253A"); - helper.request() - .get('/swarm-credentials/auth/callback?code=123456&state=n4:'+state[1]) - .expect(200) - .end(function(err, res) { - if (err) return done(err); - res.text.should.containEql('Oh no'); - done(); - }); - }); - }); - }); - - it(' fails if profile can\'t be retrieved', function(done) { - helper.load(swarmNode, - [ {id:"n1", type:"helper", wires:[["n2"]]}, - {id:"n4", type:"swarm-credentials"}, - {id:"n2", type:"swarm", swarm: "n4", wires:[["n3"]]}, - {id:"n3", type:"helper"}], - function() { - - var scope = nock('https://foursquare.com:443') - .filteringRequestBody(/redirect_uri=[^&]*/g, 'redirect_uri=XXX') - .post('/oauth2/access_token', "redirect_uri=XXX&" + - "grant_type=authorization_code&client_id=abcdefg&client_secret=mnopqrs&code=123456") - .reply(200, {"access_token":"2468abc"}); - - var apiscope = nock('https://api.foursquare.com:443') - .get('/v2/users/self?oauth_token=2468abc&v=20141016') - .reply(401, '{"meta":[{"code":"401"}]}'); - - helper.request() - .get('/swarm-credentials/auth?id=n4&clientid=abcdefg&clientsecret=mnopqrs&response_type=code&callback=http://localhost:1880:/swarm-credentials') - .expect(302) - .end(function(err, res) { - if (err) return done(err); - var state = res.text.split("state=n4%253A"); - helper.request() - .get('/swarm-credentials/auth/callback?code=123456&state=n4:'+state[1]) - .expect(200) - .end(function(err, res) { - if (err) return done(err); - res.text.should.containEql('Http return code'); - done(); - }); - }); - }); - }); - - it(' fails if CSRF token mismatch', function(done) { - helper.load(swarmNode, - [ {id:"n1", type:"helper", wires:[["n2"]]}, - {id:"n4", type:"swarm-credentials"}, - {id:"n2", type:"swarm", swarm: "n4", wires:[["n3"]]}, - {id:"n3", type:"helper"}], - function() { - - var scope = nock('https://foursquare.com:443') - .filteringRequestBody(/redirect_uri=[^&]*/g, 'redirect_uri=XXX') - .post('/oauth2/access_token', "redirect_uri=XXX&" + - "grant_type=authorization_code&client_id=abcdefg&client_secret=mnopqrs&code=123456") - .reply(200, {"access_token":"2468abc"}); - - helper.request() - .get('/swarm-credentials/auth?id=n4&clientid=abcdefg&clientsecret=mnopqrs&response_type=code&callback=http://localhost:1880:/swarm-credentials') - .expect(302) - .end(function(err, res) { - if (err) return done(err); - helper.request() - .get('/swarm-credentials/auth/callback?code=123456&state=n4:13579') - .expect(401) - .end(function(err, res) { - if (err) return done(err); - res.text.should.containEql('CSRF token mismatch, possible cross-site request forgery attempt'); - done(); - }); - }); - }); - }); - - it(' can fetch check-in information', function(done) { - helper.load(swarmNode, - [ {id:"n1", type:"helper", wires:[["n2"]]}, - {id:"n4", type:"swarm-credentials"}, - {id:"n2", type:"swarm", swarm: "n4", wires:[["n3"]]}, - {id:"n3", type:"helper"}], - { - "n4": { - displayname : "John", - clientid: "987654321", - clientsecret:"123456789", - accesstoken:"abcd1234", - }, - }, - function() { - var scope = nock('https://api.foursquare.com:443') - .filteringPath(/afterTimestamp=[^&]*/g, 'afterTimestamp=foo') - .get('/v2/users/self/checkins?oauth_token=abcd1234&v=20141016&afterTimestamp=foo&sort=newestfirst') - .reply(200, {"meta":{"code":200},"response":{"checkins":{"count":1, "items":[{"id":"b695edf5ewc2","createdAt":1412861751,"type":"checkin","timeZoneOffset":60,"venue":{"id":"49a8b774","name":"Bobs House"}}]}}}); - - var n1 = helper.getNode("n1"); - var n2 = helper.getNode("n2"); - var n3 = helper.getNode("n3"); - n2.should.have.property('id','n2'); - n1.send({payload:"foo"}); - n3.on('input', function(msg){ - var venue = msg.payload.venue; - venue.should.have.property('name', "Bobs House"); - done(); - }); - - }); - }); - - it(' fails if error fetching check-in information', function(done) { - helper.load(swarmNode, - [ {id:"n1", type:"helper", wires:[["n2"]]}, - {id:"n4", type:"swarm-credentials"}, - {id:"n2", type:"swarm", swarm: "n4", wires:[["n3"]]}, - {id:"n3", type:"helper"}], - { - "n4": { - displayname : "John", - clientid: "987654321", - clientsecret:"123456789", - accesstoken:"abcd1234", - }, - }, - function() { - var scope = nock('https://api.foursquare.com:443') - .filteringPath(/afterTimestamp=[^&]*/g, 'afterTimestamp=foo') - .get('/v2/users/self/checkins?oauth_token=abcd1234&v=20141016&afterTimestamp=foo&sort=newestfirst') - .reply(200, {"meta":{"code":400, "errorDetail":'test forced failure'}}); - - var n1 = helper.getNode("n1"); - var n2 = helper.getNode("n2"); - var n3 = helper.getNode("n3"); - n2.should.have.property('id','n2'); - - sinon.stub(n2, 'status', function(status){ - var expected = {fill:"red",shape:"ring",text:"failed"}; - should.deepEqual(status, expected); - done(); - }); - - n1.send({payload:"foo"}); - }); - }); - - }} - ); - - - describe('in node', function() { - - if (nock) { - - it(' can fetch check-in information', function(done) { - helper.load(swarmNode, - [ {id:"n1", type:"helper", wires:[["n2"]]}, - {id:"n4", type:"swarm-credentials"}, - {id:"n2", type:"swarm in", swarm: "n4", wires:[["n3"]]}, - {id:"n3", type:"helper"}], - { - "n4": { - displayname : "John", - clientid: "987654321", - clientsecret:"123456789", - accesstoken:"abcd1234", - }, - }, - function() { - var scope = nock('https://api.foursquare.com:443') - .filteringPath(/afterTimestamp=[^&]*/g, 'afterTimestamp=foo') - .get('/v2/users/self/checkins?oauth_token=abcd1234&v=20141016&afterTimestamp=foo&sort=newestfirst') - .reply(200, {"meta":{"code":200},"response":{"checkins":{"count":1, "items":[{"id":"b695edf5ewc2","createdAt":1412861751,"type":"checkin","timeZoneOffset":60,"venue":{"id":"49a8b774","name":"Bobs House"}}]}}}); - - var n1 = helper.getNode("n1"); - var n2 = helper.getNode("n2"); - var n3 = helper.getNode("n3"); - n2.should.have.property('id','n2'); - n2.emit("input", {}); - n3.on('input', function(msg){ - var venue = msg.payload.venue; - venue.should.have.property('name', "Bobs House"); - done(); - }); - }); - }); - - } - - }); - -});