From f8da450264e1bb8aecf6ad4a6b3d3d41344c6c04 Mon Sep 17 00:00:00 2001 From: Evelyn Chan Date: Mon, 9 Feb 2015 22:32:20 -0800 Subject: [PATCH] add support for passing in extra authorization into the headers (client secret/url) --- api-angular-min.js | 2 +- api-angular.js | 30 ++++++++++++++---- lib/http_adapter/angular.js | 12 ++++++-- lib/http_adapter/node.js | 6 ++++ lib/index.js | 8 ++++- lib/wrappers/angular.js | 10 ++++-- test/unit/lib/http_adapter/angular_test.js | 21 ++++++++++++- test/unit/lib/http_adapter/node_test.js | 36 ++++++++++------------ test/unit/lib/wrappers/angular_test.js | 1 + 9 files changed, 93 insertions(+), 33 deletions(-) diff --git a/api-angular-min.js b/api-angular-min.js index b2feb60..13b3d72 100644 --- a/api-angular-min.js +++ b/api-angular-min.js @@ -1 +1 @@ -!function(){"use strict";function a(b,c){for(var d in c)if(c.hasOwnProperty(d))try{b[d]=c[d].constructor===Object?a(b[d],c[d]):c[d]}catch(e){b[d]=c[d]}return b}var b="undefined"!=typeof exports?exports:window,c=b.btoa||require("btoa"),d=b.Promise||require("bluebird"),e=/; */,f=function(b,c,d){if(this._endpoint=b,this._http=d,this._queue=[],this._batchTimeout=null,this._options=a({query:{},params:{track:this._generateTrackId()}},c||{}),this._cookies={},c.cookies){var f=c.cookies.split(e);f.forEach(function(a){var b=a.split("=",2);this._cookies[b[0]]=b[1]}.bind(this))}this._events={}};f.prototype._generateTrackId=function(){return c(1e8*Math.random()).substr(0,10)},f.prototype.execute=function(b,c){if(!b||"string"!=typeof b)throw new Error("Method is required to execute API calls");var e=new d(function(d,e){var f=a({},this._options.params);this._queue.push({method:b,params:a(f,c||{}),deferred:{resolve:d,reject:e}}),null===this._batchTimeout&&(this._batchTimeout=setTimeout(this._postToApi.bind(this),1))}.bind(this));return e},f.prototype._postToApi=function(){var a=j(this._queue),b={};for(var c in this._options.query)b[c]=this._options.query[c];var d;this._http.getSessionToken?d=this._http.getSessionToken():this._cookies.S&&(d=this._cookies.S),d&&(b.session_token=d);var e=[];for(var f in b)b.hasOwnProperty(f)&&e.push(f+"="+b[f]);var k=e.join("&");this._http.post({body:a,url:this._endpoint+"?"+k,cookies:this._options.cookies,clientId:this._options.clientId}).then(g).then(h.bind(this,this._queue)).catch(i.bind(this,this._queue)),this.resetQueue()};var g=function(a){var b=[],c=JSON.parse(a.body);for(var d in c)b.push(JSON.parse(c[d]));return b},h=function(a,b){for(var c in a){var d=b[c];if(d.stat&&this._events.hasOwnProperty(d.stat))for(var e in this._events[d.stat])this._events[d.stat][e](a[c],d);d.stat&&"ok"!==d.stat?a[c].deferred.reject(d):a[c].deferred.resolve(d)}return b},i=function(a,b){for(var c in a)a[c].deferred.reject(b);return b};f.prototype.resetQueue=function(){null!==this._batchTimeout&&(clearTimeout(this._batchTimeout),this._batchTimeout=null),this._queue=[]},f.prototype.on=function(a,b){this._events.hasOwnProperty(a)||(this._events[a]=[]),this._events[a].push(b)},f.middleware=function(b,c){var d=require("./http_adapter/node"),e=new d;return function(d,g,h){d.api=new f(b,a({query:{application_id:"user",format:"JSON",session_token:d.cookies&&d.cookies.S},params:{api_signature:""},cookies:d.headers&&d.headers.cookie},c||{}),e),h()}};var j=function(a){var b=[];for(var c in a){var d=k(a[c]);b.push(d)}return"\n"+b.join("\n")+"\n"},k=function(a){var b=["method="+encodeURIComponent(a.method)];for(var c in a.params)null!==a.params[c]&&a.params.hasOwnProperty(c)&&b.push(l(c,a.params[c]));return b.join("&")},l=function(a,b){var c=typeof b;switch(c){case"string":case"number":case"boolean":return m(a,b);case"undefined":return m(a,"");case"object":return null===b?m(a,b):n(a,b);default:throw new Error("Unable to parameterize key "+a+" with type "+c)}},m=function(a,b){return encodeURIComponent(a)+"="+encodeURIComponent(b)},n=function(a,b){var c=[];if(Array.isArray(b))for(var d=0,e=b.length;e>d;d++)c.push(encodeURIComponent(a)+"[]="+encodeURIComponent(b[d]));else for(var f in b)b.hasOwnProperty(f)&&c.push(encodeURIComponent(a)+"["+encodeURIComponent(f)+"]="+encodeURIComponent(b[f]));return c.join("&")};"undefined"!=typeof exports?module.exports=f:b.TaggedApi=f}(),function(){"use strict";function a(a,b){this._$http=a,this._$document=b}var b="undefined"!=typeof exports?exports:window,c=(b.Promise||require("bluebird"),/(?:^| )S=/);a.$inject=["$http","$document"],a.prototype.post=function(a){return this._$http.post(a.url,a.body,{timeout:1e4,transformResponse:d}).then(e)},a.prototype.getSessionToken=function(){var a=this._$document[0].cookie;if(!a.length)return null;var b=a.match(c);if(!b)return null;var d=b.index;if(-1==d)return null;d+=2," "===b[0].charAt(0)&&d++;var e=a.indexOf(";",d);return-1==e&&(e=a.length),unescape(a.substring(d,e))};var d=function(a){return a},e=function(a){return{body:a.data}};if("undefined"!=typeof exports)module.exports=a;else{var f=b.TaggedApi||{};f.AngularAdapter=a}}(),function(){"use strict";var a=function(a,b){function c(a,c,d){var e=new b.AngularAdapter(a,c),f=new b("/api/",{query:{application_id:"user",format:"json"}},e);return f.execute=function(a,c){return d.when(b.prototype.execute.call(this,a,c))},f}var d=a.module("tagged.service.api",[]);d.factory("taggedApi",c),c.$inject=["$http","$document","$q"]};"undefined"!=typeof exports?module.exports=a:TaggedApi.angularWrapper=a}(),TaggedApi.angularWrapper(angular,TaggedApi); \ No newline at end of file +!function(){"use strict";function a(b,c){for(var d in c)if(c.hasOwnProperty(d))try{b[d]=c[d].constructor===Object?a(b[d],c[d]):c[d]}catch(e){b[d]=c[d]}return b}var b="undefined"!=typeof exports?exports:window,c=b.btoa||require("btoa"),d=b.Promise||require("bluebird"),e=/; */,f=function(b,c,d){if(this._endpoint=b,this._http=d,this._queue=[],this._batchTimeout=null,this._options=a({query:{},params:{track:this._generateTrackId()}},c||{}),this._cookies={},c.cookies){var f=c.cookies.split(e);f.forEach(function(a){var b=a.split("=",2);this._cookies[b[0]]=b[1]}.bind(this))}this._events={}};f.prototype._generateTrackId=function(){return c(1e8*Math.random()).substr(0,10)},f.prototype.execute=function(b,c){if(!b||"string"!=typeof b)throw new Error("Method is required to execute API calls");var e=new d(function(d,e){var f=a({},this._options.params);this._queue.push({method:b,params:a(f,c||{}),deferred:{resolve:d,reject:e}}),null===this._batchTimeout&&(this._batchTimeout=setTimeout(this._postToApi.bind(this),1))}.bind(this));return e},f.prototype._postToApi=function(){var a=j(this._queue),b={};for(var c in this._options.query)b[c]=this._options.query[c];var d;this._http.getSessionToken?d=this._http.getSessionToken():this._cookies.S&&(d=this._cookies.S),d&&(b.session_token=d);var e=[];for(var f in b)b.hasOwnProperty(f)&&e.push(f+"="+b[f]);var k=e.join("&");this._http.post({body:a,url:this._endpoint+"?"+k,cookies:this._options.cookies,clientId:this._options.clientId,secret:this._options.secret}).then(g).then(h.bind(this,this._queue))["catch"](i.bind(this,this._queue)),this.resetQueue()};var g=function(a){var b=[],c=JSON.parse(a.body);for(var d in c)b.push(JSON.parse(c[d]));return b},h=function(a,b){for(var c in a){var d=b[c];if(d.stat&&this._events.hasOwnProperty(d.stat))for(var e in this._events[d.stat])this._events[d.stat][e](a[c],d);d.stat&&"ok"!==d.stat?a[c].deferred.reject(d):a[c].deferred.resolve(d)}return b},i=function(a,b){for(var c in a)a[c].deferred.reject(b);return b};f.prototype.resetQueue=function(){null!==this._batchTimeout&&(clearTimeout(this._batchTimeout),this._batchTimeout=null),this._queue=[]},f.prototype.on=function(a,b){this._events.hasOwnProperty(a)||(this._events[a]=[]),this._events[a].push(b)},f.middleware=function(b,c){var d=require("./http_adapter/node"),e=new d;return function(d,g,h){d.api=new f(b,a({query:{application_id:"user",format:"JSON",session_token:d.cookies&&d.cookies.S},params:{api_signature:""},cookies:d.headers&&d.headers.cookie},c||{}),e),h()}};var j=function(a){var b=[];for(var c in a){var d=k(a[c]);b.push(d)}return"\n"+b.join("\n")+"\n"},k=function(a){var b=["method="+encodeURIComponent(a.method)];for(var c in a.params)null!==a.params[c]&&a.params.hasOwnProperty(c)&&b.push(l(c,a.params[c]));return b.join("&")},l=function(a,b){var c=typeof b;switch(c){case"string":case"number":case"boolean":return m(a,b);case"undefined":return m(a,"");case"object":return null===b?m(a,b):n(a,b);default:throw new Error("Unable to parameterize key "+a+" with type "+c)}},m=function(a,b){return encodeURIComponent(a)+"="+encodeURIComponent(b)},n=function(a,b){var c=[];if(Array.isArray(b))for(var d=0,e=b.length;e>d;d++)c.push(encodeURIComponent(a)+"[]="+encodeURIComponent(b[d]));else for(var f in b)b.hasOwnProperty(f)&&c.push(encodeURIComponent(a)+"["+encodeURIComponent(f)+"]="+encodeURIComponent(b[f]));return c.join("&")};f.prototype.addOptions=function(b){this._options=a(b||{},this._options)},"undefined"!=typeof exports?module.exports=f:b.TaggedApi=f}(),function(){"use strict";function a(a,b,c){this._$http=a,this._$document=b,this._$window=c}var b="undefined"!=typeof exports?exports:window,c=(b.Promise||require("bluebird"),/(?:^| )S=/);a.$inject=["$http","$document","$window"],a.prototype.post=function(a){var b={"x-tagged-client-id":a.clientId,"x-tagged-client-url":this._$window.location};return this._$http.post(a.url,a.body,{timeout:1e4,transformResponse:d,headers:b}).then(e)},a.prototype.getSessionToken=function(){var a=this._$document[0].cookie;if(!a.length)return null;var b=a.match(c);if(!b)return null;var d=b.index;if(-1==d)return null;d+=2," "===b[0].charAt(0)&&d++;var e=a.indexOf(";",d);return-1==e&&(e=a.length),unescape(a.substring(d,e))};var d=function(a){return a},e=function(a){return{body:a.data}};if("undefined"!=typeof exports)module.exports=a;else{var f=b.TaggedApi||{};f.AngularAdapter=a}}(),function(){"use strict";var a=function(a,b){function c(a,c,d){var e=new b.AngularAdapter(a,c),f=new b("/api/",{query:{application_id:"user",format:"json"}},e);return f.execute=function(a,c){return c.clientId&&"undefined"!=typeof c.clientId?(b.prototype.execute.call(this,c.clientId),d.when(b.prototype.execute.call(this,a,c.params||{}))):d.reject("Client ID is required to make API calls")},f}var d=a.module("tagged.service.api",[]);d.factory("taggedApi",c),c.$inject=["$http","$document","$q"]};"undefined"!=typeof exports?module.exports=a:TaggedApi.angularWrapper=a}(),TaggedApi.angularWrapper(angular,TaggedApi); \ No newline at end of file diff --git a/api-angular.js b/api-angular.js index 1405ecb..e056a85 100644 --- a/api-angular.js +++ b/api-angular.js @@ -122,7 +122,8 @@ body: body, url: this._endpoint + "?" + queryString, cookies: this._options.cookies, - clientId: this._options.clientId + clientId: this._options.clientId, + secret: this._options.secret }) .then(parseResponseBody) .then(resolveQueue.bind(this, this._queue)) @@ -319,6 +320,11 @@ return obj1; } + // Adds other configuration after the api has already been initialized + TaggedApi.prototype.addOptions = function(options) { + this._options = mergeRecursive(options || {}, this._options); + } + if (typeof exports !== 'undefined') { // We're in a nodejs environment, export this module module.exports = TaggedApi; @@ -336,16 +342,22 @@ var Promise = context.Promise || require('bluebird'); var SESSION_COOKIE_NAME_REGEX = /(?:^| )S=/; - AngularAdapter.$inject = ['$http', '$document']; - function AngularAdapter($http, $document) { + AngularAdapter.$inject = ['$http', '$document', '$window']; + function AngularAdapter($http, $document, $window) { this._$http = $http; this._$document = $document; + this._$window = $window; } AngularAdapter.prototype.post = function(req) { + var headers = { + 'x-tagged-client-id': req.clientId, + 'x-tagged-client-url': this._$window.location + }; return this._$http.post(req.url, req.body, { timeout: 10000, - transformResponse: transformResponse + transformResponse: transformResponse, + headers: headers }).then(formatResponse); }; @@ -437,8 +449,14 @@ }, angularAdapter); // Wrap `execute()` in an Angular promise - api.execute = function(method, params) { - return $q.when(TaggedApi.prototype.execute.call(this, method, params)); + api.execute = function(method, options) { + if (!options.clientId || typeof options.clientId === 'undefined') { + return $q.reject('Client ID is required to make API calls'); + } + + // append the client ID from the params sent from client + TaggedApi.prototype.execute.call(this, options.clientId); + return $q.when(TaggedApi.prototype.execute.call(this, method, options.params || {})); }; return api; diff --git a/lib/http_adapter/angular.js b/lib/http_adapter/angular.js index 7c1fc5e..0692f2e 100644 --- a/lib/http_adapter/angular.js +++ b/lib/http_adapter/angular.js @@ -6,16 +6,22 @@ var Promise = context.Promise || require('bluebird'); var SESSION_COOKIE_NAME_REGEX = /(?:^| )S=/; - AngularAdapter.$inject = ['$http', '$document']; - function AngularAdapter($http, $document) { + AngularAdapter.$inject = ['$http', '$document', '$window']; + function AngularAdapter($http, $document, $window) { this._$http = $http; this._$document = $document; + this._$window = $window; } AngularAdapter.prototype.post = function(req) { + var headers = { + 'x-tagged-client-id': req.clientId, + 'x-tagged-client-url': this._$window.location + }; return this._$http.post(req.url, req.body, { timeout: 10000, - transformResponse: transformResponse + transformResponse: transformResponse, + headers: headers }).then(formatResponse); }; diff --git a/lib/http_adapter/node.js b/lib/http_adapter/node.js index b50e3b3..7c42453 100644 --- a/lib/http_adapter/node.js +++ b/lib/http_adapter/node.js @@ -14,6 +14,12 @@ Http.prototype.post = function(req) { headers['x-tagged-client-id'] = req.clientId; } + if (req.secret) { + headers['x-tagged-client-secret'] = req.secret; + } + + console.info('post /api headers: ', headers); + return this._request.postAsync({ url: req.url, body: req.body, diff --git a/lib/index.js b/lib/index.js index fcb168c..9cd2fab 100644 --- a/lib/index.js +++ b/lib/index.js @@ -122,7 +122,8 @@ body: body, url: this._endpoint + "?" + queryString, cookies: this._options.cookies, - clientId: this._options.clientId + clientId: this._options.clientId, + secret: this._options.secret }) .then(parseResponseBody) .then(resolveQueue.bind(this, this._queue)) @@ -319,6 +320,11 @@ return obj1; } + // Adds other configuration after the api has already been initialized + TaggedApi.prototype.addOptions = function(options) { + this._options = mergeRecursive(options || {}, this._options); + } + if (typeof exports !== 'undefined') { // We're in a nodejs environment, export this module module.exports = TaggedApi; diff --git a/lib/wrappers/angular.js b/lib/wrappers/angular.js index 662f886..aa0b87d 100644 --- a/lib/wrappers/angular.js +++ b/lib/wrappers/angular.js @@ -27,8 +27,14 @@ }, angularAdapter); // Wrap `execute()` in an Angular promise - api.execute = function(method, params) { - return $q.when(TaggedApi.prototype.execute.call(this, method, params)); + api.execute = function(method, options) { + if (!options.clientId || typeof options.clientId === 'undefined') { + return $q.reject('Client ID is required to make API calls'); + } + + // append the client ID from the params sent from client + TaggedApi.prototype.execute.call(this, options.clientId); + return $q.when(TaggedApi.prototype.execute.call(this, method, options.params || {})); }; return api; diff --git a/test/unit/lib/http_adapter/angular_test.js b/test/unit/lib/http_adapter/angular_test.js index d705ed2..6d1a9da 100644 --- a/test/unit/lib/http_adapter/angular_test.js +++ b/test/unit/lib/http_adapter/angular_test.js @@ -16,7 +16,10 @@ describe('Angular Adapter', function() { cookie: 'foo=bar; S=test_session_token' } ]; - this.adapter = new AngularAdapter(this.$http, this.$document); + this.$window = { + location: '/foo' + }; + this.adapter = new AngularAdapter(this.$http, this.$document, this.$window); }); it('is a constructor', function() { @@ -62,6 +65,22 @@ describe('Angular Adapter', function() { }); result.should.become({ body: 'some data'}); }); + + it('sets client ID and url in headers', function() { + var url = 'http://example.com/foo'; + var body = 'post body'; + var clientId = 'testclientid'; + var location = '/foo'; + this.adapter.post({ + url: url, + body: body, + clientId: clientId + }); + this.$http.post.calledOnce.should.be.true; + this.$http.post.lastCall.args[2].should.have.property('headers'); + this.$http.post.lastCall.args[2].headers['x-tagged-client-id'].should.equal(clientId); + this.$http.post.lastCall.args[2].headers['x-tagged-client-url'].should.equal(location); + }); }); describe('getSessionToken', function() { diff --git a/test/unit/lib/http_adapter/node_test.js b/test/unit/lib/http_adapter/node_test.js index 2f39a61..79586d7 100644 --- a/test/unit/lib/http_adapter/node_test.js +++ b/test/unit/lib/http_adapter/node_test.js @@ -26,17 +26,17 @@ describe('Node HTTP Adapter', function() { describe('post()', function() { beforeEach(function() { this.http = new Http(this.request); + this.url = 'http://example.com/foo'; + this.body = 'post body'; }); it('posts body to specified url', function() { - var url = 'http://example.com/foo'; - var body = 'post body'; this.http.post({ - url: url, - body: body + url: this.url, + body: this.body }); this.request.post.calledOnce.should.be.true; - this.request.post.lastCall.args[0].should.have.property('url', url); + this.request.post.lastCall.args[0].should.have.property('url', this.url); }); it('posts cookies to specified url', function() { @@ -53,29 +53,28 @@ describe('Node HTTP Adapter', function() { this.request.post.lastCall.args[0].headers.should.have.property('Cookie', cookies); }); - it('posts client id header if provided', function() { - var url = 'http://example.com/foo'; - var body = 'post body'; + it('posts client id and client secret to header if provided', function() { var cookies = 'testcookies=1'; var clientId = 'testClientId'; + var clientSecret = 'testSecret'; this.http.post({ - url: url, - body: body, + url: this.url, + body: this.body, cookies: cookies, - clientId: clientId + clientId: clientId, + secret: clientSecret }); this.request.post.calledOnce.should.be.true; this.request.post.lastCall.args[0].should.have.property('headers'); this.request.post.lastCall.args[0].headers.should.have.property('x-tagged-client-id', clientId); + this.request.post.lastCall.args[0].headers.should.have.property('x-tagged-client-secret', clientSecret); }); it('resolves with response', function() { - var url = 'http://example.com/foo'; - var body = 'post body'; var expectedBody = 'expected body'; var result = this.http.post({ - url: url, - body: body + url: this.url, + body: this.body }); this.request.respondWith(null, { @@ -88,17 +87,16 @@ describe('Node HTTP Adapter', function() { }); it('rejects with error', function() { - var url = 'http://example.com/foo'; - var body = 'post body'; var error = 'test error'; var result = this.http.post({ - url: url, - body: body + url: this.url, + body: this.body }); this.request.respondWith(error); return result.should.be.rejectedWith(error); }); + }); }); diff --git a/test/unit/lib/wrappers/angular_test.js b/test/unit/lib/wrappers/angular_test.js index 5cd7b37..0bdea5e 100644 --- a/test/unit/lib/wrappers/angular_test.js +++ b/test/unit/lib/wrappers/angular_test.js @@ -42,4 +42,5 @@ describe('Angular Wrapper', function() { var api = this.module.factory.lastCall.args[1]($http, $q); this.TaggedApi.lastCall.args[2].should.be.instanceOf(this.TaggedApi.AngularAdapter); }); + });