From 93e1abee7c73024bca5f3cb46f8c9ee2f177e880 Mon Sep 17 00:00:00 2001 From: Andrea Reginato Date: Tue, 26 Aug 2014 11:16:59 +0200 Subject: [PATCH 001/107] Revert pull request #24 --- app/scripts/services/access-token.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/scripts/services/access-token.js b/app/scripts/services/access-token.js index 9bf875a..2423c98 100644 --- a/app/scripts/services/access-token.js +++ b/app/scripts/services/access-token.js @@ -65,7 +65,7 @@ accessTokenService.factory('AccessToken', function($rootScope, $location, $sessi if (token) { removeFragment(); service.setToken(token); - setExpiresAt(token); + setExpiresAt(); $rootScope.$broadcast('oauth:login', token); } }; @@ -131,7 +131,7 @@ accessTokenService.factory('AccessToken', function($rootScope, $location, $sessi * Set the access token expiration date (useful for refresh logics) */ - var setExpiresAt = function(token) { + var setExpiresAt = function() { if (token) { var expires_at = new Date(); expires_at.setSeconds(expires_at.getSeconds() + parseInt(token.expires_in) - 60); // 60 seconds less to secure browser and response latency From 4ab133ff16a4a7b0f18ecae5c70f8772f1e9054b Mon Sep 17 00:00:00 2001 From: ryanp Date: Wed, 27 Aug 2014 11:14:22 -0500 Subject: [PATCH 002/107] Fixed `expries_at` not being set in some situations. Only use session storage when oAuth hash not in URL. Only remove oAuth2 tokens from hash --- CHANGELOG.md | 6 + app/scripts/app.js | 2 +- app/scripts/services/access-token.js | 324 ++++++++++++++------------- bower.json | 5 +- dist/oauth-ng.js | 288 ++++++++++++------------ package.json | 2 +- test/spec/services/access-token.js | 31 +-- 7 files changed, 337 insertions(+), 321 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f23b23a..99d20fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## 0.2.8 (August 27, 2014) + +* Fixed `expries_at` not being set in some situations +* Only use session storage when oAuth hash not in URL +* Only remove oAuth2 tokens from hash + ## 0.2.7 (August 26, 2014) * Fixed `expires_at` not being set diff --git a/app/scripts/app.js b/app/scripts/app.js index 3755008..fdc751d 100644 --- a/app/scripts/app.js +++ b/app/scripts/app.js @@ -7,7 +7,7 @@ var app = angular.module('oauth', [ 'oauth.endpoint', // oauth endpoint service 'oauth.profile', // profile model 'oauth.interceptor' // bearer token interceptor -]) +]); angular.module('oauth').config(['$locationProvider','$httpProvider', function($locationProvider, $httpProvider) { diff --git a/app/scripts/services/access-token.js b/app/scripts/services/access-token.js index 2423c98..edcc9a7 100644 --- a/app/scripts/services/access-token.js +++ b/app/scripts/services/access-token.js @@ -2,163 +2,169 @@ var accessTokenService = angular.module('oauth.accessToken', ['ngStorage']); -accessTokenService.factory('AccessToken', function($rootScope, $location, $sessionStorage, $timeout) { - - var service = {}; - var token = null; - - - /* - * Returns the access token. - */ - - service.get = function() { - return token - } - - - /* - * Sets and returns the access token. It tries (in order) the following strategies: - * - takes the token from the fragment URI - * - takes the token from the sessionStorage - */ - - service.set = function() { - service.setTokenFromString($location.hash()); - service.setTokenFromSession(); - return token - } - - - /* - * Delete the access token and remove the session. - */ - - service.destroy = function() { - delete $sessionStorage.token; - return token = null; - } - - - /* - * Tells if the access token is expired. - */ - - service.expired = function() { - return (token && token.expires_at && token.expires_at < new Date()); - } - - - - /* * * * * * * * * * - * PRIVATE METHODS * - * * * * * * * * * */ - - - /* - * Get the access token from a string and save it - */ - - service.setTokenFromString = function(hash) { - var token = getTokenFromString(hash); - - if (token) { - removeFragment(); - service.setToken(token); - setExpiresAt(); - $rootScope.$broadcast('oauth:login', token); - } - }; - - - /* - * Parse the fragment URI and return an object - */ - - var getTokenFromString = function(hash) { - var splitted = hash.split('&'); - var params = {}; - - for (var i = 0; i < splitted.length; i++) { - var param = splitted[i].split('='); - var key = param[0]; - var value = param[1]; - params[key] = value - } - - if (params.access_token || params.error) - return params; - } - - - /* - * Set the access token from the sessionStorage. - */ - - service.setTokenFromSession = function() { - if ($sessionStorage.token) { - var params = $sessionStorage.token; - params.expires_at = new Date(params.expires_at); - service.setToken(params); - } - } - - - /* - * Save the access token into the session - */ - - var setTokenInSession = function() { - $sessionStorage.token = token; - } - - - /* - * Set the access token. - */ - - service.setToken = function(params) { - token = token || {}; // init the token - angular.extend(token, params); // set the access token params - setTokenInSession(); // save the token into the session - setExpiresAtEvent(); // event to fire when the token expires - - return token; - }; - - - /* - * Set the access token expiration date (useful for refresh logics) - */ - - var setExpiresAt = function() { - if (token) { - var expires_at = new Date(); - expires_at.setSeconds(expires_at.getSeconds() + parseInt(token.expires_in) - 60); // 60 seconds less to secure browser and response latency - token.expires_at = expires_at; - } - }; - - - /* - * Set the timeout at which the expired event is fired - */ - - var setExpiresAtEvent = function() { - var time = (new Date(token.expires_at)) - (new Date()) - if (time) { $timeout(function() { $rootScope.$broadcast('oauth:expired', token) }, time) } - } - - - /* - * Remove the fragment URI - * TODO we need to remove only the access token - */ - - var removeFragment = function(scope) { - $location.hash(''); - } - - - return service; +accessTokenService.factory('AccessToken', function($rootScope, $location, $sessionStorage, $timeout){ + + var service = { + token: null + }, + oAuth2HashTokens = [ //per http://tools.ietf.org/html/rfc6749#section-4.2.2 + 'access_token', 'token_type', 'expires_in', 'scope', 'state', + 'error','error_description' + ]; + + /** + * Returns the access token. + */ + service.get = function(){ + return this.token; + }; + + /** + * Sets and returns the access token. It tries (in order) the following strategies: + * - takes the token from the fragment URI + * - takes the token from the sessionStorage + */ + service.set = function(){ + setTokenFromString($location.hash()); + + //If hash is present in URL always use it, cuz its coming from oAuth2 provider redirect + if(null === service.token){ + setTokenFromSession(); + } + + return this.token; + }; + + /** + * Delete the access token and remove the session. + * @returns {null} + */ + service.destroy = function(){ + delete $sessionStorage.token; + this.token = null; + return this.token; + }; + + + /** + * Tells if the access token is expired. + */ + service.expired = function(){ + return (this.token && this.token.expires_at && this.token.expires_at", "description": "AngularJS Directive for OAuth 2.0", "repository": { diff --git a/test/spec/services/access-token.js b/test/spec/services/access-token.js index 67dda57..1fe4194 100644 --- a/test/spec/services/access-token.js +++ b/test/spec/services/access-token.js @@ -4,7 +4,7 @@ describe('AccessToken', function() { var result, $location, $sessionStorage, AccessToken, date; - var fragment = 'access_token=token&token_type=bearer&expires_in=7200&state=/path'; + var fragment = 'access_token=token&token_type=bearer&expires_in=7200&state=/path&extra=stuff'; var denied = 'error=access_denied&error_description=error'; var expires_at = '2014-08-17T17:38:37.584Z'; var token = { access_token: 'token', token_type: 'bearer', expires_in: 7200, state: '/path', expires_at: expires_at }; @@ -54,7 +54,7 @@ describe('AccessToken', function() { }); it('removes the fragment string', function() { - expect($location.hash()).toEqual(''); + expect($location.hash()).toEqual('extra=stuff'); }); it('stores the token in the session', function() { @@ -193,21 +193,22 @@ describe('AccessToken', function() { expect(result).toBe(true); }); }); + }); - describe('with the access token stored in the session', function() { - - beforeEach(function() { - $sessionStorage.token = token; - }); + describe('#sessionExpired', function() { + describe('with the access token stored in the session', function() { - beforeEach(function() { - result = AccessToken.set().expires_at; - }); + beforeEach(function() { + //It is an invalid test to have oAuth hash AND be getting token from session + //if hash is in URL it should always be used, cuz its coming from oAuth2 provider re-direct + $location.hash(''); + $sessionStorage.token = token; + result = AccessToken.set().expires_at; + }); - it('rehydrates the expires_at value', function() { - expect(result).toEqual(new Date(expires_at)); - }); + it('rehydrates the expires_at value', function() { + expect(result).toEqual(new Date(expires_at)); + }); + }); }); - - }); }); From 22f2e34dbb04f64cce8d30bb5b235dcecabb09cd Mon Sep 17 00:00:00 2001 From: ryanp Date: Wed, 27 Aug 2014 16:12:11 -0500 Subject: [PATCH 003/107] more efficent and uri decode of hash tokens per https://developers.google.com/accounts/docs/OAuth2UserAgent#handlingtheresponse --- app/scripts/services/access-token.js | 15 ++++++--------- dist/oauth-ng.js | 15 ++++++--------- 2 files changed, 12 insertions(+), 18 deletions(-) diff --git a/app/scripts/services/access-token.js b/app/scripts/services/access-token.js index edcc9a7..0d62517 100644 --- a/app/scripts/services/access-token.js +++ b/app/scripts/services/access-token.js @@ -105,15 +105,12 @@ accessTokenService.factory('AccessToken', function($rootScope, $location, $sessi * @returns {{}} */ var getTokenFromString = function(hash){ - var splitted = hash.split('&'); - var params = {}; - - //TODO: I think we should only parse out tokens in the oAuth spec (@see:oAuth2HashTokens) - for(var i = 0; i Date: Mon, 8 Sep 2014 11:43:38 +1000 Subject: [PATCH 004/107] Broadcast the profile when available using the message 'ouath:profile' --- dist/oauth-ng.js | 1 + 1 file changed, 1 insertion(+) diff --git a/dist/oauth-ng.js b/dist/oauth-ng.js index c1da64a..80c8572 100644 --- a/dist/oauth-ng.js +++ b/dist/oauth-ng.js @@ -347,6 +347,7 @@ directives.directive('oauth', function(AccessToken, Endpoint, Profile, $location if (token && token.access_token && scope.profileUri) { Profile.find(scope.profileUri).success(function(response) { scope.profile = response + $rootScope.$broadcast('oauth:profile', response); }) } }; From 0f5adf743abe49f6d1c6a4cdbd78dcc4be47c431 Mon Sep 17 00:00:00 2001 From: James Crowley Date: Fri, 26 Sep 2014 20:59:43 +0100 Subject: [PATCH 005/107] Rename parameters to make clearer --- app/scripts/services/endpoint.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/app/scripts/services/endpoint.js b/app/scripts/services/endpoint.js index 5353e40..deb3c7f 100644 --- a/app/scripts/services/endpoint.js +++ b/app/scripts/services/endpoint.js @@ -12,17 +12,17 @@ endpointClient.factory('Endpoint', function(AccessToken, $location) { * Defines the authorization URL */ - service.set = function(scope) { - var oAuthScope = (scope.scope) ? scope.scope : '', - state = (scope.state) ? encodeURIComponent(scope.state) : '', - authPathHasQuery = (scope.authorizePath.indexOf('?') == -1) ? false : true, + service.set = function(params) { + var oAuthScope = (params.scope) ? params.scope : '', + state = (params.state) ? encodeURIComponent(params.state) : '', + authPathHasQuery = (params.authorizePath.indexOf('?') == -1) ? false : true, appendChar = (authPathHasQuery) ? '&' : '?'; //if authorizePath has ? already append OAuth2 params - url = scope.site + - scope.authorizePath + + url = params.site + + params.authorizePath + appendChar + 'response_type=token&' + - 'client_id=' + encodeURIComponent(scope.clientId) + '&' + - 'redirect_uri=' + encodeURIComponent(scope.redirectUri) + '&' + + 'client_id=' + encodeURIComponent(params.clientId) + '&' + + 'redirect_uri=' + encodeURIComponent(params.redirectUri) + '&' + 'scope=' + oAuthScope + '&' + 'state=' + state; From 45b536c5bb2a5e8407a1c2bae61ab2025510c584 Mon Sep 17 00:00:00 2001 From: rowasc Date: Thu, 9 Oct 2014 23:09:37 -0200 Subject: [PATCH 006/107] Check against undefined params in the initialization object --- dist/oauth-ng.js | 17 ++++++++++------- test/spec/services/endpoint.js | 17 +++++++++++++++++ 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/dist/oauth-ng.js b/dist/oauth-ng.js index c1da64a..88f864a 100644 --- a/dist/oauth-ng.js +++ b/dist/oauth-ng.js @@ -324,14 +324,17 @@ directives.directive('oauth', function(AccessToken, Endpoint, Profile, $location initProfile(scope); // gets the profile resource (if existing the access token) initView(); // sets the view (logged in or out) }; - + /** + * Check against undefined params, assign default if they are not present + */ var initAttributes = function() { - scope.authorizePath = scope.authorizePath || '/oauth/authorize'; - scope.tokenPath = scope.tokenPath || '/oauth/token'; - scope.template = scope.template || 'bower_components/oauth-ng/dist/views/templates/default.html'; - scope.text = scope.text || 'Sign In'; - scope.state = scope.state || undefined; - scope.scope = scope.scope || undefined; + scope.authorizePath = typeof(scope.authorizePath)!=="undefined" ?scope.authorizePath : '/oauth/authorize'; + scope.tokenPath = typeof(scope.tokenPath)!=="undefined" ?scope.tokenPath: '/oauth/token'; + scope.template = typeof(scope.template)!=="undefined"? scope.template: 'bower_components/oauth-ngw/dist/views/templates/default.html'; + scope.text = typeof(scope.text)!=="undefined"?scope.text: 'Sign In'; + scope.state = typeof(scope.state)!=="undefined" ?scope.state : undefined; + scope.scope = typeof(scope.scope)!=="undefined" ?scope.scope : undefined; + }; var compile = function() { diff --git a/test/spec/services/endpoint.js b/test/spec/services/endpoint.js index 245237b..de878f0 100644 --- a/test/spec/services/endpoint.js +++ b/test/spec/services/endpoint.js @@ -71,6 +71,23 @@ describe('Endpoint', function() { expect(result).toEqual(expectedUri); }); }); + + describe('authorizePath can be empty', function() { + var paramsClone = JSON.parse(JSON.stringify(params)); + + beforeEach(function() { + paramsClone.authorizePath = ''; + }); + + beforeEach(function() { + result = Endpoint.set(paramsClone); + }); + + it('uri should not be in state', function() { + var expectedUri = 'http://example.com/?response_type=token&client_id=client-id&redirect_uri=http%3A%2F%2Fexample.com%2Fredirect&scope=scope&state='; + expect(result).toEqual(expectedUri); + }); + }); }); describe('#get', function() { From 3822e497a5c4f406fd66dd0dd1e5904c99d5c174 Mon Sep 17 00:00:00 2001 From: rowasc Date: Thu, 9 Oct 2014 23:15:37 -0200 Subject: [PATCH 007/107] Check against undefined params in the initialization object --- dist/oauth-ng.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/dist/oauth-ng.js b/dist/oauth-ng.js index 88f864a..ed26586 100644 --- a/dist/oauth-ng.js +++ b/dist/oauth-ng.js @@ -328,12 +328,12 @@ directives.directive('oauth', function(AccessToken, Endpoint, Profile, $location * Check against undefined params, assign default if they are not present */ var initAttributes = function() { - scope.authorizePath = typeof(scope.authorizePath)!=="undefined" ?scope.authorizePath : '/oauth/authorize'; - scope.tokenPath = typeof(scope.tokenPath)!=="undefined" ?scope.tokenPath: '/oauth/token'; - scope.template = typeof(scope.template)!=="undefined"? scope.template: 'bower_components/oauth-ngw/dist/views/templates/default.html'; - scope.text = typeof(scope.text)!=="undefined"?scope.text: 'Sign In'; - scope.state = typeof(scope.state)!=="undefined" ?scope.state : undefined; - scope.scope = typeof(scope.scope)!=="undefined" ?scope.scope : undefined; + scope.authorizePath = typeof(scope.authorizePath)!=="undefined" ?scope.authorizePath : '/oauth/authorize'; + scope.tokenPath = typeof(scope.tokenPath)!=="undefined" ?scope.tokenPath: '/oauth/token'; + scope.template = typeof(scope.template)!=="undefined"? scope.template: 'bower_components/oauth-ngw/dist/views/templates/default.html'; + scope.text = typeof(scope.text)!=="undefined"?scope.text: 'Sign In'; + scope.state = typeof(scope.state)!=="undefined" ?scope.state : undefined; + scope.scope = typeof(scope.scope)!=="undefined" ?scope.scope : undefined; }; From 22857f8f031e3e002c8a601ccd8b7feb80898e69 Mon Sep 17 00:00:00 2001 From: abuecker Date: Mon, 20 Oct 2014 21:53:19 -0700 Subject: [PATCH 008/107] fix(AccessToken): Added token query based on $location.path() refactor(template): Correct template path typo --- dist/oauth-ng.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dist/oauth-ng.js b/dist/oauth-ng.js index 1909d0e..988d7dd 100644 --- a/dist/oauth-ng.js +++ b/dist/oauth-ng.js @@ -43,7 +43,7 @@ accessTokenService.factory('AccessToken', function($rootScope, $location, $sessi * - takes the token from the sessionStorage */ service.set = function(){ - setTokenFromString($location.hash()); + setTokenFromString($location.path().substr(1)); //If hash is present in URL always use it, cuz its coming from oAuth2 provider redirect if(null === service.token){ @@ -330,7 +330,7 @@ directives.directive('oauth', function(AccessToken, Endpoint, Profile, $location var initAttributes = function() { scope.authorizePath = typeof(scope.authorizePath)!=="undefined" ?scope.authorizePath : '/oauth/authorize'; scope.tokenPath = typeof(scope.tokenPath)!=="undefined" ?scope.tokenPath: '/oauth/token'; - scope.template = typeof(scope.template)!=="undefined"? scope.template: 'bower_components/oauth-ngw/dist/views/templates/default.html'; + scope.template = typeof(scope.template)!=="undefined"? scope.template: 'bower_components/oauth-ng/dist/views/templates/default.html'; scope.text = typeof(scope.text)!=="undefined"?scope.text: 'Sign In'; scope.state = typeof(scope.state)!=="undefined" ?scope.state : undefined; scope.scope = typeof(scope.scope)!=="undefined" ?scope.scope : undefined; From c7da2bba28dce1ede6585367f8c73e2423fa0e44 Mon Sep 17 00:00:00 2001 From: Jean-Francois Turcot Date: Wed, 22 Oct 2014 18:21:45 -0400 Subject: [PATCH 009/107] Broadcast event oauth:tokenDestroy after a logout --- app/scripts/directives/oauth.js | 1 + test/spec/directives/oauth.js | 11 ++++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/app/scripts/directives/oauth.js b/app/scripts/directives/oauth.js index 08cf7c0..ad039c9 100644 --- a/app/scripts/directives/oauth.js +++ b/app/scripts/directives/oauth.js @@ -74,6 +74,7 @@ directives.directive('oauth', function(AccessToken, Endpoint, Profile, $location scope.logout = function() { AccessToken.destroy(scope); + $rootScope.$broadcast('oauth:tokenDestroy'); loggedOut(); }; diff --git a/test/spec/directives/oauth.js b/test/spec/directives/oauth.js index e5e34c8..0f37632 100644 --- a/test/spec/directives/oauth.js +++ b/test/spec/directives/oauth.js @@ -137,6 +137,10 @@ describe('oauth', function() { callback = jasmine.createSpy('callback'); }); + beforeEach(function() { + $rootScope.$on('oauth:tokenDestroy', callback); + }); + beforeEach(function() { $rootScope.$on('oauth:logout', callback); }); @@ -150,9 +154,10 @@ describe('oauth', function() { expect(element.find('.logged-in').attr('class')).toMatch('ng-hide'); }); - it('fires the oauth:logout event', function() { + it('fires the oauth:tokenDestroy and oauth:logout event', function() { var event = jasmine.any(Object); expect(callback).toHaveBeenCalledWith(event); + expect(callback.calls.count()).toBe(2); }); }); }); @@ -199,6 +204,10 @@ describe('oauth', function() { var event = jasmine.any(Object); expect(callback).toHaveBeenCalledWith(event); }); + + it('does not fire the oauth:tokenDestroy event', function() { + expect(callback.calls.count()).toBe(1); + }); }); From 87190eaee8714fdf46a8ceae94fd3d15923fafc5 Mon Sep 17 00:00:00 2001 From: Andrea Reginato Date: Sun, 26 Oct 2014 23:46:34 +0100 Subject: [PATCH 010/107] Bump to v.0.2.8 --- dist/oauth-ng.js | 38 +++++++++++++++++--------------------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/dist/oauth-ng.js b/dist/oauth-ng.js index 988d7dd..1a409a0 100644 --- a/dist/oauth-ng.js +++ b/dist/oauth-ng.js @@ -1,4 +1,4 @@ -/* oauth-ng - v0.2.8 - 2014-08-27 */ +/* oauth-ng - v0.2.8 - 2014-10-26 */ 'use strict'; @@ -43,7 +43,7 @@ accessTokenService.factory('AccessToken', function($rootScope, $location, $sessi * - takes the token from the sessionStorage */ service.set = function(){ - setTokenFromString($location.path().substr(1)); + setTokenFromString($location.hash()); //If hash is present in URL always use it, cuz its coming from oAuth2 provider redirect if(null === service.token){ @@ -198,17 +198,17 @@ endpointClient.factory('Endpoint', function(AccessToken, $location) { * Defines the authorization URL */ - service.set = function(scope) { - var oAuthScope = (scope.scope) ? scope.scope : '', - state = (scope.state) ? encodeURIComponent(scope.state) : '', - authPathHasQuery = (scope.authorizePath.indexOf('?') == -1) ? false : true, + service.set = function(params) { + var oAuthScope = (params.scope) ? params.scope : '', + state = (params.state) ? encodeURIComponent(params.state) : '', + authPathHasQuery = (params.authorizePath.indexOf('?') == -1) ? false : true, appendChar = (authPathHasQuery) ? '&' : '?'; //if authorizePath has ? already append OAuth2 params - url = scope.site + - scope.authorizePath + + url = params.site + + params.authorizePath + appendChar + 'response_type=token&' + - 'client_id=' + encodeURIComponent(scope.clientId) + '&' + - 'redirect_uri=' + encodeURIComponent(scope.redirectUri) + '&' + + 'client_id=' + encodeURIComponent(params.clientId) + '&' + + 'redirect_uri=' + encodeURIComponent(params.redirectUri) + '&' + 'scope=' + oAuthScope + '&' + 'state=' + state; @@ -324,17 +324,14 @@ directives.directive('oauth', function(AccessToken, Endpoint, Profile, $location initProfile(scope); // gets the profile resource (if existing the access token) initView(); // sets the view (logged in or out) }; - /** - * Check against undefined params, assign default if they are not present - */ - var initAttributes = function() { - scope.authorizePath = typeof(scope.authorizePath)!=="undefined" ?scope.authorizePath : '/oauth/authorize'; - scope.tokenPath = typeof(scope.tokenPath)!=="undefined" ?scope.tokenPath: '/oauth/token'; - scope.template = typeof(scope.template)!=="undefined"? scope.template: 'bower_components/oauth-ng/dist/views/templates/default.html'; - scope.text = typeof(scope.text)!=="undefined"?scope.text: 'Sign In'; - scope.state = typeof(scope.state)!=="undefined" ?scope.state : undefined; - scope.scope = typeof(scope.scope)!=="undefined" ?scope.scope : undefined; + var initAttributes = function() { + scope.authorizePath = scope.authorizePath || '/oauth/authorize'; + scope.tokenPath = scope.tokenPath || '/oauth/token'; + scope.template = scope.template || 'bower_components/oauth-ng/dist/views/templates/default.html'; + scope.text = scope.text || 'Sign In'; + scope.state = scope.state || undefined; + scope.scope = scope.scope || undefined; }; var compile = function() { @@ -350,7 +347,6 @@ directives.directive('oauth', function(AccessToken, Endpoint, Profile, $location if (token && token.access_token && scope.profileUri) { Profile.find(scope.profileUri).success(function(response) { scope.profile = response - $rootScope.$broadcast('oauth:profile', response); }) } }; From bea3e00458ad96b3a3eb020f6a2e5c8612629a6f Mon Sep 17 00:00:00 2001 From: Jean-Francois Turcot Date: Mon, 27 Oct 2014 22:10:12 -0400 Subject: [PATCH 011/107] Broadcasting oauth:logout when a user logs out manually and oauth:loggedOut when no access token --- app/scripts/directives/oauth.js | 4 ++-- test/spec/directives/oauth.js | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/app/scripts/directives/oauth.js b/app/scripts/directives/oauth.js index ad039c9..f47478d 100644 --- a/app/scripts/directives/oauth.js +++ b/app/scripts/directives/oauth.js @@ -74,7 +74,7 @@ directives.directive('oauth', function(AccessToken, Endpoint, Profile, $location scope.logout = function() { AccessToken.destroy(scope); - $rootScope.$broadcast('oauth:tokenDestroy'); + $rootScope.$broadcast('oauth:logout'); loggedOut(); }; @@ -86,7 +86,7 @@ directives.directive('oauth', function(AccessToken, Endpoint, Profile, $location // set the oauth directive to the logged-out status var loggedOut = function() { - $rootScope.$broadcast('oauth:logout'); + $rootScope.$broadcast('oauth:loggedOut'); scope.show = 'logged-out'; }; diff --git a/test/spec/directives/oauth.js b/test/spec/directives/oauth.js index 0f37632..1d4b2de 100644 --- a/test/spec/directives/oauth.js +++ b/test/spec/directives/oauth.js @@ -138,11 +138,11 @@ describe('oauth', function() { }); beforeEach(function() { - $rootScope.$on('oauth:tokenDestroy', callback); + $rootScope.$on('oauth:logout', callback); }); beforeEach(function() { - $rootScope.$on('oauth:logout', callback); + $rootScope.$on('oauth:loggedOut', callback); }); beforeEach(function() { @@ -154,7 +154,7 @@ describe('oauth', function() { expect(element.find('.logged-in').attr('class')).toMatch('ng-hide'); }); - it('fires the oauth:tokenDestroy and oauth:logout event', function() { + it('fires the oauth:logout and oauth:loggedOut event', function() { var event = jasmine.any(Object); expect(callback).toHaveBeenCalledWith(event); expect(callback.calls.count()).toBe(2); @@ -170,7 +170,7 @@ describe('oauth', function() { }); beforeEach(function() { - $rootScope.$on('oauth:logout', callback); + $rootScope.$on('oauth:loggedOut', callback); }); beforeEach(function() { @@ -200,12 +200,12 @@ describe('oauth', function() { expect(element.find('.logged-in').attr('class')).toMatch('ng-hide'); }); - it('fires the oauth:logout event', function() { + it('fires the oauth:loggedOut event', function() { var event = jasmine.any(Object); expect(callback).toHaveBeenCalledWith(event); }); - it('does not fire the oauth:tokenDestroy event', function() { + it('does not fire the oauth:logout event', function() { expect(callback.calls.count()).toBe(1); }); }); From 9a79fd57bd39f2d58ee0717318808be51fc007b1 Mon Sep 17 00:00:00 2001 From: Jean-Francois Turcot Date: Wed, 22 Oct 2014 18:21:45 -0400 Subject: [PATCH 012/107] Broadcast event oauth:tokenDestroy after a logout --- app/scripts/directives/oauth.js | 1 + test/spec/directives/oauth.js | 11 ++++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/app/scripts/directives/oauth.js b/app/scripts/directives/oauth.js index 08cf7c0..ad039c9 100644 --- a/app/scripts/directives/oauth.js +++ b/app/scripts/directives/oauth.js @@ -74,6 +74,7 @@ directives.directive('oauth', function(AccessToken, Endpoint, Profile, $location scope.logout = function() { AccessToken.destroy(scope); + $rootScope.$broadcast('oauth:tokenDestroy'); loggedOut(); }; diff --git a/test/spec/directives/oauth.js b/test/spec/directives/oauth.js index e5e34c8..0f37632 100644 --- a/test/spec/directives/oauth.js +++ b/test/spec/directives/oauth.js @@ -137,6 +137,10 @@ describe('oauth', function() { callback = jasmine.createSpy('callback'); }); + beforeEach(function() { + $rootScope.$on('oauth:tokenDestroy', callback); + }); + beforeEach(function() { $rootScope.$on('oauth:logout', callback); }); @@ -150,9 +154,10 @@ describe('oauth', function() { expect(element.find('.logged-in').attr('class')).toMatch('ng-hide'); }); - it('fires the oauth:logout event', function() { + it('fires the oauth:tokenDestroy and oauth:logout event', function() { var event = jasmine.any(Object); expect(callback).toHaveBeenCalledWith(event); + expect(callback.calls.count()).toBe(2); }); }); }); @@ -199,6 +204,10 @@ describe('oauth', function() { var event = jasmine.any(Object); expect(callback).toHaveBeenCalledWith(event); }); + + it('does not fire the oauth:tokenDestroy event', function() { + expect(callback.calls.count()).toBe(1); + }); }); From 7d0566489e8da4f4b6226cb5e3283c8025e613c0 Mon Sep 17 00:00:00 2001 From: Jean-Francois Turcot Date: Mon, 27 Oct 2014 22:10:12 -0400 Subject: [PATCH 013/107] Broadcasting oauth:logout when a user logs out manually and oauth:loggedOut when no access token --- app/scripts/directives/oauth.js | 4 ++-- test/spec/directives/oauth.js | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/app/scripts/directives/oauth.js b/app/scripts/directives/oauth.js index ad039c9..f47478d 100644 --- a/app/scripts/directives/oauth.js +++ b/app/scripts/directives/oauth.js @@ -74,7 +74,7 @@ directives.directive('oauth', function(AccessToken, Endpoint, Profile, $location scope.logout = function() { AccessToken.destroy(scope); - $rootScope.$broadcast('oauth:tokenDestroy'); + $rootScope.$broadcast('oauth:logout'); loggedOut(); }; @@ -86,7 +86,7 @@ directives.directive('oauth', function(AccessToken, Endpoint, Profile, $location // set the oauth directive to the logged-out status var loggedOut = function() { - $rootScope.$broadcast('oauth:logout'); + $rootScope.$broadcast('oauth:loggedOut'); scope.show = 'logged-out'; }; diff --git a/test/spec/directives/oauth.js b/test/spec/directives/oauth.js index 0f37632..1d4b2de 100644 --- a/test/spec/directives/oauth.js +++ b/test/spec/directives/oauth.js @@ -138,11 +138,11 @@ describe('oauth', function() { }); beforeEach(function() { - $rootScope.$on('oauth:tokenDestroy', callback); + $rootScope.$on('oauth:logout', callback); }); beforeEach(function() { - $rootScope.$on('oauth:logout', callback); + $rootScope.$on('oauth:loggedOut', callback); }); beforeEach(function() { @@ -154,7 +154,7 @@ describe('oauth', function() { expect(element.find('.logged-in').attr('class')).toMatch('ng-hide'); }); - it('fires the oauth:tokenDestroy and oauth:logout event', function() { + it('fires the oauth:logout and oauth:loggedOut event', function() { var event = jasmine.any(Object); expect(callback).toHaveBeenCalledWith(event); expect(callback.calls.count()).toBe(2); @@ -170,7 +170,7 @@ describe('oauth', function() { }); beforeEach(function() { - $rootScope.$on('oauth:logout', callback); + $rootScope.$on('oauth:loggedOut', callback); }); beforeEach(function() { @@ -200,12 +200,12 @@ describe('oauth', function() { expect(element.find('.logged-in').attr('class')).toMatch('ng-hide'); }); - it('fires the oauth:logout event', function() { + it('fires the oauth:loggedOut event', function() { var event = jasmine.any(Object); expect(callback).toHaveBeenCalledWith(event); }); - it('does not fire the oauth:tokenDestroy event', function() { + it('does not fire the oauth:logout event', function() { expect(callback.calls.count()).toBe(1); }); }); From e0039eb820b7a91d1b0489a4076e4cf222fb855f Mon Sep 17 00:00:00 2001 From: Jean-Francois Turcot Date: Tue, 28 Oct 2014 18:00:58 -0400 Subject: [PATCH 014/107] Building dist with the new logout and loggedOut broadcasted events --- dist/oauth-ng.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/dist/oauth-ng.js b/dist/oauth-ng.js index 1a409a0..b262f5e 100644 --- a/dist/oauth-ng.js +++ b/dist/oauth-ng.js @@ -1,4 +1,4 @@ -/* oauth-ng - v0.2.8 - 2014-10-26 */ +/* oauth-ng - v0.2.8 - 2014-10-28 */ 'use strict'; @@ -365,6 +365,7 @@ directives.directive('oauth', function(AccessToken, Endpoint, Profile, $location scope.logout = function() { AccessToken.destroy(scope); + $rootScope.$broadcast('oauth:logout'); loggedOut(); }; @@ -376,7 +377,7 @@ directives.directive('oauth', function(AccessToken, Endpoint, Profile, $location // set the oauth directive to the logged-out status var loggedOut = function() { - $rootScope.$broadcast('oauth:logout'); + $rootScope.$broadcast('oauth:loggedOut'); scope.show = 'logged-out'; }; From fe7a377d29ffaec80a24beadb745662ea3e9d635 Mon Sep 17 00:00:00 2001 From: Jean-Francois Turcot Date: Wed, 22 Oct 2014 18:21:45 -0400 Subject: [PATCH 015/107] Broadcast event oauth:tokenDestroy after a logout --- app/scripts/directives/oauth.js | 1 + test/spec/directives/oauth.js | 11 ++++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/app/scripts/directives/oauth.js b/app/scripts/directives/oauth.js index 08cf7c0..ad039c9 100644 --- a/app/scripts/directives/oauth.js +++ b/app/scripts/directives/oauth.js @@ -74,6 +74,7 @@ directives.directive('oauth', function(AccessToken, Endpoint, Profile, $location scope.logout = function() { AccessToken.destroy(scope); + $rootScope.$broadcast('oauth:tokenDestroy'); loggedOut(); }; diff --git a/test/spec/directives/oauth.js b/test/spec/directives/oauth.js index e5e34c8..0f37632 100644 --- a/test/spec/directives/oauth.js +++ b/test/spec/directives/oauth.js @@ -137,6 +137,10 @@ describe('oauth', function() { callback = jasmine.createSpy('callback'); }); + beforeEach(function() { + $rootScope.$on('oauth:tokenDestroy', callback); + }); + beforeEach(function() { $rootScope.$on('oauth:logout', callback); }); @@ -150,9 +154,10 @@ describe('oauth', function() { expect(element.find('.logged-in').attr('class')).toMatch('ng-hide'); }); - it('fires the oauth:logout event', function() { + it('fires the oauth:tokenDestroy and oauth:logout event', function() { var event = jasmine.any(Object); expect(callback).toHaveBeenCalledWith(event); + expect(callback.calls.count()).toBe(2); }); }); }); @@ -199,6 +204,10 @@ describe('oauth', function() { var event = jasmine.any(Object); expect(callback).toHaveBeenCalledWith(event); }); + + it('does not fire the oauth:tokenDestroy event', function() { + expect(callback.calls.count()).toBe(1); + }); }); From 1c51932c3a8d53b0f7f9065e31cc898f936540d9 Mon Sep 17 00:00:00 2001 From: Jean-Francois Turcot Date: Mon, 27 Oct 2014 22:10:12 -0400 Subject: [PATCH 016/107] Broadcasting oauth:logout when a user logs out manually and oauth:loggedOut when no access token --- app/scripts/directives/oauth.js | 4 ++-- test/spec/directives/oauth.js | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/app/scripts/directives/oauth.js b/app/scripts/directives/oauth.js index ad039c9..f47478d 100644 --- a/app/scripts/directives/oauth.js +++ b/app/scripts/directives/oauth.js @@ -74,7 +74,7 @@ directives.directive('oauth', function(AccessToken, Endpoint, Profile, $location scope.logout = function() { AccessToken.destroy(scope); - $rootScope.$broadcast('oauth:tokenDestroy'); + $rootScope.$broadcast('oauth:logout'); loggedOut(); }; @@ -86,7 +86,7 @@ directives.directive('oauth', function(AccessToken, Endpoint, Profile, $location // set the oauth directive to the logged-out status var loggedOut = function() { - $rootScope.$broadcast('oauth:logout'); + $rootScope.$broadcast('oauth:loggedOut'); scope.show = 'logged-out'; }; diff --git a/test/spec/directives/oauth.js b/test/spec/directives/oauth.js index 0f37632..1d4b2de 100644 --- a/test/spec/directives/oauth.js +++ b/test/spec/directives/oauth.js @@ -138,11 +138,11 @@ describe('oauth', function() { }); beforeEach(function() { - $rootScope.$on('oauth:tokenDestroy', callback); + $rootScope.$on('oauth:logout', callback); }); beforeEach(function() { - $rootScope.$on('oauth:logout', callback); + $rootScope.$on('oauth:loggedOut', callback); }); beforeEach(function() { @@ -154,7 +154,7 @@ describe('oauth', function() { expect(element.find('.logged-in').attr('class')).toMatch('ng-hide'); }); - it('fires the oauth:tokenDestroy and oauth:logout event', function() { + it('fires the oauth:logout and oauth:loggedOut event', function() { var event = jasmine.any(Object); expect(callback).toHaveBeenCalledWith(event); expect(callback.calls.count()).toBe(2); @@ -170,7 +170,7 @@ describe('oauth', function() { }); beforeEach(function() { - $rootScope.$on('oauth:logout', callback); + $rootScope.$on('oauth:loggedOut', callback); }); beforeEach(function() { @@ -200,12 +200,12 @@ describe('oauth', function() { expect(element.find('.logged-in').attr('class')).toMatch('ng-hide'); }); - it('fires the oauth:logout event', function() { + it('fires the oauth:loggedOut event', function() { var event = jasmine.any(Object); expect(callback).toHaveBeenCalledWith(event); }); - it('does not fire the oauth:tokenDestroy event', function() { + it('does not fire the oauth:logout event', function() { expect(callback.calls.count()).toBe(1); }); }); From bf928653d3466b0acb06d367f2fd06f5c0e6fb19 Mon Sep 17 00:00:00 2001 From: Jean-Francois Turcot Date: Tue, 28 Oct 2014 18:00:58 -0400 Subject: [PATCH 017/107] Building dist with the new logout and loggedOut broadcasted events --- dist/oauth-ng.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/dist/oauth-ng.js b/dist/oauth-ng.js index 1a409a0..b262f5e 100644 --- a/dist/oauth-ng.js +++ b/dist/oauth-ng.js @@ -1,4 +1,4 @@ -/* oauth-ng - v0.2.8 - 2014-10-26 */ +/* oauth-ng - v0.2.8 - 2014-10-28 */ 'use strict'; @@ -365,6 +365,7 @@ directives.directive('oauth', function(AccessToken, Endpoint, Profile, $location scope.logout = function() { AccessToken.destroy(scope); + $rootScope.$broadcast('oauth:logout'); loggedOut(); }; @@ -376,7 +377,7 @@ directives.directive('oauth', function(AccessToken, Endpoint, Profile, $location // set the oauth directive to the logged-out status var loggedOut = function() { - $rootScope.$broadcast('oauth:logout'); + $rootScope.$broadcast('oauth:loggedOut'); scope.show = 'logged-out'; }; From 01afbf69cdbe9a77afb39d0a7b8da7f5d52a40f7 Mon Sep 17 00:00:00 2001 From: Andy Buecker Date: Wed, 29 Oct 2014 09:16:09 -0700 Subject: [PATCH 018/107] fix(AccessToken): Added token query based on $location.path() --- dist/oauth-ng.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dist/oauth-ng.js b/dist/oauth-ng.js index 1a409a0..0507eca 100644 --- a/dist/oauth-ng.js +++ b/dist/oauth-ng.js @@ -43,7 +43,7 @@ accessTokenService.factory('AccessToken', function($rootScope, $location, $sessi * - takes the token from the sessionStorage */ service.set = function(){ - setTokenFromString($location.hash()); + setTokenFromString($location.path().substr(1)); //If hash is present in URL always use it, cuz its coming from oAuth2 provider redirect if(null === service.token){ From 846c660fadc7504104ad7b81f8135aefbf4c52f0 Mon Sep 17 00:00:00 2001 From: Andrea Reginato Date: Wed, 29 Oct 2014 19:32:41 +0100 Subject: [PATCH 019/107] Bump to v0.2.9 --- CHANGELOG.md | 4 ++++ bower.json | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 99d20fd..750d697 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 0.2.9 (August 27, 2014) + +* Correctly running tests with E2E protractor + ## 0.2.8 (August 27, 2014) * Fixed `expries_at` not being set in some situations diff --git a/bower.json b/bower.json index 2238de6..ac1f992 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "oauth-ng", - "version": "0.2.8", + "version": "0.2.9", "main": [ "dist/oauth-ng.js", "dist/views/templates/default" From c673466dd444a6f816e2d30bfacc0575c2eee386 Mon Sep 17 00:00:00 2001 From: Sebastien Errien Date: Thu, 30 Oct 2014 14:37:26 +0100 Subject: [PATCH 020/107] Make AccessToken method setTokenFromString public for HTML5 mode OFF --- app/scripts/services/access-token.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/app/scripts/services/access-token.js b/app/scripts/services/access-token.js index 0d62517..686de0a 100644 --- a/app/scripts/services/access-token.js +++ b/app/scripts/services/access-token.js @@ -25,7 +25,7 @@ accessTokenService.factory('AccessToken', function($rootScope, $location, $sessi * - takes the token from the sessionStorage */ service.set = function(){ - setTokenFromString($location.hash()); + this.setTokenFromString($location.hash()); //If hash is present in URL always use it, cuz its coming from oAuth2 provider redirect if(null === service.token){ @@ -54,15 +54,11 @@ accessTokenService.factory('AccessToken', function($rootScope, $location, $sessi }; - /* * * * * * * * * * - * PRIVATE METHODS * - * * * * * * * * * */ - /** * Get the access token from a string and save it * @param hash */ - var setTokenFromString = function(hash){ + service.setTokenFromString = function(hash){ var params = getTokenFromString(hash); if(params){ @@ -73,6 +69,11 @@ accessTokenService.factory('AccessToken', function($rootScope, $location, $sessi } }; + + /* * * * * * * * * * + * PRIVATE METHODS * + * * * * * * * * * */ + /** * Set the access token from the sessionStorage. */ From e1b5f63d73113f89a902ad66c7373a8584465209 Mon Sep 17 00:00:00 2001 From: Andy Buecker Date: Thu, 30 Oct 2014 11:24:26 -0700 Subject: [PATCH 021/107] fix(protractor): Replace $timeout with $interval --- app/scripts/services/access-token.js | 6 +++--- dist/oauth-ng.js | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/scripts/services/access-token.js b/app/scripts/services/access-token.js index 686de0a..b6ffc7e 100644 --- a/app/scripts/services/access-token.js +++ b/app/scripts/services/access-token.js @@ -2,7 +2,7 @@ var accessTokenService = angular.module('oauth.accessToken', ['ngStorage']); -accessTokenService.factory('AccessToken', function($rootScope, $location, $sessionStorage, $timeout){ +accessTokenService.factory('AccessToken', function($rootScope, $location, $sessionStorage, $interval){ var service = { token: null @@ -144,9 +144,9 @@ accessTokenService.factory('AccessToken', function($rootScope, $location, $sessi var setExpiresAtEvent = function(){ var time = (new Date(service.token.expires_at))-(new Date()); if(time){ - $timeout(function(){ + $interval(function(){ $rootScope.$broadcast('oauth:expired', service.token) - }, time) + }, time, 1) } }; diff --git a/dist/oauth-ng.js b/dist/oauth-ng.js index 84f02c5..3182e8c 100644 --- a/dist/oauth-ng.js +++ b/dist/oauth-ng.js @@ -20,7 +20,7 @@ angular.module('oauth').config(['$locationProvider','$httpProvider', var accessTokenService = angular.module('oauth.accessToken', ['ngStorage']); -accessTokenService.factory('AccessToken', function($rootScope, $location, $sessionStorage, $timeout){ +accessTokenService.factory('AccessToken', function($rootScope, $location, $sessionStorage, $interval){ var service = { token: null @@ -162,9 +162,9 @@ accessTokenService.factory('AccessToken', function($rootScope, $location, $sessi var setExpiresAtEvent = function(){ var time = (new Date(service.token.expires_at))-(new Date()); if(time){ - $timeout(function(){ + $interval(function(){ $rootScope.$broadcast('oauth:expired', service.token) - }, time) + }, time, 1) } }; From 1a1243b6cd064288aa3a9fed8c12846c35e23fe6 Mon Sep 17 00:00:00 2001 From: Andy Buecker Date: Thu, 30 Oct 2014 11:47:13 -0700 Subject: [PATCH 022/107] =?UTF-8?q?fix(profile):=20Add=20broadcast=20?= =?UTF-8?q?=E2=80=9Coath:profile=E2=80=9D=20once=20profile=20is=20retrieve?= =?UTF-8?q?d.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/scripts/services/profile.js | 7 +- dist/oauth-ng.js | 7 +- test/spec/services/profile.js | 223 +++++++++++++++++--------------- 3 files changed, 129 insertions(+), 108 deletions(-) diff --git a/app/scripts/services/profile.js b/app/scripts/services/profile.js index 671136a..48990bb 100644 --- a/app/scripts/services/profile.js +++ b/app/scripts/services/profile.js @@ -2,13 +2,16 @@ var profileClient = angular.module('oauth.profile', []) -profileClient.factory('Profile', function($http, AccessToken) { +profileClient.factory('Profile', function($http, AccessToken, $rootScope) { var service = {}; var profile; service.find = function(uri) { var promise = $http.get(uri, { headers: headers() }); - promise.success(function(response) { profile = response }); + promise.success(function(response) { + profile = response; + $rootScope.$broadcast('oauth:profile', profile); + }); return promise; }; diff --git a/dist/oauth-ng.js b/dist/oauth-ng.js index 84f02c5..5a2b2b0 100644 --- a/dist/oauth-ng.js +++ b/dist/oauth-ng.js @@ -240,13 +240,16 @@ endpointClient.factory('Endpoint', function(AccessToken, $location) { var profileClient = angular.module('oauth.profile', []) -profileClient.factory('Profile', function($http, AccessToken) { +profileClient.factory('Profile', function($http, AccessToken, $rootScope) { var service = {}; var profile; service.find = function(uri) { var promise = $http.get(uri, { headers: headers() }); - promise.success(function(response) { profile = response }); + promise.success(function(response) { + profile = response; + $rootScope.$broadcast('oauth:profile', profile); + }); return promise; }; diff --git a/test/spec/services/profile.js b/test/spec/services/profile.js index 3eedbe9..f080a4b 100644 --- a/test/spec/services/profile.js +++ b/test/spec/services/profile.js @@ -1,105 +1,120 @@ -//'use strict'; +'use strict'; -//describe('Profile', function() { - - //var $rootScope, $location, $httpBackend, $http, AccessToken, Profile; - //var result, date, callback; - - //var fragment = 'access_token=token&token_type=bearer&expires_in=7200&state=/path'; - //var headers = { 'Accept': 'application/json, text/plain, */*', 'Authorization': 'Bearer token' } - //var params = { site: 'http://example.com/', client: 'client-id', redirect: 'http://example.com/redirect', scope: 'scope', profileUri: 'http://example.com/me' }; - //var resource = { id: '1', name: 'Alice' }; - - //beforeEach(module('oauth')); - - //beforeEach(inject(function($injector) { $rootScope = $injector.get('$rootScope') })); - //beforeEach(inject(function($injector) { $location = $injector.get('$location') })); - //beforeEach(inject(function($injector) { $httpBackend = $injector.get('$httpBackend') })); - //beforeEach(inject(function($injector) { $http = $injector.get('$http') })); - //beforeEach(inject(function($injector) { AccessToken = $injector.get('AccessToken') })); - //beforeEach(inject(function($injector) { Profile = $injector.get('Profile') })); - - //beforeEach(function() { callback = jasmine.createSpy('callback') }); - - - //describe('.get', function() { - - //describe('when authenticated', function() { - - //beforeEach(function() { - //$location.hash(fragment); - //AccessToken.set(params); - //}); - - //beforeEach(function() { - //$httpBackend.whenGET('http://example.com/me', headers).respond(resource); - //}); - - //describe('when gets the profile', function() { - - //it('makes the request', function() { - //$httpBackend.expect('GET', 'http://example.com/me'); - //Profile.find(params.profileUri); - //$rootScope.$apply(); - //$httpBackend.flush(); - //}); - - //it('gets the resource', inject(function(Profile) { - //Profile.find(params.profileUri).success(function(response) { result = response }); - //$rootScope.$apply(); - //$httpBackend.flush(); - //expect(result.name).toEqual('Alice'); - //})); - - //it('caches the profile', function() { - //Profile.find(params.profileUri); - //$rootScope.$apply(); - //$httpBackend.flush(); - //expect(Profile.get().name).toEqual('Alice'); - //}); - - //describe('when expired', function() { - - //beforeEach(function() { - //$rootScope.$on('oauth:expired', callback); - //}); - - //beforeEach(function() { - //date = new Date(); - //date.setTime(date.getTime() + 86400000); - //}); - - //beforeEach(function() { - //Timecop.install(); - //Timecop.travel(date); // go one day in the future - //}); - - //afterEach(function() { - //Timecop.uninstall(); - //}); - - //it('fires the oauth:expired event', inject(function(Profile) { - //Profile.find(params.profileUri); - //$rootScope.$apply(); - //$httpBackend.flush(); - //var event = jasmine.any(Object); - //var token = jasmine.any(Object); - //expect(callback).toHaveBeenCalledWith(event, token); - //})); - //}); - //}); - - - //describe('when sets the profile', function() { - - //beforeEach(function() { - //Profile.set(resource); - //}); - - //it('caches the profile', function() { - //expect(Profile.get().name).toEqual('Alice'); - //}); - //}); - //}); - //}); -//}); +describe('Profile', function() { + + var $rootScope, $location, $httpBackend, $http, AccessToken, Profile; + var result, date, callback; + + var fragment = 'access_token=token&token_type=bearer&expires_in=7200&state=/path'; + var headers = { 'Accept': 'application/json, text/plain, */*', 'Authorization': 'Bearer token' } + var params = { site: 'http://example.com/', client: 'client-id', redirect: 'http://example.com/redirect', scope: 'scope', profileUri: 'http://example.com/me' }; + var resource = { id: '1', name: 'Alice' }; + + beforeEach(module('oauth')); + + beforeEach(inject(function($injector) { $rootScope = $injector.get('$rootScope') })); + beforeEach(inject(function($injector) { $location = $injector.get('$location') })); + beforeEach(inject(function($injector) { $httpBackend = $injector.get('$httpBackend') })); + beforeEach(inject(function($injector) { $http = $injector.get('$http') })); + beforeEach(inject(function($injector) { AccessToken = $injector.get('AccessToken') })); + beforeEach(inject(function($injector) { Profile = $injector.get('Profile') })); + + beforeEach(function() { callback = jasmine.createSpy('callback') }); + + + describe('.get', function() { + + describe('when authenticated', function() { + + beforeEach(function() { + $location.hash(fragment); + AccessToken.set(params); + }); + + beforeEach(function() { + $httpBackend.whenGET('http://example.com/me', headers).respond(resource); + }); + + describe('when gets the profile', function() { + + it('makes the request', function() { + $httpBackend.expect('GET', 'http://example.com/me'); + Profile.find(params.profileUri); + $rootScope.$apply(); + $httpBackend.flush(); + }); + + it('gets the resource', inject(function(Profile) { + Profile.find(params.profileUri).success(function(response) { result = response }); + $rootScope.$apply(); + $httpBackend.flush(); + expect(result.name).toEqual('Alice'); + })); + + it('caches the profile', function() { + Profile.find(params.profileUri); + $rootScope.$apply(); + $httpBackend.flush(); + expect(Profile.get().name).toEqual('Alice'); + }); + + it('fires oauth:profile event', function(done) { + + $rootScope.$on('oauth:profile', function (event, profile) { + expect(typeof profile).toBe('object'); + done(); + }); + + Profile.find(params.profileUri); + $rootScope.$apply(); + $httpBackend.flush(); + + }); + + + describe('when expired', function() { + + beforeEach(function() { + $rootScope.$on('oauth:expired', callback); + }); + + beforeEach(function() { + date = new Date(); + date.setTime(date.getTime() + 86400000); + }); + + beforeEach(function() { + Timecop.install(); + Timecop.travel(date); // go one day in the future + }); + + afterEach(function() { + Timecop.uninstall(); + }); + + it('fires the oauth:expired event', inject(function(Profile) { + Profile.find(params.profileUri); + $rootScope.$apply(); + $httpBackend.flush(); + var event = jasmine.any(Object); + var token = jasmine.any(Object); + expect(callback).toHaveBeenCalledWith(event, token); + })); + }); + }); + + + describe('when sets the profile', function() { + + beforeEach(function() { + Profile.set(resource); + }); + + it('caches the profile', function() { + expect(Profile.get().name).toEqual('Alice'); + }); + + }); + }); + }); +}); From 0bedc946c0ddfa0b158933c50482e4bbb9315ffe Mon Sep 17 00:00:00 2001 From: Andrea Reginato Date: Mon, 3 Nov 2014 20:54:13 +0100 Subject: [PATCH 023/107] Bump to 0.3.1 --- CHANGELOG.md | 6 ++++++ bower.json | 6 +++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0473ba1..7df809c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog + +## 0.3.1 (November 3, 2014) + +* Replace $timeout with $interval #50 +* Add broadcast “oath:profile” once profile is retrieved. #51 + ## 0.3.0 (October 30, 2014) * Solved bug on access token definition from hash diff --git a/bower.json b/bower.json index 0460266..c315e84 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "oauth-ng", - "version": "0.3.0", + "version": "0.3.1", "main": [ "dist/oauth-ng.js", "dist/views/templates/default" @@ -14,7 +14,7 @@ "package.json" ], "dependencies": { - "angular": "~1.2.22", + "angular": "~1.2.26", "ngstorage": "~0.3.0" }, "devDependencies": { @@ -23,6 +23,6 @@ "timecop": "~0.1.1", "bootstrap-sass": "~3.0.2", "jquery": "~1.9.1", - "angular-mocks": "~1.2.22" + "angular-mocks": "~1.2.26" } } From 5117f2e8ce33e04fa1acfe6b9df6f7a6ff98c278 Mon Sep 17 00:00:00 2001 From: Andy Buecker Date: Mon, 3 Nov 2014 15:37:31 -0800 Subject: [PATCH 024/107] fix(test): Get Travis CI working --- .travis.yml | 4 ---- Gruntfile.js | 17 +++++++++++++++-- README.md | 2 +- package.json | 26 +++++++++++++++----------- 4 files changed, 31 insertions(+), 18 deletions(-) diff --git a/.travis.yml b/.travis.yml index 83f4e22..244b7e8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,3 @@ language: node_js node_js: - - '0.8' - '0.10' -before_script: - - 'npm install -g bower grunt-cli' - - 'bower install' diff --git a/Gruntfile.js b/Gruntfile.js index bd21cae..51afe4b 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -229,9 +229,14 @@ module.exports = function (grunt) { // Test settings karma: { - unit: { + options: { configFile: 'karma.conf.js', + }, + unit: { singleRun: false + }, + once: { + singleRun: true } }, @@ -278,7 +283,15 @@ module.exports = function (grunt) { 'concurrent:test', 'autoprefixer', 'connect:test', - 'karma' + 'karma:once' + ]); + + grunt.registerTask('test:unit', [ + 'clean:server', + 'concurrent:test', + 'autoprefixer', + 'connect:test', + 'karma:unit' ]); grunt.registerTask('build', [ diff --git a/README.md b/README.md index ffabaae..8972a69 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# AngularJS directive for OAuth 2.0 +# AngularJS directive for OAuth 2.0 [![Build Status](https://travis-ci.org/andreareginato/oauth-ng.svg?branch=master)](https://travis-ci.org/andreareginato/oauth-ng) AngularJS directive for the [OAuth 2.0 Implicit Flow](http://tools.ietf.org/html/rfc6749#section-1.3.2). diff --git a/package.json b/package.json index bdbbff7..f3bd60f 100644 --- a/package.json +++ b/package.json @@ -9,9 +9,11 @@ }, "dependencies": {}, "devDependencies": { + "bower": "^1.3.12", "grunt": "~0.4.1", "grunt-autoprefixer": "~0.4.0", "grunt-bower-install": "~0.7.0", + "grunt-cli": "^0.1.13", "grunt-concurrent": "~0.4.1", "grunt-contrib-clean": "~0.5.0", "grunt-contrib-coffee": "~0.7.0", @@ -26,30 +28,32 @@ "grunt-contrib-uglify": "~0.2.0", "grunt-contrib-watch": "~0.5.2", "grunt-google-cdn": "~0.2.0", + "grunt-karma": "~0.8.0", "grunt-newer": "~0.5.4", "grunt-ngmin": "~0.0.2", + "grunt-protractor-runner": "~0.2.4", + "grunt-replace": "~0.7.7", "grunt-rev": "~0.1.0", + "grunt-string-replace": "~0.2.7", "grunt-svgmin": "~0.2.0", "grunt-usemin": "~2.0.0", "jshint-stylish": "~0.1.3", - "load-grunt-tasks": "~0.2.0", - "time-grunt": "~0.2.1", - "karma-ng-scenario": "~0.1.0", - "grunt-karma": "~0.8.0", "karma": "~0.12.0", - "karma-ng-html2js-preprocessor": "~0.1.0", - "karma-jasmine": "~0.2.2", - "karma-phantomjs-launcher": "~0.1.2", "karma-chrome-launcher": "~0.1.2", "karma-coverage": "~0.2.1", - "grunt-protractor-runner": "~0.2.4", - "grunt-replace": "~0.7.7", - "grunt-string-replace": "~0.2.7" + "karma-jasmine": "~0.2.2", + "karma-ng-html2js-preprocessor": "~0.1.0", + "karma-ng-scenario": "~0.1.0", + "karma-phantomjs-launcher": "~0.1.2", + "load-grunt-tasks": "~0.2.0", + "time-grunt": "~0.2.1" }, "engines": { "node": ">=0.8.0" }, "scripts": { - "test": "grunt test:unit" + "postinstall": "bower install --allow-root", + "clean": "rm -rf node_modules dist app/bower_components", + "test": "grunt test && grunt build" } } From 8ca6649c709b8ba8291e98435322c078e8b990c6 Mon Sep 17 00:00:00 2001 From: Andrea Reginato Date: Thu, 6 Nov 2014 08:19:50 +0100 Subject: [PATCH 025/107] Updated changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7df809c..7f3f53b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ * Replace $timeout with $interval #50 * Add broadcast “oath:profile” once profile is retrieved. #51 +* Added travis ## 0.3.0 (October 30, 2014) From dcbadce4908bcabb7922096495c4965de486a517 Mon Sep 17 00:00:00 2001 From: Raymond Date: Fri, 14 Nov 2014 12:31:25 +0100 Subject: [PATCH 026/107] I needed to implement the Authorization Code method, rather than Implicit. This requires the response_type to be set to 'code' rather than 'token'. I have now made this an optional attribute that defaults to 'token' --- dist/oauth-ng.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dist/oauth-ng.js b/dist/oauth-ng.js index 0d1972a..ed0ba32 100644 --- a/dist/oauth-ng.js +++ b/dist/oauth-ng.js @@ -207,7 +207,7 @@ endpointClient.factory('Endpoint', function(AccessToken, $location) { url = params.site + params.authorizePath + - appendChar + 'response_type=token&' + + appendChar + 'response_type='+params.responseType+'&' + 'client_id=' + encodeURIComponent(params.clientId) + '&' + 'redirect_uri=' + encodeURIComponent(params.redirectUri) + '&' + 'scope=' + oAuthScope + '&' + @@ -306,6 +306,7 @@ directives.directive('oauth', function(AccessToken, Endpoint, Profile, $location site: '@', // (required) set the oauth server host (e.g. http://oauth.example.com) clientId: '@', // (required) client id redirectUri: '@', // (required) client redirect uri + responseType: '@', // (optional) response type, defaults to token scope: '@', // (optional) scope profileUri: '@', // (optional) user profile uri (e.g http://example.com/me) template: '@', // (optional) template to render (e.g bower_components/oauth-ng/dist/views/templates/default.html) From 56a33f6501d44e1c5fa7dbd7d0d512f2c247e3a3 Mon Sep 17 00:00:00 2001 From: Raymond Date: Sun, 16 Nov 2014 20:57:05 +0100 Subject: [PATCH 027/107] updated readme --- README.md | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 8972a69..4ba1546 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,10 @@ # AngularJS directive for OAuth 2.0 [![Build Status](https://travis-ci.org/andreareginato/oauth-ng.svg?branch=master)](https://travis-ci.org/andreareginato/oauth-ng) -AngularJS directive for the [OAuth 2.0 Implicit Flow](http://tools.ietf.org/html/rfc6749#section-1.3.2). + +http://tools.ietf.org/html/rfc6749#section-4.1 + +AngularJS directive for the [OAuth 2.0 Authorization code Flow](http://tools.ietf.org/html/rfc6749#section-1.3.1) +and the [OAuth 2.0 Implicit Flow](http://tools.ietf.org/html/rfc6749#section-1.3.2). ## Documentation @@ -15,7 +19,24 @@ Please also update `gh-pages` branch with documentation when applicable. ### Setup * Fork and clone the repository -* Run `npm install && bower install` +* Run `npm install && bower install + +### OAuth 2.0 supported grant types + +We support both [OAuth 2.0 Authorization code Flow](http://tools.ietf.org/html/rfc6749#section-1.3.1) +and the [OAuth 2.0 Implicit Flow](http://tools.ietf.org/html/rfc6749#section-1.3.2). + +#### Authorization code flow + +See: http://tools.ietf.org/html/rfc6749#section-4.1 + +The use the Authorization code flow set response-type="code" in the oauth directive. + +#### Implicit flow + +See: http://tools.ietf.org/html/rfc6749#section-4.2 + +The use the Implicit flow set response-type="token" in the oauth directive. ### Unit tests (karma) From 6a904ed9a37588595625e7ef8507a3b608fb1a51 Mon Sep 17 00:00:00 2001 From: Raymond Date: Sun, 16 Nov 2014 20:58:39 +0100 Subject: [PATCH 028/107] updated readme --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 4ba1546..bd3385f 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,6 @@ # AngularJS directive for OAuth 2.0 [![Build Status](https://travis-ci.org/andreareginato/oauth-ng.svg?branch=master)](https://travis-ci.org/andreareginato/oauth-ng) -http://tools.ietf.org/html/rfc6749#section-4.1 - AngularJS directive for the [OAuth 2.0 Authorization code Flow](http://tools.ietf.org/html/rfc6749#section-1.3.1) and the [OAuth 2.0 Implicit Flow](http://tools.ietf.org/html/rfc6749#section-1.3.2). From 3d08f4bde3e67f0ccb3b6c0d4546f735dd58e0c0 Mon Sep 17 00:00:00 2001 From: Andrea Reginato Date: Sun, 16 Nov 2014 23:08:36 +0100 Subject: [PATCH 029/107] Bump to v0.3.2 --- CHANGELOG.md | 3 +++ bower.json | 2 +- package.json | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f3f53b..2735c81 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # Changelog +## 0.3.2 (November 16, 2014) + +* Authorization Code method option added ## 0.3.1 (November 3, 2014) diff --git a/bower.json b/bower.json index c315e84..cffc007 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "oauth-ng", - "version": "0.3.1", + "version": "0.3.2", "main": [ "dist/oauth-ng.js", "dist/views/templates/default" diff --git a/package.json b/package.json index f3bd60f..9bc8f05 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "oauth-ng", - "version": "0.2.8", + "version": "0.3.2", "author": "Andrea Reginato ", "description": "AngularJS Directive for OAuth 2.0", "repository": { From 6947b1c7088c34e833494fb14d0e972e0835b64c Mon Sep 17 00:00:00 2001 From: Andrea Reginato Date: Sun, 16 Nov 2014 23:10:10 +0100 Subject: [PATCH 030/107] Build v0.3.2 --- dist/oauth-ng.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/dist/oauth-ng.js b/dist/oauth-ng.js index ed0ba32..2e87219 100644 --- a/dist/oauth-ng.js +++ b/dist/oauth-ng.js @@ -1,4 +1,4 @@ -/* oauth-ng - v0.3.0 - 2014-10-30 */ +/* oauth-ng - v0.3.2 - 2014-11-16 */ 'use strict'; @@ -207,7 +207,7 @@ endpointClient.factory('Endpoint', function(AccessToken, $location) { url = params.site + params.authorizePath + - appendChar + 'response_type='+params.responseType+'&' + + appendChar + 'response_type=token&' + 'client_id=' + encodeURIComponent(params.clientId) + '&' + 'redirect_uri=' + encodeURIComponent(params.redirectUri) + '&' + 'scope=' + oAuthScope + '&' + @@ -306,7 +306,6 @@ directives.directive('oauth', function(AccessToken, Endpoint, Profile, $location site: '@', // (required) set the oauth server host (e.g. http://oauth.example.com) clientId: '@', // (required) client id redirectUri: '@', // (required) client redirect uri - responseType: '@', // (optional) response type, defaults to token scope: '@', // (optional) scope profileUri: '@', // (optional) user profile uri (e.g http://example.com/me) template: '@', // (optional) template to render (e.g bower_components/oauth-ng/dist/views/templates/default.html) From 1f603d43dcf6b8f5656966622195713af053189c Mon Sep 17 00:00:00 2001 From: Raymond Date: Fri, 21 Nov 2014 20:24:39 +0100 Subject: [PATCH 031/107] added the chances to support authorization flow again --- dist/oauth-ng.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dist/oauth-ng.js b/dist/oauth-ng.js index 2e87219..5a1f234 100644 --- a/dist/oauth-ng.js +++ b/dist/oauth-ng.js @@ -207,7 +207,7 @@ endpointClient.factory('Endpoint', function(AccessToken, $location) { url = params.site + params.authorizePath + - appendChar + 'response_type=token&' + + appendChar + 'response_type='+params.responseType+'&' + 'client_id=' + encodeURIComponent(params.clientId) + '&' + 'redirect_uri=' + encodeURIComponent(params.redirectUri) + '&' + 'scope=' + oAuthScope + '&' + @@ -306,6 +306,7 @@ directives.directive('oauth', function(AccessToken, Endpoint, Profile, $location site: '@', // (required) set the oauth server host (e.g. http://oauth.example.com) clientId: '@', // (required) client id redirectUri: '@', // (required) client redirect uri + responseType: '@', // (optional) response type, defaults to token (use 'token' for implicit flow and 'code' for authorization code flow scope: '@', // (optional) scope profileUri: '@', // (optional) user profile uri (e.g http://example.com/me) template: '@', // (optional) template to render (e.g bower_components/oauth-ng/dist/views/templates/default.html) From 60905df48cc65b9d6a67bfd9c8b214031685b94d Mon Sep 17 00:00:00 2001 From: Andrea Reginato Date: Tue, 25 Nov 2014 08:20:58 +0100 Subject: [PATCH 032/107] bump to v0.3.2 --- app/scripts/directives/oauth.js | 1 + app/scripts/services/endpoint.js | 2 +- dist/oauth-ng.js | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/app/scripts/directives/oauth.js b/app/scripts/directives/oauth.js index 08cf7c0..03e34a7 100644 --- a/app/scripts/directives/oauth.js +++ b/app/scripts/directives/oauth.js @@ -11,6 +11,7 @@ directives.directive('oauth', function(AccessToken, Endpoint, Profile, $location site: '@', // (required) set the oauth server host (e.g. http://oauth.example.com) clientId: '@', // (required) client id redirectUri: '@', // (required) client redirect uri + responseType: '@', // (optional) response type, defaults to token (use 'token' for implicit flow and 'code' for authorization code flow scope: '@', // (optional) scope profileUri: '@', // (optional) user profile uri (e.g http://example.com/me) template: '@', // (optional) template to render (e.g views/templates/default.html) diff --git a/app/scripts/services/endpoint.js b/app/scripts/services/endpoint.js index deb3c7f..f8cf253 100644 --- a/app/scripts/services/endpoint.js +++ b/app/scripts/services/endpoint.js @@ -20,7 +20,7 @@ endpointClient.factory('Endpoint', function(AccessToken, $location) { url = params.site + params.authorizePath + - appendChar + 'response_type=token&' + + appendChar + 'response_type='+params.responseType + '&' + 'client_id=' + encodeURIComponent(params.clientId) + '&' + 'redirect_uri=' + encodeURIComponent(params.redirectUri) + '&' + 'scope=' + oAuthScope + '&' + diff --git a/dist/oauth-ng.js b/dist/oauth-ng.js index 5a1f234..96f3670 100644 --- a/dist/oauth-ng.js +++ b/dist/oauth-ng.js @@ -1,4 +1,4 @@ -/* oauth-ng - v0.3.2 - 2014-11-16 */ +/* oauth-ng - v0.3.2 - 2014-11-25 */ 'use strict'; @@ -207,7 +207,7 @@ endpointClient.factory('Endpoint', function(AccessToken, $location) { url = params.site + params.authorizePath + - appendChar + 'response_type='+params.responseType+'&' + + appendChar + 'response_type='+params.responseType + '&' + 'client_id=' + encodeURIComponent(params.clientId) + '&' + 'redirect_uri=' + encodeURIComponent(params.redirectUri) + '&' + 'scope=' + oAuthScope + '&' + From 8a7e54202d3674d89c1180ecf46b776a9ae4adbb Mon Sep 17 00:00:00 2001 From: Andrea Reginato Date: Tue, 25 Nov 2014 08:22:32 +0100 Subject: [PATCH 033/107] bump to v0.3.3 --- CHANGELOG.md | 4 ++++ bower.json | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2735c81..5a3e7ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 0.3.3 (November 25, 2014) + +* Fixed Code method option added + ## 0.3.2 (November 16, 2014) * Authorization Code method option added diff --git a/bower.json b/bower.json index cffc007..4860e62 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "oauth-ng", - "version": "0.3.2", + "version": "0.3.3", "main": [ "dist/oauth-ng.js", "dist/views/templates/default" From a92571966af6327c5bf0bfbf761f08a32ba09f6b Mon Sep 17 00:00:00 2001 From: Andrea Reginato Date: Tue, 25 Nov 2014 08:32:44 +0100 Subject: [PATCH 034/107] Fixed tests on responseType param --- app/scripts/directives/oauth.js | 1 + app/scripts/services/endpoint.js | 2 +- bower.json | 2 +- test/spec/services/endpoint.js | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/app/scripts/directives/oauth.js b/app/scripts/directives/oauth.js index 03e34a7..adc89ae 100644 --- a/app/scripts/directives/oauth.js +++ b/app/scripts/directives/oauth.js @@ -39,6 +39,7 @@ directives.directive('oauth', function(AccessToken, Endpoint, Profile, $location scope.authorizePath = scope.authorizePath || '/oauth/authorize'; scope.tokenPath = scope.tokenPath || '/oauth/token'; scope.template = scope.template || 'views/templates/default.html'; + scope.responseType = scope.responseType || 'token'; scope.text = scope.text || 'Sign In'; scope.state = scope.state || undefined; scope.scope = scope.scope || undefined; diff --git a/app/scripts/services/endpoint.js b/app/scripts/services/endpoint.js index f8cf253..cd40c74 100644 --- a/app/scripts/services/endpoint.js +++ b/app/scripts/services/endpoint.js @@ -20,7 +20,7 @@ endpointClient.factory('Endpoint', function(AccessToken, $location) { url = params.site + params.authorizePath + - appendChar + 'response_type='+params.responseType + '&' + + appendChar + 'response_type='+ params.responseType + '&' + 'client_id=' + encodeURIComponent(params.clientId) + '&' + 'redirect_uri=' + encodeURIComponent(params.redirectUri) + '&' + 'scope=' + oAuthScope + '&' + diff --git a/bower.json b/bower.json index 4860e62..0968e0b 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "oauth-ng", - "version": "0.3.3", + "version": "0.3.4", "main": [ "dist/oauth-ng.js", "dist/views/templates/default" diff --git a/test/spec/services/endpoint.js b/test/spec/services/endpoint.js index de878f0..e044c1c 100644 --- a/test/spec/services/endpoint.js +++ b/test/spec/services/endpoint.js @@ -5,7 +5,7 @@ describe('Endpoint', function() { var result, $location, $sessionStorage, Endpoint; var fragment = 'access_token=token&token_type=bearer&expires_in=7200&state=/path'; - var params = { site: 'http://example.com/', clientId: 'client-id', redirectUri: 'http://example.com/redirect', scope: 'scope', authorizePath: '/oauth/authorize' }; + var params = { site: 'http://example.com/', clientId: 'client-id', redirectUri: 'http://example.com/redirect', scope: 'scope', authorizePath: '/oauth/authorize', responseType: 'token' }; var uri = 'http://example.com/oauth/authorize?response_type=token&client_id=client-id&redirect_uri=http%3A%2F%2Fexample.com%2Fredirect&scope=scope&state='; beforeEach(module('oauth')); From 3b5fcca7684923c954c6a7b72eb3474d86156262 Mon Sep 17 00:00:00 2001 From: Michael Carroll Date: Fri, 28 Nov 2014 23:15:43 -0500 Subject: [PATCH 035/107] Change dependencies to strings to prevent minification failures --- dist/oauth-ng.js | 355 ++++++++++++++++++++++++----------------------- 1 file changed, 180 insertions(+), 175 deletions(-) diff --git a/dist/oauth-ng.js b/dist/oauth-ng.js index 96f3670..581325b 100644 --- a/dist/oauth-ng.js +++ b/dist/oauth-ng.js @@ -20,7 +20,8 @@ angular.module('oauth').config(['$locationProvider','$httpProvider', var accessTokenService = angular.module('oauth.accessToken', ['ngStorage']); -accessTokenService.factory('AccessToken', function($rootScope, $location, $sessionStorage, $interval){ +accessTokenService.factory('AccessToken', [ '$rootScope', '$location', '$sessionStorage', '$interval', + function($rootScope, $location, $sessionStorage, $interval){ var service = { token: null @@ -183,226 +184,230 @@ accessTokenService.factory('AccessToken', function($rootScope, $location, $sessi return service; -}); +}]); 'use strict'; var endpointClient = angular.module('oauth.endpoint', []); -endpointClient.factory('Endpoint', function(AccessToken, $location) { +endpointClient.factory('Endpoint', ['AccessToken', '$location', + function(AccessToken, $location) { - var service = {}; - var url; + var service = {}; + var url; - /* - * Defines the authorization URL - */ - - service.set = function(params) { - var oAuthScope = (params.scope) ? params.scope : '', - state = (params.state) ? encodeURIComponent(params.state) : '', - authPathHasQuery = (params.authorizePath.indexOf('?') == -1) ? false : true, - appendChar = (authPathHasQuery) ? '&' : '?'; //if authorizePath has ? already append OAuth2 params - - url = params.site + - params.authorizePath + - appendChar + 'response_type='+params.responseType + '&' + - 'client_id=' + encodeURIComponent(params.clientId) + '&' + - 'redirect_uri=' + encodeURIComponent(params.redirectUri) + '&' + - 'scope=' + oAuthScope + '&' + - 'state=' + state; + /* + * Defines the authorization URL + */ - return url; - }; + service.set = function(params) { + var oAuthScope = (params.scope) ? params.scope : '', + state = (params.state) ? encodeURIComponent(params.state) : '', + authPathHasQuery = (params.authorizePath.indexOf('?') == -1) ? false : true, + appendChar = (authPathHasQuery) ? '&' : '?'; //if authorizePath has ? already append OAuth2 params + + url = params.site + + params.authorizePath + + appendChar + 'response_type='+params.responseType + '&' + + 'client_id=' + encodeURIComponent(params.clientId) + '&' + + 'redirect_uri=' + encodeURIComponent(params.redirectUri) + '&' + + 'scope=' + oAuthScope + '&' + + 'state=' + state; + + return url; + }; - /* - * Returns the authorization URL - */ + /* + * Returns the authorization URL + */ - service.get = function() { - return url; - }; + service.get = function() { + return url; + }; - /* - * Redirects the app to the authorization URL - */ + /* + * Redirects the app to the authorization URL + */ - service.redirect = function() { - window.location.replace(url); - }; + service.redirect = function() { + window.location.replace(url); + }; - return service; -}); + return service; +}]); 'use strict'; var profileClient = angular.module('oauth.profile', []) -profileClient.factory('Profile', function($http, AccessToken, $rootScope) { - var service = {}; - var profile; +profileClient.factory('Profile', [ '$http', 'AccessToken', '$rootScope', + function($http, AccessToken, $rootScope) { + var service = {}; + var profile; - service.find = function(uri) { - var promise = $http.get(uri, { headers: headers() }); - promise.success(function(response) { - profile = response; - $rootScope.$broadcast('oauth:profile', profile); - }); - return promise; - }; + service.find = function(uri) { + var promise = $http.get(uri, { headers: headers() }); + promise.success(function(response) { + profile = response; + $rootScope.$broadcast('oauth:profile', profile); + }); + return promise; + }; - service.get = function(uri) { - return profile; - }; + service.get = function(uri) { + return profile; + }; - service.set = function(resource) { - profile = resource; - return profile; - }; + service.set = function(resource) { + profile = resource; + return profile; + }; - var headers = function() { - return { Authorization: 'Bearer ' + AccessToken.get().access_token }; - }; + var headers = function() { + return { Authorization: 'Bearer ' + AccessToken.get().access_token }; + }; - return service; -}); + return service; +}]); 'use strict'; var interceptorService = angular.module('oauth.interceptor', []); -interceptorService.factory('ExpiredInterceptor', function ($rootScope, $q, $sessionStorage) { +interceptorService.factory('ExpiredInterceptor', [ '$rootScope', '$q', '$sessionStorage', + function ($rootScope, $q, $sessionStorage) { - var service = {}; + var service = {}; - service.request = function(config) { - var token = $sessionStorage.token; + service.request = function(config) { + var token = $sessionStorage.token; - if (token && expired(token)) - $rootScope.$broadcast('oauth:expired', token); + if (token && expired(token)) + $rootScope.$broadcast('oauth:expired', token); - return config; - }; + return config; + }; - var expired = function(token) { - return (token && token.expires_at && new Date(token.expires_at) < new Date()) - }; + var expired = function(token) { + return (token && token.expires_at && new Date(token.expires_at) < new Date()) + }; - return service; -}); + return service; +}]); 'use strict'; var directives = angular.module('oauth.directive', []); -directives.directive('oauth', function(AccessToken, Endpoint, Profile, $location, $rootScope, $compile, $http, $templateCache) { - - var definition = { - restrict: 'AE', - replace: true, - scope: { - site: '@', // (required) set the oauth server host (e.g. http://oauth.example.com) - clientId: '@', // (required) client id - redirectUri: '@', // (required) client redirect uri - responseType: '@', // (optional) response type, defaults to token (use 'token' for implicit flow and 'code' for authorization code flow - scope: '@', // (optional) scope - profileUri: '@', // (optional) user profile uri (e.g http://example.com/me) - template: '@', // (optional) template to render (e.g bower_components/oauth-ng/dist/views/templates/default.html) - text: '@', // (optional) login text - authorizePath: '@', // (optional) authorization url - state: '@' // (optional) An arbitrary unique string created by your app to guard against Cross-site Request Forgery - } - }; - - definition.link = function postLink(scope, element, attrs) { - scope.show = 'none'; - - scope.$watch('clientId', function(value) { init() }); - - var init = function() { - initAttributes(); // sets defaults - compile(); // compiles the desired layout - Endpoint.set(scope); // sets the oauth authorization url - AccessToken.set(scope); // sets the access token object (if existing, from fragment or session) - initProfile(scope); // gets the profile resource (if existing the access token) - initView(); // sets the view (logged in or out) - }; - - var initAttributes = function() { - scope.authorizePath = scope.authorizePath || '/oauth/authorize'; - scope.tokenPath = scope.tokenPath || '/oauth/token'; - scope.template = scope.template || 'bower_components/oauth-ng/dist/views/templates/default.html'; - scope.text = scope.text || 'Sign In'; - scope.state = scope.state || undefined; - scope.scope = scope.scope || undefined; - }; - - var compile = function() { - $http.get(scope.template, { cache: $templateCache }).success(function(html) { - element.html(html); - $compile(element.contents())(scope); - }); - }; - - var initProfile = function(scope) { - var token = AccessToken.get(); - - if (token && token.access_token && scope.profileUri) { - Profile.find(scope.profileUri).success(function(response) { - scope.profile = response - }) +directives.directive('oauth', [ 'AccessToken', 'Endpoint', 'Profile', '$location', '$rootScope', '$compile', '$http', '$templateCache', + function(AccessToken, Endpoint, Profile, $location, $rootScope, $compile, $http, $templateCache) { + + var definition = { + restrict: 'AE', + replace: true, + scope: { + site: '@', // (required) set the oauth server host (e.g. http://oauth.example.com) + clientId: '@', // (required) client id + redirectUri: '@', // (required) client redirect uri + responseType: '@', // (optional) response type, defaults to token (use 'token' for implicit flow and 'code' for authorization code flow + scope: '@', // (optional) scope + profileUri: '@', // (optional) user profile uri (e.g http://example.com/me) + template: '@', // (optional) template to render (e.g bower_components/oauth-ng/dist/views/templates/default.html) + text: '@', // (optional) login text + authorizePath: '@', // (optional) authorization url + state: '@' // (optional) An arbitrary unique string created by your app to guard against Cross-site Request Forgery } }; - var initView = function() { - var token = AccessToken.get(); - - if (!token) { return loggedOut() } // without access token it's logged out - if (token.access_token) { return authorized() } // if there is the access token we are done - if (token.error) { return denied() } // if the request has been denied we fire the denied event - }; - - scope.login = function() { - Endpoint.redirect(); - }; - - scope.logout = function() { - AccessToken.destroy(scope); - loggedOut(); - }; + definition.link = function postLink(scope, element, attrs) { + scope.show = 'none'; + + scope.$watch('clientId', function(value) { init() }); + + var init = function() { + initAttributes(); // sets defaults + compile(); // compiles the desired layout + Endpoint.set(scope); // sets the oauth authorization url + AccessToken.set(scope); // sets the access token object (if existing, from fragment or session) + initProfile(scope); // gets the profile resource (if existing the access token) + initView(); // sets the view (logged in or out) + }; + + var initAttributes = function() { + scope.authorizePath = scope.authorizePath || '/oauth/authorize'; + scope.tokenPath = scope.tokenPath || '/oauth/token'; + scope.template = scope.template || 'bower_components/oauth-ng/dist/views/templates/default.html'; + scope.text = scope.text || 'Sign In'; + scope.state = scope.state || undefined; + scope.scope = scope.scope || undefined; + }; + + var compile = function() { + $http.get(scope.template, { cache: $templateCache }).success(function(html) { + element.html(html); + $compile(element.contents())(scope); + }); + }; - // user is authorized - var authorized = function() { - $rootScope.$broadcast('oauth:authorized', AccessToken.get()); - scope.show = 'logged-in'; - }; + var initProfile = function(scope) { + var token = AccessToken.get(); - // set the oauth directive to the logged-out status - var loggedOut = function() { - $rootScope.$broadcast('oauth:logout'); - scope.show = 'logged-out'; - }; + if (token && token.access_token && scope.profileUri) { + Profile.find(scope.profileUri).success(function(response) { + scope.profile = response + }) + } + }; + + var initView = function() { + var token = AccessToken.get(); + + if (!token) { return loggedOut() } // without access token it's logged out + if (token.access_token) { return authorized() } // if there is the access token we are done + if (token.error) { return denied() } // if the request has been denied we fire the denied event + }; + + scope.login = function() { + Endpoint.redirect(); + }; + + scope.logout = function() { + AccessToken.destroy(scope); + loggedOut(); + }; + + // user is authorized + var authorized = function() { + $rootScope.$broadcast('oauth:authorized', AccessToken.get()); + scope.show = 'logged-in'; + }; + + // set the oauth directive to the logged-out status + var loggedOut = function() { + $rootScope.$broadcast('oauth:logout'); + scope.show = 'logged-out'; + }; + + // set the oauth directive to the denied status + var denied = function() { + scope.show = 'denied'; + $rootScope.$broadcast('oauth:denied'); + }; + + // Updates the template at runtime + scope.$on('oauth:template:update', function(event, template) { + scope.template = template; + compile(scope); + }); - // set the oauth directive to the denied status - var denied = function() { - scope.show = 'denied'; - $rootScope.$broadcast('oauth:denied'); + // Hack to update the directive content on logout + // TODO think to a cleaner solution + scope.$on('$routeChangeSuccess', function () { + init(); + }); }; - // Updates the template at runtime - scope.$on('oauth:template:update', function(event, template) { - scope.template = template; - compile(scope); - }); - - // Hack to update the directive content on logout - // TODO think to a cleaner solution - scope.$on('$routeChangeSuccess', function () { - init(); - }); - }; - - return definition -}); + return definition +}]); From cc3b6441bcb9d439955838eed9ec88164717978a Mon Sep 17 00:00:00 2001 From: Alexandre Fonseca Date: Sat, 29 Nov 2014 13:01:51 +0100 Subject: [PATCH 036/107] Act on token expiration by deleting token and updating directive UI. --- app/scripts/directives/oauth.js | 5 + dist/oauth-ng.js | 363 ++++++++++++++++---------------- 2 files changed, 187 insertions(+), 181 deletions(-) diff --git a/app/scripts/directives/oauth.js b/app/scripts/directives/oauth.js index adc89ae..557fad7 100644 --- a/app/scripts/directives/oauth.js +++ b/app/scripts/directives/oauth.js @@ -79,6 +79,11 @@ directives.directive('oauth', function(AccessToken, Endpoint, Profile, $location loggedOut(); }; + scope.$on('oauth:expired', function() { + AccessToken.destroy(scope); + scope.show = 'logged-out'; + }); + // user is authorized var authorized = function() { $rootScope.$broadcast('oauth:authorized', AccessToken.get()); diff --git a/dist/oauth-ng.js b/dist/oauth-ng.js index 581325b..766445d 100644 --- a/dist/oauth-ng.js +++ b/dist/oauth-ng.js @@ -1,4 +1,4 @@ -/* oauth-ng - v0.3.2 - 2014-11-25 */ +/* oauth-ng - v0.3.4 - 2014-11-29 */ 'use strict'; @@ -20,8 +20,7 @@ angular.module('oauth').config(['$locationProvider','$httpProvider', var accessTokenService = angular.module('oauth.accessToken', ['ngStorage']); -accessTokenService.factory('AccessToken', [ '$rootScope', '$location', '$sessionStorage', '$interval', - function($rootScope, $location, $sessionStorage, $interval){ +accessTokenService.factory('AccessToken', function($rootScope, $location, $sessionStorage, $interval){ var service = { token: null @@ -184,230 +183,232 @@ accessTokenService.factory('AccessToken', [ '$rootScope', '$location', '$session return service; -}]); +}); 'use strict'; var endpointClient = angular.module('oauth.endpoint', []); -endpointClient.factory('Endpoint', ['AccessToken', '$location', - function(AccessToken, $location) { +endpointClient.factory('Endpoint', function(AccessToken, $location) { - var service = {}; - var url; + var service = {}; + var url; - /* - * Defines the authorization URL - */ + /* + * Defines the authorization URL + */ - service.set = function(params) { - var oAuthScope = (params.scope) ? params.scope : '', - state = (params.state) ? encodeURIComponent(params.state) : '', - authPathHasQuery = (params.authorizePath.indexOf('?') == -1) ? false : true, - appendChar = (authPathHasQuery) ? '&' : '?'; //if authorizePath has ? already append OAuth2 params - - url = params.site + - params.authorizePath + - appendChar + 'response_type='+params.responseType + '&' + - 'client_id=' + encodeURIComponent(params.clientId) + '&' + - 'redirect_uri=' + encodeURIComponent(params.redirectUri) + '&' + - 'scope=' + oAuthScope + '&' + - 'state=' + state; - - return url; - }; + service.set = function(params) { + var oAuthScope = (params.scope) ? params.scope : '', + state = (params.state) ? encodeURIComponent(params.state) : '', + authPathHasQuery = (params.authorizePath.indexOf('?') == -1) ? false : true, + appendChar = (authPathHasQuery) ? '&' : '?'; //if authorizePath has ? already append OAuth2 params - /* - * Returns the authorization URL - */ + url = params.site + + params.authorizePath + + appendChar + 'response_type='+ params.responseType + '&' + + 'client_id=' + encodeURIComponent(params.clientId) + '&' + + 'redirect_uri=' + encodeURIComponent(params.redirectUri) + '&' + + 'scope=' + oAuthScope + '&' + + 'state=' + state; - service.get = function() { - return url; - }; + return url; + }; + /* + * Returns the authorization URL + */ - /* - * Redirects the app to the authorization URL - */ + service.get = function() { + return url; + }; - service.redirect = function() { - window.location.replace(url); - }; - return service; -}]); + /* + * Redirects the app to the authorization URL + */ + + service.redirect = function() { + window.location.replace(url); + }; + + return service; +}); 'use strict'; var profileClient = angular.module('oauth.profile', []) -profileClient.factory('Profile', [ '$http', 'AccessToken', '$rootScope', - function($http, AccessToken, $rootScope) { - var service = {}; - var profile; +profileClient.factory('Profile', function($http, AccessToken, $rootScope) { + var service = {}; + var profile; - service.find = function(uri) { - var promise = $http.get(uri, { headers: headers() }); - promise.success(function(response) { - profile = response; - $rootScope.$broadcast('oauth:profile', profile); - }); - return promise; - }; + service.find = function(uri) { + var promise = $http.get(uri, { headers: headers() }); + promise.success(function(response) { + profile = response; + $rootScope.$broadcast('oauth:profile', profile); + }); + return promise; + }; - service.get = function(uri) { - return profile; - }; + service.get = function(uri) { + return profile; + }; - service.set = function(resource) { - profile = resource; - return profile; - }; + service.set = function(resource) { + profile = resource; + return profile; + }; - var headers = function() { - return { Authorization: 'Bearer ' + AccessToken.get().access_token }; - }; + var headers = function() { + return { Authorization: 'Bearer ' + AccessToken.get().access_token }; + }; - return service; -}]); + return service; +}); 'use strict'; var interceptorService = angular.module('oauth.interceptor', []); -interceptorService.factory('ExpiredInterceptor', [ '$rootScope', '$q', '$sessionStorage', - function ($rootScope, $q, $sessionStorage) { +interceptorService.factory('ExpiredInterceptor', function ($rootScope, $q, $sessionStorage) { - var service = {}; + var service = {}; - service.request = function(config) { - var token = $sessionStorage.token; + service.request = function(config) { + var token = $sessionStorage.token; - if (token && expired(token)) - $rootScope.$broadcast('oauth:expired', token); + if (token && expired(token)) + $rootScope.$broadcast('oauth:expired', token); - return config; - }; + return config; + }; - var expired = function(token) { - return (token && token.expires_at && new Date(token.expires_at) < new Date()) - }; + var expired = function(token) { + return (token && token.expires_at && new Date(token.expires_at) < new Date()) + }; - return service; -}]); + return service; +}); 'use strict'; var directives = angular.module('oauth.directive', []); -directives.directive('oauth', [ 'AccessToken', 'Endpoint', 'Profile', '$location', '$rootScope', '$compile', '$http', '$templateCache', - function(AccessToken, Endpoint, Profile, $location, $rootScope, $compile, $http, $templateCache) { - - var definition = { - restrict: 'AE', - replace: true, - scope: { - site: '@', // (required) set the oauth server host (e.g. http://oauth.example.com) - clientId: '@', // (required) client id - redirectUri: '@', // (required) client redirect uri - responseType: '@', // (optional) response type, defaults to token (use 'token' for implicit flow and 'code' for authorization code flow - scope: '@', // (optional) scope - profileUri: '@', // (optional) user profile uri (e.g http://example.com/me) - template: '@', // (optional) template to render (e.g bower_components/oauth-ng/dist/views/templates/default.html) - text: '@', // (optional) login text - authorizePath: '@', // (optional) authorization url - state: '@' // (optional) An arbitrary unique string created by your app to guard against Cross-site Request Forgery +directives.directive('oauth', function(AccessToken, Endpoint, Profile, $location, $rootScope, $compile, $http, $templateCache) { + + var definition = { + restrict: 'AE', + replace: true, + scope: { + site: '@', // (required) set the oauth server host (e.g. http://oauth.example.com) + clientId: '@', // (required) client id + redirectUri: '@', // (required) client redirect uri + responseType: '@', // (optional) response type, defaults to token (use 'token' for implicit flow and 'code' for authorization code flow + scope: '@', // (optional) scope + profileUri: '@', // (optional) user profile uri (e.g http://example.com/me) + template: '@', // (optional) template to render (e.g bower_components/oauth-ng/dist/views/templates/default.html) + text: '@', // (optional) login text + authorizePath: '@', // (optional) authorization url + state: '@' // (optional) An arbitrary unique string created by your app to guard against Cross-site Request Forgery + } + }; + + definition.link = function postLink(scope, element, attrs) { + scope.show = 'none'; + + scope.$watch('clientId', function(value) { init() }); + + var init = function() { + initAttributes(); // sets defaults + compile(); // compiles the desired layout + Endpoint.set(scope); // sets the oauth authorization url + AccessToken.set(scope); // sets the access token object (if existing, from fragment or session) + initProfile(scope); // gets the profile resource (if existing the access token) + initView(); // sets the view (logged in or out) + }; + + var initAttributes = function() { + scope.authorizePath = scope.authorizePath || '/oauth/authorize'; + scope.tokenPath = scope.tokenPath || '/oauth/token'; + scope.template = scope.template || 'bower_components/oauth-ng/dist/views/templates/default.html'; + scope.responseType = scope.responseType || 'token'; + scope.text = scope.text || 'Sign In'; + scope.state = scope.state || undefined; + scope.scope = scope.scope || undefined; + }; + + var compile = function() { + $http.get(scope.template, { cache: $templateCache }).success(function(html) { + element.html(html); + $compile(element.contents())(scope); + }); + }; + + var initProfile = function(scope) { + var token = AccessToken.get(); + + if (token && token.access_token && scope.profileUri) { + Profile.find(scope.profileUri).success(function(response) { + scope.profile = response + }) } }; - definition.link = function postLink(scope, element, attrs) { - scope.show = 'none'; - - scope.$watch('clientId', function(value) { init() }); - - var init = function() { - initAttributes(); // sets defaults - compile(); // compiles the desired layout - Endpoint.set(scope); // sets the oauth authorization url - AccessToken.set(scope); // sets the access token object (if existing, from fragment or session) - initProfile(scope); // gets the profile resource (if existing the access token) - initView(); // sets the view (logged in or out) - }; - - var initAttributes = function() { - scope.authorizePath = scope.authorizePath || '/oauth/authorize'; - scope.tokenPath = scope.tokenPath || '/oauth/token'; - scope.template = scope.template || 'bower_components/oauth-ng/dist/views/templates/default.html'; - scope.text = scope.text || 'Sign In'; - scope.state = scope.state || undefined; - scope.scope = scope.scope || undefined; - }; - - var compile = function() { - $http.get(scope.template, { cache: $templateCache }).success(function(html) { - element.html(html); - $compile(element.contents())(scope); - }); - }; + var initView = function() { + var token = AccessToken.get(); - var initProfile = function(scope) { - var token = AccessToken.get(); + if (!token) { return loggedOut() } // without access token it's logged out + if (token.access_token) { return authorized() } // if there is the access token we are done + if (token.error) { return denied() } // if the request has been denied we fire the denied event + }; - if (token && token.access_token && scope.profileUri) { - Profile.find(scope.profileUri).success(function(response) { - scope.profile = response - }) - } - }; - - var initView = function() { - var token = AccessToken.get(); - - if (!token) { return loggedOut() } // without access token it's logged out - if (token.access_token) { return authorized() } // if there is the access token we are done - if (token.error) { return denied() } // if the request has been denied we fire the denied event - }; - - scope.login = function() { - Endpoint.redirect(); - }; - - scope.logout = function() { - AccessToken.destroy(scope); - loggedOut(); - }; - - // user is authorized - var authorized = function() { - $rootScope.$broadcast('oauth:authorized', AccessToken.get()); - scope.show = 'logged-in'; - }; - - // set the oauth directive to the logged-out status - var loggedOut = function() { - $rootScope.$broadcast('oauth:logout'); - scope.show = 'logged-out'; - }; - - // set the oauth directive to the denied status - var denied = function() { - scope.show = 'denied'; - $rootScope.$broadcast('oauth:denied'); - }; - - // Updates the template at runtime - scope.$on('oauth:template:update', function(event, template) { - scope.template = template; - compile(scope); - }); + scope.login = function() { + Endpoint.redirect(); + }; - // Hack to update the directive content on logout - // TODO think to a cleaner solution - scope.$on('$routeChangeSuccess', function () { - init(); - }); + scope.logout = function() { + AccessToken.destroy(scope); + loggedOut(); + }; + + scope.$on('oauth:expired', function() { + AccessToken.destroy(scope); + scope.show = 'logged-out'; + }); + + // user is authorized + var authorized = function() { + $rootScope.$broadcast('oauth:authorized', AccessToken.get()); + scope.show = 'logged-in'; + }; + + // set the oauth directive to the logged-out status + var loggedOut = function() { + $rootScope.$broadcast('oauth:logout'); + scope.show = 'logged-out'; + }; + + // set the oauth directive to the denied status + var denied = function() { + scope.show = 'denied'; + $rootScope.$broadcast('oauth:denied'); }; - return definition -}]); + // Updates the template at runtime + scope.$on('oauth:template:update', function(event, template) { + scope.template = template; + compile(scope); + }); + + // Hack to update the directive content on logout + // TODO think to a cleaner solution + scope.$on('$routeChangeSuccess', function () { + init(); + }); + }; + + return definition +}); From 7918bcc6af3fabaf6f6868660c46ead1f837f20b Mon Sep 17 00:00:00 2001 From: Andrea Reginato Date: Sat, 29 Nov 2014 14:36:25 +0100 Subject: [PATCH 037/107] Bump to v0.3.5 --- app/scripts/services/endpoint.js | 2 +- bower.json | 2 +- dist/oauth-ng.js | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/scripts/services/endpoint.js b/app/scripts/services/endpoint.js index cd40c74..8da04df 100644 --- a/app/scripts/services/endpoint.js +++ b/app/scripts/services/endpoint.js @@ -20,7 +20,7 @@ endpointClient.factory('Endpoint', function(AccessToken, $location) { url = params.site + params.authorizePath + - appendChar + 'response_type='+ params.responseType + '&' + + appendChar + 'response_type=' + params.responseType + '&' + 'client_id=' + encodeURIComponent(params.clientId) + '&' + 'redirect_uri=' + encodeURIComponent(params.redirectUri) + '&' + 'scope=' + oAuthScope + '&' + diff --git a/bower.json b/bower.json index 0968e0b..5983637 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "oauth-ng", - "version": "0.3.4", + "version": "0.3.5", "main": [ "dist/oauth-ng.js", "dist/views/templates/default" diff --git a/dist/oauth-ng.js b/dist/oauth-ng.js index 766445d..f7de1cb 100644 --- a/dist/oauth-ng.js +++ b/dist/oauth-ng.js @@ -1,4 +1,4 @@ -/* oauth-ng - v0.3.4 - 2014-11-29 */ +/* oauth-ng - v0.3.5 - 2014-11-29 */ 'use strict'; @@ -207,7 +207,7 @@ endpointClient.factory('Endpoint', function(AccessToken, $location) { url = params.site + params.authorizePath + - appendChar + 'response_type='+ params.responseType + '&' + + appendChar + 'response_type=' + params.responseType + '&' + 'client_id=' + encodeURIComponent(params.clientId) + '&' + 'redirect_uri=' + encodeURIComponent(params.redirectUri) + '&' + 'scope=' + oAuthScope + '&' + From 22296b927dd9f690f04c52470626af0000e306ca Mon Sep 17 00:00:00 2001 From: Andrea Reginato Date: Sat, 29 Nov 2014 14:37:52 +0100 Subject: [PATCH 038/107] Updated changelog v0.3.5 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a3e7ae..786167d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 0.3.5 (November 29, 2014) + +* Remove access token and change directive text to 'logout' when token is expired. + ## 0.3.3 (November 25, 2014) * Fixed Code method option added From 53c37e180a350c4f183372f6114f75dfe5c06dd9 Mon Sep 17 00:00:00 2001 From: Andrea Reginato Date: Wed, 3 Dec 2014 10:10:53 +0100 Subject: [PATCH 039/107] Bump to v0.3.6 --- CHANGELOG.md | 4 ++++ bower.json | 2 +- dist/oauth-ng.js | 2 +- package.json | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 786167d..59fd501 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 0.3.6 (Dicember 03, 2014) + +* Broadcast event oauth:tokenDestroy after a logout. + ## 0.3.5 (November 29, 2014) * Remove access token and change directive text to 'logout' when token is expired. diff --git a/bower.json b/bower.json index 5983637..3ddee69 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "oauth-ng", - "version": "0.3.5", + "version": "0.3.6", "main": [ "dist/oauth-ng.js", "dist/views/templates/default" diff --git a/dist/oauth-ng.js b/dist/oauth-ng.js index 8adc357..c8817ee 100644 --- a/dist/oauth-ng.js +++ b/dist/oauth-ng.js @@ -1,4 +1,4 @@ -/* oauth-ng - v0.3.5 - 2014-11-29 */ +/* oauth-ng - v0.3.6 - 2014-12-03 */ 'use strict'; diff --git a/package.json b/package.json index 9bc8f05..ec4beeb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "oauth-ng", - "version": "0.3.2", + "version": "0.3.6", "author": "Andrea Reginato ", "description": "AngularJS Directive for OAuth 2.0", "repository": { From 7bd4ea41c4c35a07d3f77847df905454f28b08f8 Mon Sep 17 00:00:00 2001 From: mattmomont Date: Wed, 4 Feb 2015 11:08:08 -0800 Subject: [PATCH 040/107] Update angular version && bumped up the version info --- bower.json | 6 +++--- package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/bower.json b/bower.json index 3ddee69..9bfa331 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "oauth-ng", - "version": "0.3.6", + "version": "0.3.7", "main": [ "dist/oauth-ng.js", "dist/views/templates/default" @@ -14,7 +14,7 @@ "package.json" ], "dependencies": { - "angular": "~1.2.26", + "angular": "~1.3.12", "ngstorage": "~0.3.0" }, "devDependencies": { @@ -23,6 +23,6 @@ "timecop": "~0.1.1", "bootstrap-sass": "~3.0.2", "jquery": "~1.9.1", - "angular-mocks": "~1.2.26" + "angular-mocks": "~1.3.12" } } diff --git a/package.json b/package.json index ec4beeb..55c94d9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "oauth-ng", - "version": "0.3.6", + "version": "0.3.7", "author": "Andrea Reginato ", "description": "AngularJS Directive for OAuth 2.0", "repository": { From 72b6d0da3d0928878c6d5f8cda9c7c4a15dc2bba Mon Sep 17 00:00:00 2001 From: Andrea Reginato Date: Fri, 6 Feb 2015 16:08:59 +0100 Subject: [PATCH 041/107] Upgraded to AngularJS v1.3.12 --- CHANGELOG.md | 4 ++++ bower.json | 2 +- dist/oauth-ng.js | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 59fd501..801e7ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 0.3.8 (February 06, 2015) + +* Upgrade to AngularJS v1.3.12. + ## 0.3.6 (Dicember 03, 2014) * Broadcast event oauth:tokenDestroy after a logout. diff --git a/bower.json b/bower.json index 9bfa331..19bcbb8 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "oauth-ng", - "version": "0.3.7", + "version": "0.3.8", "main": [ "dist/oauth-ng.js", "dist/views/templates/default" diff --git a/dist/oauth-ng.js b/dist/oauth-ng.js index c8817ee..e5df5a5 100644 --- a/dist/oauth-ng.js +++ b/dist/oauth-ng.js @@ -1,4 +1,4 @@ -/* oauth-ng - v0.3.6 - 2014-12-03 */ +/* oauth-ng - v0.3.8 - 2015-02-06 */ 'use strict'; From f800935fdd7d01b6b717316a53c1f1e0c4eb8866 Mon Sep 17 00:00:00 2001 From: Andrea Date: Wed, 8 Apr 2015 18:14:42 +0200 Subject: [PATCH 042/107] Inline annotations for dependency injection Switched to inline annotation as otherwise some types of minifcation would break the dependency injection. --- dist/oauth-ng.js | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/dist/oauth-ng.js b/dist/oauth-ng.js index e5df5a5..6cd9f66 100644 --- a/dist/oauth-ng.js +++ b/dist/oauth-ng.js @@ -20,7 +20,7 @@ angular.module('oauth').config(['$locationProvider','$httpProvider', var accessTokenService = angular.module('oauth.accessToken', ['ngStorage']); -accessTokenService.factory('AccessToken', function($rootScope, $location, $sessionStorage, $interval){ +accessTokenService.factory('AccessToken', ['$rootScope', '$location', '$sessionStorage', '$interval', function($rootScope, $location, $sessionStorage, $interval){ var service = { token: null @@ -183,13 +183,13 @@ accessTokenService.factory('AccessToken', function($rootScope, $location, $sessi return service; -}); +}]); 'use strict'; var endpointClient = angular.module('oauth.endpoint', []); -endpointClient.factory('Endpoint', function(AccessToken, $location) { +endpointClient.factory('Endpoint', ['AccessToken', '$location', function(AccessToken, $location) { var service = {}; var url; @@ -234,13 +234,13 @@ endpointClient.factory('Endpoint', function(AccessToken, $location) { }; return service; -}); +}]); 'use strict'; var profileClient = angular.module('oauth.profile', []) -profileClient.factory('Profile', function($http, AccessToken, $rootScope) { +profileClient.factory('Profile', ['$http', 'AccessToken', '$rootScope', function($http, AccessToken, $rootScope) { var service = {}; var profile; @@ -267,13 +267,13 @@ profileClient.factory('Profile', function($http, AccessToken, $rootScope) { }; return service; -}); +}]); 'use strict'; var interceptorService = angular.module('oauth.interceptor', []); -interceptorService.factory('ExpiredInterceptor', function ($rootScope, $q, $sessionStorage) { +interceptorService.factory('ExpiredInterceptor', ['$rootScope', '$q', '$sessionStorage', function ($rootScope, $q, $sessionStorage) { var service = {}; @@ -291,13 +291,13 @@ interceptorService.factory('ExpiredInterceptor', function ($rootScope, $q, $sess }; return service; -}); +}]); 'use strict'; var directives = angular.module('oauth.directive', []); -directives.directive('oauth', function(AccessToken, Endpoint, Profile, $location, $rootScope, $compile, $http, $templateCache) { +directives.directive('oauth', ['AccessToken', 'Endpoint', 'Profile', '$location', '$rootScope', '$compile', '$http', '$templateCache', function(AccessToken, Endpoint, Profile, $location, $rootScope, $compile, $http, $templateCache) { var definition = { restrict: 'AE', @@ -412,4 +412,4 @@ directives.directive('oauth', function(AccessToken, Endpoint, Profile, $location }; return definition -}); +}]); From 51ed32dc89ff39865282c011a397b712051b8f16 Mon Sep 17 00:00:00 2001 From: Massimiliano Sartoretto Date: Tue, 14 Apr 2015 22:29:50 +0200 Subject: [PATCH 043/107] Add inline annotations for DI --- app/scripts/app.js | 2 +- app/scripts/directives/oauth.js | 13 +++++++++++-- app/scripts/interceptors/oauth-interceptor.js | 4 ++-- app/scripts/services/access-token.js | 4 ++-- app/scripts/services/endpoint.js | 2 +- app/scripts/services/profile.js | 4 ++-- dist/oauth-ng.js | 17 +++++++++++++---- package.json | 2 +- 8 files changed, 33 insertions(+), 15 deletions(-) diff --git a/app/scripts/app.js b/app/scripts/app.js index fdc751d..86287d8 100644 --- a/app/scripts/app.js +++ b/app/scripts/app.js @@ -12,4 +12,4 @@ var app = angular.module('oauth', [ angular.module('oauth').config(['$locationProvider','$httpProvider', function($locationProvider, $httpProvider) { $httpProvider.interceptors.push('ExpiredInterceptor'); - }]); + }]); \ No newline at end of file diff --git a/app/scripts/directives/oauth.js b/app/scripts/directives/oauth.js index cfa96f7..b376b5f 100644 --- a/app/scripts/directives/oauth.js +++ b/app/scripts/directives/oauth.js @@ -2,7 +2,16 @@ var directives = angular.module('oauth.directive', []); -directives.directive('oauth', function(AccessToken, Endpoint, Profile, $location, $rootScope, $compile, $http, $templateCache) { +directives.directive('oauth', [ + 'AccessToken', + 'Endpoint', + 'Profile', + '$location', + '$rootScope', + '$compile', + '$http', + '$templateCache', + function(AccessToken, Endpoint, Profile, $location, $rootScope, $compile, $http, $templateCache) { var definition = { restrict: 'AE', @@ -117,4 +126,4 @@ directives.directive('oauth', function(AccessToken, Endpoint, Profile, $location }; return definition -}); +}]); diff --git a/app/scripts/interceptors/oauth-interceptor.js b/app/scripts/interceptors/oauth-interceptor.js index ba27246..6290890 100644 --- a/app/scripts/interceptors/oauth-interceptor.js +++ b/app/scripts/interceptors/oauth-interceptor.js @@ -2,7 +2,7 @@ var interceptorService = angular.module('oauth.interceptor', []); -interceptorService.factory('ExpiredInterceptor', function ($rootScope, $q, $sessionStorage) { +interceptorService.factory('ExpiredInterceptor', ['$rootScope', '$q', '$sessionStorage', function ($rootScope, $q, $sessionStorage) { var service = {}; @@ -20,4 +20,4 @@ interceptorService.factory('ExpiredInterceptor', function ($rootScope, $q, $sess }; return service; -}); +}]); diff --git a/app/scripts/services/access-token.js b/app/scripts/services/access-token.js index b6ffc7e..d98793e 100644 --- a/app/scripts/services/access-token.js +++ b/app/scripts/services/access-token.js @@ -2,7 +2,7 @@ var accessTokenService = angular.module('oauth.accessToken', ['ngStorage']); -accessTokenService.factory('AccessToken', function($rootScope, $location, $sessionStorage, $interval){ +accessTokenService.factory('AccessToken', ['$rootScope', '$location', '$sessionStorage', '$interval', function($rootScope, $location, $sessionStorage, $interval){ var service = { token: null @@ -165,4 +165,4 @@ accessTokenService.factory('AccessToken', function($rootScope, $location, $sessi return service; -}); +}]); diff --git a/app/scripts/services/endpoint.js b/app/scripts/services/endpoint.js index 8da04df..48d963f 100644 --- a/app/scripts/services/endpoint.js +++ b/app/scripts/services/endpoint.js @@ -2,7 +2,7 @@ var endpointClient = angular.module('oauth.endpoint', []); -endpointClient.factory('Endpoint', function(AccessToken, $location) { +endpointClient.factory('Endpoint', function() { var service = {}; var url; diff --git a/app/scripts/services/profile.js b/app/scripts/services/profile.js index 48990bb..e8971e4 100644 --- a/app/scripts/services/profile.js +++ b/app/scripts/services/profile.js @@ -2,7 +2,7 @@ var profileClient = angular.module('oauth.profile', []) -profileClient.factory('Profile', function($http, AccessToken, $rootScope) { +profileClient.factory('Profile', ['$http', 'AccessToken', '$rootScope', function($http, AccessToken, $rootScope) { var service = {}; var profile; @@ -29,4 +29,4 @@ profileClient.factory('Profile', function($http, AccessToken, $rootScope) { }; return service; -}); +}]); diff --git a/dist/oauth-ng.js b/dist/oauth-ng.js index 6cd9f66..a5eb7c5 100644 --- a/dist/oauth-ng.js +++ b/dist/oauth-ng.js @@ -1,4 +1,4 @@ -/* oauth-ng - v0.3.8 - 2015-02-06 */ +/* oauth-ng - v0.3.8 - 2015-04-14 */ 'use strict'; @@ -189,7 +189,7 @@ accessTokenService.factory('AccessToken', ['$rootScope', '$location', '$sessionS var endpointClient = angular.module('oauth.endpoint', []); -endpointClient.factory('Endpoint', ['AccessToken', '$location', function(AccessToken, $location) { +endpointClient.factory('Endpoint', function() { var service = {}; var url; @@ -234,7 +234,7 @@ endpointClient.factory('Endpoint', ['AccessToken', '$location', function(AccessT }; return service; -}]); +}); 'use strict'; @@ -297,7 +297,16 @@ interceptorService.factory('ExpiredInterceptor', ['$rootScope', '$q', '$sessionS var directives = angular.module('oauth.directive', []); -directives.directive('oauth', ['AccessToken', 'Endpoint', 'Profile', '$location', '$rootScope', '$compile', '$http', '$templateCache', function(AccessToken, Endpoint, Profile, $location, $rootScope, $compile, $http, $templateCache) { +directives.directive('oauth', [ + 'AccessToken', + 'Endpoint', + 'Profile', + '$location', + '$rootScope', + '$compile', + '$http', + '$templateCache', + function(AccessToken, Endpoint, Profile, $location, $rootScope, $compile, $http, $templateCache) { var definition = { restrict: 'AE', diff --git a/package.json b/package.json index 55c94d9..e883bba 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "oauth-ng", - "version": "0.3.7", + "version": "0.3.8", "author": "Andrea Reginato ", "description": "AngularJS Directive for OAuth 2.0", "repository": { From 630a50a710678c5e3a7ec4724d8a551f57dfe350 Mon Sep 17 00:00:00 2001 From: Massimiliano Sartoretto Date: Tue, 14 Apr 2015 23:03:26 +0200 Subject: [PATCH 044/107] Bump to 0.3.9 --- CHANGELOG.md | 4 ++++ package.json | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 801e7ec..43bbbe9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 0.3.9 (April 14, 2015) + +* Add inline annotations for dependency injection + ## 0.3.8 (February 06, 2015) * Upgrade to AngularJS v1.3.12. diff --git a/package.json b/package.json index e883bba..f3139b7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "oauth-ng", - "version": "0.3.8", + "version": "0.3.9", "author": "Andrea Reginato ", "description": "AngularJS Directive for OAuth 2.0", "repository": { From 55adc0c9991954a5f3d5b356825a21bf1fc2c49f Mon Sep 17 00:00:00 2001 From: Wayne Durack Date: Tue, 10 Mar 2015 12:56:34 +0000 Subject: [PATCH 045/107] Storage service & tests --- app/scripts/app.js | 5 +- app/scripts/services/storage.js | 49 +++++++++++++++ test/spec/services/storage.js | 103 ++++++++++++++++++++++++++++++++ 3 files changed, 155 insertions(+), 2 deletions(-) create mode 100644 app/scripts/services/storage.js create mode 100644 test/spec/services/storage.js diff --git a/app/scripts/app.js b/app/scripts/app.js index 86287d8..5e7039c 100644 --- a/app/scripts/app.js +++ b/app/scripts/app.js @@ -6,10 +6,11 @@ var app = angular.module('oauth', [ 'oauth.accessToken', // access token service 'oauth.endpoint', // oauth endpoint service 'oauth.profile', // profile model - 'oauth.interceptor' // bearer token interceptor + 'oauth.interceptor', // bearer token interceptor + 'oauth.storage' // storage ]); angular.module('oauth').config(['$locationProvider','$httpProvider', function($locationProvider, $httpProvider) { $httpProvider.interceptors.push('ExpiredInterceptor'); - }]); \ No newline at end of file + }]); diff --git a/app/scripts/services/storage.js b/app/scripts/services/storage.js new file mode 100644 index 0000000..7b0ed3f --- /dev/null +++ b/app/scripts/services/storage.js @@ -0,0 +1,49 @@ +'use strict'; + +var storageService = angular.module('oauth.storage', ['ngStorage']); + +storageService.factory('Storage', function($rootScope, $sessionStorage, $localStorage){ + + var service = { + storage: $sessionStorage // By default + }; + + /** + * Deletes the item from storage, + * Returns the item's previous value + */ + service.delete = function (name) { + var stored = this.get(name); + delete this.storage[name]; + return stored; + }; + + /** + * Returns the item from storage + */ + service.get = function (name) { + return this.storage[name]; + }; + + /** + * Sets the item in storage to the value specified + * Returns the item's value + */ + service.set = function (name, value) { + this.storage[name] = value; + return this.get(name); + }; + + /** + * Change the storage service being used + */ + service.use = function (storage) { + if (storage === 'sessionStorage') { + this.storage = $sessionStorage; + } else if (storage === 'localStorage') { + this.storage = $localStorage; + } + }; + + return service; +}); \ No newline at end of file diff --git a/test/spec/services/storage.js b/test/spec/services/storage.js new file mode 100644 index 0000000..5ff18eb --- /dev/null +++ b/test/spec/services/storage.js @@ -0,0 +1,103 @@ +'use strict'; + +describe('Storage', function() { + + var $sessionStorage, $localStorage, Storage; + var token = 'MOCK TOKEN'; + + beforeEach(module('oauth')); + + beforeEach(inject(function($injector) { $sessionStorage = $injector.get('$sessionStorage') })); + beforeEach(inject(function($injector) { $localStorage = $injector.get('$localStorage') })); + beforeEach(inject(function($injector) { Storage = $injector.get('Storage') })); + + it('should use sessionStorage by default', function () { + expect(Storage.storage).toEqual($sessionStorage); + }); + + describe('#use', function () { + + beforeEach(function () { + $sessionStorage.$reset(); + $localStorage.$reset(); + Storage.storage = null; + }); + + it('should use sessionStorage when specified', function () { + Storage.use('sessionStorage'); + expect(Storage.storage).toEqual($sessionStorage); + }); + + it('should use localStorage when specified', function () { + Storage.use('localStorage'); + expect(Storage.storage).toEqual($localStorage); + }); + + }); + + describe('#set', function() { + + beforeEach(function () { + $sessionStorage.$reset(); + $localStorage.$reset(); + }); + + it('should set something in sessionStorage', function () { + Storage.storage = $sessionStorage; + Storage.set('token', token); + expect($sessionStorage.token).toEqual(token); + }); + + it('should set something in sessionStorage', function () { + Storage.storage = $localStorage; + Storage.set('token', token); + expect($localStorage.token).toEqual(token); + }); + + }); + + describe('#get', function() { + + beforeEach(function () { + $sessionStorage.$reset(); + $localStorage.$reset(); + }); + + it('should set something in sessionStorage', function () { + $sessionStorage.token = token + Storage.storage = $sessionStorage; + expect(Storage.get('token')).toEqual(token); + }); + + it('should set something in sessionStorage', function () { + $localStorage.token = token + Storage.storage = $localStorage; + expect(Storage.get('token')).toEqual(token); + }); + + }); + + describe('#delete', function() { + + beforeEach(function () { + $sessionStorage.$reset(); + $localStorage.$reset(); + }); + + it('should delete the token from the sessionStorage', function () { + $sessionStorage.token = token; + Storage.storage = $sessionStorage; + expect(Storage.delete('token')).toEqual(token); + expect($sessionStorage.token).not.toBeDefined(); + }); + + it('should delete the token from the sessionStorage', function () { + $localStorage.token = token; + Storage.storage = $localStorage; + expect(Storage.delete('token')).toEqual(token); + expect($localStorage.token).not.toBeDefined(); + }); + + }); + +}); From c61bf94948bfa77f2bfe92814166ab6db745b332 Mon Sep 17 00:00:00 2001 From: Wayne Durack Date: Sun, 19 Apr 2015 13:53:43 +0100 Subject: [PATCH 046/107] Brought Storage service style inline with other services --- app/scripts/services/storage.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/scripts/services/storage.js b/app/scripts/services/storage.js index 7b0ed3f..a7f002c 100644 --- a/app/scripts/services/storage.js +++ b/app/scripts/services/storage.js @@ -2,7 +2,7 @@ var storageService = angular.module('oauth.storage', ['ngStorage']); -storageService.factory('Storage', function($rootScope, $sessionStorage, $localStorage){ +storageService.factory('Storage', ['$rootScope', '$sessionStorage', '$localStorage', function($rootScope, $sessionStorage, $localStorage){ var service = { storage: $sessionStorage // By default @@ -46,4 +46,4 @@ storageService.factory('Storage', function($rootScope, $sessionStorage, $localSt }; return service; -}); \ No newline at end of file +}]); \ No newline at end of file From 05732eb2a72fe18813e81cf633d5cd8ac0e6bafe Mon Sep 17 00:00:00 2001 From: Wayne Durack Date: Tue, 10 Mar 2015 16:50:33 +0000 Subject: [PATCH 047/107] Modified the rest of the application to use the Storage service --- app/index.html | 5 +- app/scripts/app.js | 4 +- app/scripts/directives/oauth.js | 8 +- app/scripts/interceptors/oauth-interceptor.js | 4 +- app/scripts/services/access-token.js | 12 +-- dist/oauth-ng.js | 76 ++++++++++++++++--- test/spec/directives/oauth.js | 4 +- test/spec/services/access-token.js | 14 ++-- test/spec/services/endpoint.js | 4 +- 9 files changed, 96 insertions(+), 35 deletions(-) diff --git a/app/index.html b/app/index.html index 037b7cb..1a3321f 100644 --- a/app/index.html +++ b/app/index.html @@ -17,6 +17,7 @@ + @@ -32,7 +33,8 @@ client-id="CLIENT_ID_HERE" redirect-uri="REDIRECT_URI_HERE" profile-uri="PROFILE_URI_HERE" - scope="SCOPE_HERE"> + scope="SCOPE_HERE" + storage="STORAGE_TYPE_HERE"> @@ -46,6 +48,7 @@ + diff --git a/app/scripts/app.js b/app/scripts/app.js index 5e7039c..699a08a 100644 --- a/app/scripts/app.js +++ b/app/scripts/app.js @@ -6,8 +6,8 @@ var app = angular.module('oauth', [ 'oauth.accessToken', // access token service 'oauth.endpoint', // oauth endpoint service 'oauth.profile', // profile model - 'oauth.interceptor', // bearer token interceptor - 'oauth.storage' // storage + 'oauth.storage', // storage + 'oauth.interceptor' // bearer token interceptor ]); angular.module('oauth').config(['$locationProvider','$httpProvider', diff --git a/app/scripts/directives/oauth.js b/app/scripts/directives/oauth.js index b376b5f..7b1b206 100644 --- a/app/scripts/directives/oauth.js +++ b/app/scripts/directives/oauth.js @@ -6,12 +6,13 @@ directives.directive('oauth', [ 'AccessToken', 'Endpoint', 'Profile', + 'Storage', '$location', '$rootScope', '$compile', '$http', '$templateCache', - function(AccessToken, Endpoint, Profile, $location, $rootScope, $compile, $http, $templateCache) { + function(AccessToken, Endpoint, Profile, Storage, $location, $rootScope, $compile, $http, $templateCache) { var definition = { restrict: 'AE', @@ -26,7 +27,8 @@ directives.directive('oauth', [ template: '@', // (optional) template to render (e.g views/templates/default.html) text: '@', // (optional) login text authorizePath: '@', // (optional) authorization url - state: '@' // (optional) An arbitrary unique string created by your app to guard against Cross-site Request Forgery + state: '@', // (optional) An arbitrary unique string created by your app to guard against Cross-site Request Forgery + storage: '@' // (optional) Store token in 'sessionStorage' or 'localStorage', defaults to 'sessionStorage' } }; @@ -37,6 +39,7 @@ directives.directive('oauth', [ var init = function() { initAttributes(); // sets defaults + Storage.use(scope.storage);// set storage compile(); // compiles the desired layout Endpoint.set(scope); // sets the oauth authorization url AccessToken.set(scope); // sets the access token object (if existing, from fragment or session) @@ -52,6 +55,7 @@ directives.directive('oauth', [ scope.text = scope.text || 'Sign In'; scope.state = scope.state || undefined; scope.scope = scope.scope || undefined; + scope.storage = scope.storage || 'sessionStorage'; }; var compile = function() { diff --git a/app/scripts/interceptors/oauth-interceptor.js b/app/scripts/interceptors/oauth-interceptor.js index 6290890..7759149 100644 --- a/app/scripts/interceptors/oauth-interceptor.js +++ b/app/scripts/interceptors/oauth-interceptor.js @@ -2,12 +2,12 @@ var interceptorService = angular.module('oauth.interceptor', []); -interceptorService.factory('ExpiredInterceptor', ['$rootScope', '$q', '$sessionStorage', function ($rootScope, $q, $sessionStorage) { +interceptorService.factory('ExpiredInterceptor', ['Storage', '$rootScope', '$q', function (Storage, $rootScope, $q) { var service = {}; service.request = function(config) { - var token = $sessionStorage.token; + var token = Storage.get('token'); if (token && expired(token)) $rootScope.$broadcast('oauth:expired', token); diff --git a/app/scripts/services/access-token.js b/app/scripts/services/access-token.js index d98793e..6e5795a 100644 --- a/app/scripts/services/access-token.js +++ b/app/scripts/services/access-token.js @@ -1,8 +1,8 @@ 'use strict'; -var accessTokenService = angular.module('oauth.accessToken', ['ngStorage']); +var accessTokenService = angular.module('oauth.accessToken', []); -accessTokenService.factory('AccessToken', ['$rootScope', '$location', '$sessionStorage', '$interval', function($rootScope, $location, $sessionStorage, $interval){ +accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$location', '$interval', function(Storage, $rootScope, $location, $interval){ var service = { token: null @@ -40,7 +40,7 @@ accessTokenService.factory('AccessToken', ['$rootScope', '$location', '$sessionS * @returns {null} */ service.destroy = function(){ - delete $sessionStorage.token; + Storage.delete('token'); this.token = null; return this.token; }; @@ -78,8 +78,8 @@ accessTokenService.factory('AccessToken', ['$rootScope', '$location', '$sessionS * Set the access token from the sessionStorage. */ var setTokenFromSession = function(){ - if($sessionStorage.token){ - var params = $sessionStorage.token; + if(Storage.get('token')){ + var params = Storage.get('token'); params.expires_at = new Date(params.expires_at); setToken(params); } @@ -123,7 +123,7 @@ accessTokenService.factory('AccessToken', ['$rootScope', '$location', '$sessionS * Save the access token into the session */ var setTokenInSession = function(){ - $sessionStorage.token = service.token; + Storage.set('token', service.token); }; /** diff --git a/dist/oauth-ng.js b/dist/oauth-ng.js index a5eb7c5..ae21d0b 100644 --- a/dist/oauth-ng.js +++ b/dist/oauth-ng.js @@ -1,4 +1,4 @@ -/* oauth-ng - v0.3.8 - 2015-04-14 */ +/* oauth-ng - v0.3.8 - 2015-04-19 */ 'use strict'; @@ -8,6 +8,7 @@ var app = angular.module('oauth', [ 'oauth.accessToken', // access token service 'oauth.endpoint', // oauth endpoint service 'oauth.profile', // profile model + 'oauth.storage', // storage 'oauth.interceptor' // bearer token interceptor ]); @@ -18,9 +19,9 @@ angular.module('oauth').config(['$locationProvider','$httpProvider', 'use strict'; -var accessTokenService = angular.module('oauth.accessToken', ['ngStorage']); +var accessTokenService = angular.module('oauth.accessToken', []); -accessTokenService.factory('AccessToken', ['$rootScope', '$location', '$sessionStorage', '$interval', function($rootScope, $location, $sessionStorage, $interval){ +accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$location', '$interval', function(Storage, $rootScope, $location, $interval){ var service = { token: null @@ -58,7 +59,7 @@ accessTokenService.factory('AccessToken', ['$rootScope', '$location', '$sessionS * @returns {null} */ service.destroy = function(){ - delete $sessionStorage.token; + Storage.delete('token'); this.token = null; return this.token; }; @@ -96,8 +97,8 @@ accessTokenService.factory('AccessToken', ['$rootScope', '$location', '$sessionS * Set the access token from the sessionStorage. */ var setTokenFromSession = function(){ - if($sessionStorage.token){ - var params = $sessionStorage.token; + if(Storage.get('token')){ + var params = Storage.get('token'); params.expires_at = new Date(params.expires_at); setToken(params); } @@ -141,7 +142,7 @@ accessTokenService.factory('AccessToken', ['$rootScope', '$location', '$sessionS * Save the access token into the session */ var setTokenInSession = function(){ - $sessionStorage.token = service.token; + Storage.set('token', service.token); }; /** @@ -271,14 +272,63 @@ profileClient.factory('Profile', ['$http', 'AccessToken', '$rootScope', function 'use strict'; +var storageService = angular.module('oauth.storage', ['ngStorage']); + +storageService.factory('Storage', ['$rootScope', '$sessionStorage', '$localStorage', function($rootScope, $sessionStorage, $localStorage){ + + var service = { + storage: $sessionStorage // By default + }; + + /** + * Deletes the item from storage, + * Returns the item's previous value + */ + service.delete = function (name) { + var stored = this.get(name); + delete this.storage[name]; + return stored; + }; + + /** + * Returns the item from storage + */ + service.get = function (name) { + return this.storage[name]; + }; + + /** + * Sets the item in storage to the value specified + * Returns the item's value + */ + service.set = function (name, value) { + this.storage[name] = value; + return this.get(name); + }; + + /** + * Change the storage service being used + */ + service.use = function (storage) { + if (storage === 'sessionStorage') { + this.storage = $sessionStorage; + } else if (storage === 'localStorage') { + this.storage = $localStorage; + } + }; + + return service; +}]); +'use strict'; + var interceptorService = angular.module('oauth.interceptor', []); -interceptorService.factory('ExpiredInterceptor', ['$rootScope', '$q', '$sessionStorage', function ($rootScope, $q, $sessionStorage) { +interceptorService.factory('ExpiredInterceptor', ['Storage', '$rootScope', '$q', function (Storage, $rootScope, $q) { var service = {}; service.request = function(config) { - var token = $sessionStorage.token; + var token = Storage.get('token'); if (token && expired(token)) $rootScope.$broadcast('oauth:expired', token); @@ -301,12 +351,13 @@ directives.directive('oauth', [ 'AccessToken', 'Endpoint', 'Profile', + 'Storage', '$location', '$rootScope', '$compile', '$http', '$templateCache', - function(AccessToken, Endpoint, Profile, $location, $rootScope, $compile, $http, $templateCache) { + function(AccessToken, Endpoint, Profile, Storage, $location, $rootScope, $compile, $http, $templateCache) { var definition = { restrict: 'AE', @@ -321,7 +372,8 @@ directives.directive('oauth', [ template: '@', // (optional) template to render (e.g bower_components/oauth-ng/dist/views/templates/default.html) text: '@', // (optional) login text authorizePath: '@', // (optional) authorization url - state: '@' // (optional) An arbitrary unique string created by your app to guard against Cross-site Request Forgery + state: '@', // (optional) An arbitrary unique string created by your app to guard against Cross-site Request Forgery + storage: '@' // (optional) Store token in 'sessionStorage' or 'localStorage', defaults to 'sessionStorage' } }; @@ -332,6 +384,7 @@ directives.directive('oauth', [ var init = function() { initAttributes(); // sets defaults + Storage.use(scope.storage);// set storage compile(); // compiles the desired layout Endpoint.set(scope); // sets the oauth authorization url AccessToken.set(scope); // sets the access token object (if existing, from fragment or session) @@ -347,6 +400,7 @@ directives.directive('oauth', [ scope.text = scope.text || 'Sign In'; scope.state = scope.state || undefined; scope.scope = scope.scope || undefined; + scope.storage = scope.storage || 'sessionStorage'; }; var compile = function() { diff --git a/test/spec/directives/oauth.js b/test/spec/directives/oauth.js index 1d4b2de..615860a 100644 --- a/test/spec/directives/oauth.js +++ b/test/spec/directives/oauth.js @@ -2,7 +2,7 @@ describe('oauth', function() { - var $rootScope, $location, $sessionStorage, $httpBackend, $compile, AccessToken, Endpoint, element, scope, result, callback; + var $rootScope, $location, Storage, $httpBackend, $compile, AccessToken, Endpoint, element, scope, result, callback; var uri = 'http://example.com/oauth/authorize?response_type=token&client_id=client-id&redirect_uri=http://example.com/redirect&scope=scope&state=/'; var fragment = 'access_token=token&token_type=bearer&expires_in=7200&state=/path'; @@ -16,7 +16,7 @@ describe('oauth', function() { beforeEach(inject(function($injector) { $rootScope = $injector.get('$rootScope') })); beforeEach(inject(function($injector) { $compile = $injector.get('$compile') })); beforeEach(inject(function($injector) { $location = $injector.get('$location') })); - beforeEach(inject(function($injector) { $sessionStorage = $injector.get('$sessionStorage') })); + beforeEach(inject(function($injector) { Storage = $injector.get('Storage') })); beforeEach(inject(function($injector) { $httpBackend = $injector.get('$httpBackend') })); beforeEach(inject(function($injector) { AccessToken = $injector.get('AccessToken') })); beforeEach(inject(function($injector) { Endpoint = $injector.get('Endpoint') })); diff --git a/test/spec/services/access-token.js b/test/spec/services/access-token.js index 1fe4194..ffc1358 100644 --- a/test/spec/services/access-token.js +++ b/test/spec/services/access-token.js @@ -2,7 +2,7 @@ describe('AccessToken', function() { - var result, $location, $sessionStorage, AccessToken, date; + var result, $location, Storage, AccessToken, date; var fragment = 'access_token=token&token_type=bearer&expires_in=7200&state=/path&extra=stuff'; var denied = 'error=access_denied&error_description=error'; @@ -12,7 +12,7 @@ describe('AccessToken', function() { beforeEach(module('oauth')); beforeEach(inject(function($injector) { $location = $injector.get('$location') })); - beforeEach(inject(function($injector) { $sessionStorage = $injector.get('$sessionStorage') })); + beforeEach(inject(function($injector) { Storage = $injector.get('Storage') })); beforeEach(inject(function($injector) { AccessToken = $injector.get('AccessToken') })); @@ -58,7 +58,7 @@ describe('AccessToken', function() { }); it('stores the token in the session', function() { - var stored_token = $sessionStorage.token; + var stored_token = Storage.get('token'); expect(result.access_token).toEqual('token'); }); }); @@ -66,7 +66,7 @@ describe('AccessToken', function() { describe('with the access token stored in the session', function() { beforeEach(function() { - $sessionStorage.token = token; + Storage.set('token', token); }); beforeEach(function() { @@ -97,7 +97,7 @@ describe('AccessToken', function() { }); it('stores the error message in the session', function() { - var stored_token = $sessionStorage.token; + var stored_token = Storage.get('token'); expect(result.error).toBe('access_denied'); }); }); @@ -143,7 +143,7 @@ describe('AccessToken', function() { }); it('reset the cache', function() { - expect($sessionStorage.token).toBeUndefined; + expect(Storage.get('token')).toBeUndefined; }); }); @@ -202,7 +202,7 @@ describe('AccessToken', function() { //It is an invalid test to have oAuth hash AND be getting token from session //if hash is in URL it should always be used, cuz its coming from oAuth2 provider re-direct $location.hash(''); - $sessionStorage.token = token; + Storage.set('token', token); result = AccessToken.set().expires_at; }); diff --git a/test/spec/services/endpoint.js b/test/spec/services/endpoint.js index e044c1c..ee9c226 100644 --- a/test/spec/services/endpoint.js +++ b/test/spec/services/endpoint.js @@ -2,7 +2,7 @@ describe('Endpoint', function() { - var result, $location, $sessionStorage, Endpoint; + var result, $location, Storage, Endpoint; var fragment = 'access_token=token&token_type=bearer&expires_in=7200&state=/path'; var params = { site: 'http://example.com/', clientId: 'client-id', redirectUri: 'http://example.com/redirect', scope: 'scope', authorizePath: '/oauth/authorize', responseType: 'token' }; @@ -11,7 +11,7 @@ describe('Endpoint', function() { beforeEach(module('oauth')); beforeEach(inject(function($injector) { $location = $injector.get('$location') })); - beforeEach(inject(function($injector) { $sessionStorage = $injector.get('$sessionStorage') })); + beforeEach(inject(function($injector) { Storage = $injector.get('Storage') })); beforeEach(inject(function($injector) { Endpoint = $injector.get('Endpoint') })); describe('#set', function() { From b163a79e6324f20d9ac8f76193041558f463ef05 Mon Sep 17 00:00:00 2001 From: Massimiliano Sartoretto Date: Mon, 20 Apr 2015 10:09:35 +0200 Subject: [PATCH 048/107] Update changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 43bbbe9..5f0b075 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 0.3.10 (April 20, 2015) + +* Add Storage service + ## 0.3.9 (April 14, 2015) * Add inline annotations for dependency injection From 557f1fbf33f9de9f364f3e6bccebeb298c5e8aee Mon Sep 17 00:00:00 2001 From: Massimiliano Sartoretto Date: Mon, 20 Apr 2015 10:09:47 +0200 Subject: [PATCH 049/107] Bump to 0.3.10 Build the release --- dist/oauth-ng.js | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dist/oauth-ng.js b/dist/oauth-ng.js index ae21d0b..0a0cbc0 100644 --- a/dist/oauth-ng.js +++ b/dist/oauth-ng.js @@ -1,4 +1,4 @@ -/* oauth-ng - v0.3.8 - 2015-04-19 */ +/* oauth-ng - v0.3.8 - 2015-04-20 */ 'use strict'; diff --git a/package.json b/package.json index f3139b7..c5c6927 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "oauth-ng", - "version": "0.3.9", + "version": "0.3.10", "author": "Andrea Reginato ", "description": "AngularJS Directive for OAuth 2.0", "repository": { From 5581e95f7d5667f8b1411455bc22365f2b6bea47 Mon Sep 17 00:00:00 2001 From: Massimiliano Sartoretto Date: Sun, 26 Apr 2015 19:58:00 +0200 Subject: [PATCH 050/107] Update README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index bd3385f..b2c1cfb 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ Please also update `gh-pages` branch with documentation when applicable. ### Setup * Fork and clone the repository -* Run `npm install && bower install +* Run `npm install && bower install` ### OAuth 2.0 supported grant types @@ -28,13 +28,13 @@ and the [OAuth 2.0 Implicit Flow](http://tools.ietf.org/html/rfc6749#section-1.3 See: http://tools.ietf.org/html/rfc6749#section-4.1 -The use the Authorization code flow set response-type="code" in the oauth directive. +To use the Authorization code flow set response-type="code" in the oauth directive. #### Implicit flow See: http://tools.ietf.org/html/rfc6749#section-4.2 -The use the Implicit flow set response-type="token" in the oauth directive. +To use the Implicit flow set response-type="token" in the oauth directive. ### Unit tests (karma) From 175656ff0554dde03298a4d20e9d66c5f4d86a4c Mon Sep 17 00:00:00 2001 From: Massimiliano Sartoretto Date: Mon, 27 Apr 2015 17:24:06 +0200 Subject: [PATCH 051/107] Bump bower to 0.3.10 --- bower.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bower.json b/bower.json index 19bcbb8..c1723a7 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "oauth-ng", - "version": "0.3.8", + "version": "0.3.10", "main": [ "dist/oauth-ng.js", "dist/views/templates/default" From 0ff36e9a812127f9ac22c7baa9b1ebe632754183 Mon Sep 17 00:00:00 2001 From: Wayne Durack Date: Wed, 11 Mar 2015 12:24:30 +0000 Subject: [PATCH 052/107] Supports expires_in property in token being optional, previously if expires_in was not present token was automatically expired. Ref issue #70 --- app/scripts/services/access-token.js | 19 +++++++++++---- dist/oauth-ng.js | 21 ++++++++++++---- test/spec/services/access-token.js | 36 +++++++++++++++++++++++++++- 3 files changed, 66 insertions(+), 10 deletions(-) diff --git a/app/scripts/services/access-token.js b/app/scripts/services/access-token.js index 6e5795a..85547a1 100644 --- a/app/scripts/services/access-token.js +++ b/app/scripts/services/access-token.js @@ -65,6 +65,9 @@ accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$location', removeFragment(); setToken(params); setExpiresAt(); + // We have to save it again to make sure expires_at is set + // and the expiry event is set up properly + setToken(this.token); $rootScope.$broadcast('oauth:login', service.token); } }; @@ -78,9 +81,8 @@ accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$location', * Set the access token from the sessionStorage. */ var setTokenFromSession = function(){ - if(Storage.get('token')){ - var params = Storage.get('token'); - params.expires_at = new Date(params.expires_at); + var params = Storage.get('token'); + if (params) { setToken(params); } }; @@ -130,10 +132,15 @@ accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$location', * Set the access token expiration date (useful for refresh logics) */ var setExpiresAt = function(){ - if(service.token){ + if (!service.token) { + return; + } + if(typeof(service.token.expires_in) !== 'undefined' && service.token.expires_in !== null) { var expires_at = new Date(); expires_at.setSeconds(expires_at.getSeconds()+parseInt(service.token.expires_in)-60); // 60 seconds less to secure browser and response latency service.token.expires_at = expires_at; + } else { + service.token.expires_at = null; } }; @@ -142,6 +149,10 @@ accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$location', * Set the timeout at which the expired event is fired */ var setExpiresAtEvent = function(){ + // Don't bother if there's no expires token + if (typeof(service.token.expires_at) === 'undefined' || service.token.expires_at === null) { + return; + } var time = (new Date(service.token.expires_at))-(new Date()); if(time){ $interval(function(){ diff --git a/dist/oauth-ng.js b/dist/oauth-ng.js index 0a0cbc0..ddb95e2 100644 --- a/dist/oauth-ng.js +++ b/dist/oauth-ng.js @@ -1,4 +1,4 @@ -/* oauth-ng - v0.3.8 - 2015-04-20 */ +/* oauth-ng - v0.3.10 - 2015-05-09 */ 'use strict'; @@ -84,6 +84,9 @@ accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$location', removeFragment(); setToken(params); setExpiresAt(); + // We have to save it again to make sure expires_at is set + // and the expiry event is set up properly + setToken(this.token); $rootScope.$broadcast('oauth:login', service.token); } }; @@ -97,9 +100,8 @@ accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$location', * Set the access token from the sessionStorage. */ var setTokenFromSession = function(){ - if(Storage.get('token')){ - var params = Storage.get('token'); - params.expires_at = new Date(params.expires_at); + var params = Storage.get('token'); + if (params) { setToken(params); } }; @@ -149,10 +151,15 @@ accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$location', * Set the access token expiration date (useful for refresh logics) */ var setExpiresAt = function(){ - if(service.token){ + if (!service.token) { + return; + } + if(typeof(service.token.expires_in) !== 'undefined' && service.token.expires_in !== null) { var expires_at = new Date(); expires_at.setSeconds(expires_at.getSeconds()+parseInt(service.token.expires_in)-60); // 60 seconds less to secure browser and response latency service.token.expires_at = expires_at; + } else { + service.token.expires_at = null; } }; @@ -161,6 +168,10 @@ accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$location', * Set the timeout at which the expired event is fired */ var setExpiresAtEvent = function(){ + // Don't bother if there's no expires token + if (typeof(service.token.expires_at) === 'undefined' || service.token.expires_at === null) { + return; + } var time = (new Date(service.token.expires_at))-(new Date()); if(time){ $interval(function(){ diff --git a/test/spec/services/access-token.js b/test/spec/services/access-token.js index ffc1358..7291ec3 100644 --- a/test/spec/services/access-token.js +++ b/test/spec/services/access-token.js @@ -5,6 +5,8 @@ describe('AccessToken', function() { var result, $location, Storage, AccessToken, date; var fragment = 'access_token=token&token_type=bearer&expires_in=7200&state=/path&extra=stuff'; + var fragmentForever = 'access_token=token&token_type=bearer&state=/path&extra=stuff'; + var fragmentImmediate = 'access_token=token&token_type=bearer&expires_in=0&state=/path&extra=stuff'; var denied = 'error=access_denied&error_description=error'; var expires_at = '2014-08-17T17:38:37.584Z'; var token = { access_token: 'token', token_type: 'bearer', expires_in: 7200, state: '/path', expires_at: expires_at }; @@ -39,6 +41,38 @@ describe('AccessToken', function() { }); }); + describe('when sets the access token without an expiry', function() { + + beforeEach(function() { + $location.hash(fragmentForever); + }); + + beforeEach(function() { + result = AccessToken.set(); + }); + + it('sets #expires_at', function() { + expect(result.expires_at).toBe(null); + }); + }); + + describe('when sets the access token with an expiry of 0 (immediate)', function() { + + beforeEach(function() { + $location.hash(fragmentImmediate); + }); + + beforeEach(function() { + result = AccessToken.set(); + }); + + it('sets #expires_at', function() { + var expected_date = new Date(); + expected_date.setSeconds(expected_date.getSeconds() - 60); + expect(parseInt(result.expires_at/100)).toBe(parseInt(expected_date/100)); // 10 ms + }); + }); + describe('with the access token in the fragment URI', function() { beforeEach(function() { @@ -207,7 +241,7 @@ describe('AccessToken', function() { }); it('rehydrates the expires_at value', function() { - expect(result).toEqual(new Date(expires_at)); + expect(result).toEqual(expires_at); }); }); }); From a409fb372a64bcf1e14dc59a9067c1b98445e3f8 Mon Sep 17 00:00:00 2001 From: Mark Eschbach Date: Mon, 18 May 2015 08:01:24 -0700 Subject: [PATCH 053/107] No reason to run system under root credentials --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c5c6927..52969ea 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "node": ">=0.8.0" }, "scripts": { - "postinstall": "bower install --allow-root", + "postinstall": "bower install", "clean": "rm -rf node_modules dist app/bower_components", "test": "grunt test && grunt build" } From 3dd29d5b7a1b6a7b690bbb754678da15411e89ed Mon Sep 17 00:00:00 2001 From: Mark Eschbach Date: Mon, 18 May 2015 09:29:18 -0700 Subject: [PATCH 054/107] Calculate path in Endpoint#get/1 --- app/scripts/services/endpoint.js | 29 +++++++------ test/spec/services/endpoint.js | 71 ++++++++++++++++++++++++++++++-- 2 files changed, 81 insertions(+), 19 deletions(-) diff --git a/app/scripts/services/endpoint.js b/app/scripts/services/endpoint.js index 48d963f..76d20ea 100644 --- a/app/scripts/services/endpoint.js +++ b/app/scripts/services/endpoint.js @@ -5,45 +5,44 @@ var endpointClient = angular.module('oauth.endpoint', []); endpointClient.factory('Endpoint', function() { var service = {}; - var url; - /* * Defines the authorization URL */ - service.set = function(params) { + service.set = function(configuration) { + this.config = configuration; + return this.get(); + }; + + /* + * Returns the authorization URL + */ + + service.get = function( overrides ) { + var params = angular.extend( {}, service.config, overrides); var oAuthScope = (params.scope) ? params.scope : '', state = (params.state) ? encodeURIComponent(params.state) : '', authPathHasQuery = (params.authorizePath.indexOf('?') == -1) ? false : true, appendChar = (authPathHasQuery) ? '&' : '?'; //if authorizePath has ? already append OAuth2 params - url = params.site + + var url = params.site + params.authorizePath + appendChar + 'response_type=' + params.responseType + '&' + 'client_id=' + encodeURIComponent(params.clientId) + '&' + 'redirect_uri=' + encodeURIComponent(params.redirectUri) + '&' + 'scope=' + oAuthScope + '&' + 'state=' + state; - return url; }; - /* - * Returns the authorization URL - */ - - service.get = function() { - return url; - }; - - /* * Redirects the app to the authorization URL */ service.redirect = function() { - window.location.replace(url); + var targetLocation = this.get(); + window.location.replace(targetLocation); }; return service; diff --git a/test/spec/services/endpoint.js b/test/spec/services/endpoint.js index ee9c226..d6bee5f 100644 --- a/test/spec/services/endpoint.js +++ b/test/spec/services/endpoint.js @@ -96,12 +96,75 @@ describe('Endpoint', function() { Endpoint.set(params); }); - beforeEach(function() { - result = Endpoint.get(); + describe( "without overrides", function(){ + beforeEach(function() { + result = Endpoint.get(); + }); + + it('returns the oauth server endpoint', function() { + expect(result).toEqual(uri); + }); }); - it('returns the oauth server endpoint', function() { - expect(result).toEqual(uri); + describe( "with state override", function(){ + it( "injects the state correct", function(){ + var override = { state: 'testState' }; + var result = Endpoint.get( override ); + expect( result ).toEqual( uri + encodeURIComponent( override.state ) ); + }); + }); + + describe( "with clientId override", function(){ + it( "injects the override", function(){ + var override = { clientId: 'unicorn' }; + var result = Endpoint.get( override ); + var expectedUri = 'http://example.com/oauth/authorize?response_type=token&client_id=unicorn&redirect_uri=http%3A%2F%2Fexample.com%2Fredirect&scope=scope&state='; + expect( result ).toEqual( expectedUri ); + }); + }); + + describe( "with scope override", function(){ + it( "injects the override", function(){ + var override = { scope: 'stars' }; + var result = Endpoint.get( override ); + var expectedUri = 'http://example.com/oauth/authorize?response_type=token&client_id=client-id&redirect_uri=http%3A%2F%2Fexample.com%2Fredirect&scope=stars&state='; + expect( result ).toEqual( expectedUri ); + }); + }); + + describe( "with state override", function(){ + it( "injects the state correct", function(){ + var override = { state: 'testState' }; + var result = Endpoint.get( override ); + expect( result ).toEqual( uri + encodeURIComponent( override.state ) ); + }); + }); + + describe( "with responseType override", function(){ + it( "injects the correct repsonseType", function(){ + var override = { responseType: 'id_token' }; + var result = Endpoint.get( override ); + var expectedUri = 'http://example.com/oauth/authorize?response_type=id_token&client_id=client-id&redirect_uri=http%3A%2F%2Fexample.com%2Fredirect&scope=scope&state='; + expect( result ).toEqual( expectedUri ); + }); + }); + + describe( "with site override", function(){ + it( "injects the correct site", function(){ + var override = { site: 'https://invincible.test/' }; + var result = Endpoint.get( override ); + var expectedUri = 'https://invincible.test/oauth/authorize?response_type=token&client_id=client-id&redirect_uri=http%3A%2F%2Fexample.com%2Fredirect&scope=scope&state='; + expect( result ).toEqual( expectedUri ); + }); + }); + + describe( "with authorize path overrides", function(){ + it( "injects the correct authorize path", function(){ + var override = { authorizePath: '/end/here' }; + var result = Endpoint.get( override ); + var expectedUri = 'http://example.com/end/here?response_type=token&client_id=client-id&redirect_uri=http%3A%2F%2Fexample.com%2Fredirect&scope=scope&state='; + expect( result ).toEqual( expectedUri ); + }); }); }); }); From 41f6b90c9a18fc30817ec8f877a81130eeaab3b9 Mon Sep 17 00:00:00 2001 From: Mark Eschbach Date: Mon, 18 May 2015 10:56:02 -0700 Subject: [PATCH 055/107] Ensure scopes are properly encoded --- app/scripts/services/endpoint.js | 2 +- test/spec/services/endpoint.js | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/app/scripts/services/endpoint.js b/app/scripts/services/endpoint.js index 76d20ea..7fe4be6 100644 --- a/app/scripts/services/endpoint.js +++ b/app/scripts/services/endpoint.js @@ -21,7 +21,7 @@ endpointClient.factory('Endpoint', function() { service.get = function( overrides ) { var params = angular.extend( {}, service.config, overrides); - var oAuthScope = (params.scope) ? params.scope : '', + var oAuthScope = (params.scope) ? encodeURIComponent(params.scope) : '', state = (params.state) ? encodeURIComponent(params.state) : '', authPathHasQuery = (params.authorizePath.indexOf('?') == -1) ? false : true, appendChar = (authPathHasQuery) ? '&' : '?'; //if authorizePath has ? already append OAuth2 params diff --git a/test/spec/services/endpoint.js b/test/spec/services/endpoint.js index d6bee5f..14e57b4 100644 --- a/test/spec/services/endpoint.js +++ b/test/spec/services/endpoint.js @@ -166,5 +166,14 @@ describe('Endpoint', function() { expect( result ).toEqual( expectedUri ); }); }); + + describe( "given scope with spaces", function(){ + it( "correctly encodes the spaces", function(){ + var override = { scope: 'read write profile openid' }; + var result = Endpoint.get( override ); + var expectedUri = 'http://example.com/oauth/authorize?response_type=token&client_id=client-id&redirect_uri=http%3A%2F%2Fexample.com%2Fredirect&scope=read%20write%20profile%20openid&state='; + expect( result ).toEqual( expectedUri ); + }); + }); }); }); From ee6fb04a3ef565e3ffe2cfdab38d163a66bf00ed Mon Sep 17 00:00:00 2001 From: Mark Eschbach Date: Mon, 18 May 2015 11:01:59 -0700 Subject: [PATCH 056/107] Ensuring repsonse type is properly encoded --- app/scripts/services/endpoint.js | 5 +++-- test/spec/services/endpoint.js | 9 +++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/app/scripts/services/endpoint.js b/app/scripts/services/endpoint.js index 7fe4be6..ef37591 100644 --- a/app/scripts/services/endpoint.js +++ b/app/scripts/services/endpoint.js @@ -24,11 +24,12 @@ endpointClient.factory('Endpoint', function() { var oAuthScope = (params.scope) ? encodeURIComponent(params.scope) : '', state = (params.state) ? encodeURIComponent(params.state) : '', authPathHasQuery = (params.authorizePath.indexOf('?') == -1) ? false : true, - appendChar = (authPathHasQuery) ? '&' : '?'; //if authorizePath has ? already append OAuth2 params + appendChar = (authPathHasQuery) ? '&' : '?', //if authorizePath has ? already append OAuth2 params + responseType = (params.responseType) ? encodeURIComponent(params.responseType) : ''; var url = params.site + params.authorizePath + - appendChar + 'response_type=' + params.responseType + '&' + + appendChar + 'response_type=' + responseType + '&' + 'client_id=' + encodeURIComponent(params.clientId) + '&' + 'redirect_uri=' + encodeURIComponent(params.redirectUri) + '&' + 'scope=' + oAuthScope + '&' + diff --git a/test/spec/services/endpoint.js b/test/spec/services/endpoint.js index 14e57b4..ed9762f 100644 --- a/test/spec/services/endpoint.js +++ b/test/spec/services/endpoint.js @@ -175,5 +175,14 @@ describe('Endpoint', function() { expect( result ).toEqual( expectedUri ); }); }); + + describe( "on repsonse type with spaces", function(){ + it( "correctly encodes the spaces", function(){ + var override = { responseType: 'id_token token' }; + var result = Endpoint.get( override ); + var expectedUri = 'http://example.com/oauth/authorize?response_type=id_token%20token&client_id=client-id&redirect_uri=http%3A%2F%2Fexample.com%2Fredirect&scope=scope&state='; + expect( result ).toEqual( expectedUri ); + }); + }); }); }); From 588363917ff35ffb9d54914ae8802b664633728e Mon Sep 17 00:00:00 2001 From: Mark Eschbach Date: Mon, 18 May 2015 13:38:39 -0700 Subject: [PATCH 057/107] Updating Endpoint#get to take nonce --- app/scripts/services/endpoint.js | 4 ++++ test/spec/services/endpoint.js | 9 +++++++++ 2 files changed, 13 insertions(+) diff --git a/app/scripts/services/endpoint.js b/app/scripts/services/endpoint.js index ef37591..9187606 100644 --- a/app/scripts/services/endpoint.js +++ b/app/scripts/services/endpoint.js @@ -34,6 +34,10 @@ endpointClient.factory('Endpoint', function() { 'redirect_uri=' + encodeURIComponent(params.redirectUri) + '&' + 'scope=' + oAuthScope + '&' + 'state=' + state; + + if( params.nonce ) { + url = url + "&nonce=" + params.nonce; + } return url; }; diff --git a/test/spec/services/endpoint.js b/test/spec/services/endpoint.js index ed9762f..156a1b7 100644 --- a/test/spec/services/endpoint.js +++ b/test/spec/services/endpoint.js @@ -184,5 +184,14 @@ describe('Endpoint', function() { expect( result ).toEqual( expectedUri ); }); }); + + describe( "when provided with a nonce", function(){ + it( "adds the nonce parameter", function(){ + var override = { nonce: '987654321' }; + var result = Endpoint.get( override ); + var expectedUri = 'http://example.com/oauth/authorize?response_type=token&client_id=client-id&redirect_uri=http%3A%2F%2Fexample.com%2Fredirect&scope=scope&state=&nonce=987654321'; + expect( result ).toEqual( expectedUri ); + }); + }); }); }); From f568ec9f98c8a967d7b4151edadde53718b224d3 Mon Sep 17 00:00:00 2001 From: Mark Eschbach Date: Mon, 18 May 2015 13:46:08 -0700 Subject: [PATCH 058/107] Chaining property overrides for redirection --- app/scripts/services/endpoint.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/scripts/services/endpoint.js b/app/scripts/services/endpoint.js index 9187606..dcd5883 100644 --- a/app/scripts/services/endpoint.js +++ b/app/scripts/services/endpoint.js @@ -45,8 +45,8 @@ endpointClient.factory('Endpoint', function() { * Redirects the app to the authorization URL */ - service.redirect = function() { - var targetLocation = this.get(); + service.redirect = function( overrides ) { + var targetLocation = this.get( overrides ); window.location.replace(targetLocation); }; From fa79521cfafb5c95d3116df18907dc8b4a5c4c39 Mon Sep 17 00:00:00 2001 From: Mark Eschbach Date: Tue, 19 May 2015 12:04:10 -0700 Subject: [PATCH 059/107] Bulid artifact --- dist/oauth-ng.js | 42 +++++++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/dist/oauth-ng.js b/dist/oauth-ng.js index 0a0cbc0..49b929e 100644 --- a/dist/oauth-ng.js +++ b/dist/oauth-ng.js @@ -1,4 +1,4 @@ -/* oauth-ng - v0.3.8 - 2015-04-20 */ +/* oauth-ng - v0.3.10 - 2015-05-19 */ 'use strict'; @@ -193,45 +193,49 @@ var endpointClient = angular.module('oauth.endpoint', []); endpointClient.factory('Endpoint', function() { var service = {}; - var url; - /* * Defines the authorization URL */ - service.set = function(params) { - var oAuthScope = (params.scope) ? params.scope : '', + service.set = function(configuration) { + this.config = configuration; + return this.get(); + }; + + /* + * Returns the authorization URL + */ + + service.get = function( overrides ) { + var params = angular.extend( {}, service.config, overrides); + var oAuthScope = (params.scope) ? encodeURIComponent(params.scope) : '', state = (params.state) ? encodeURIComponent(params.state) : '', authPathHasQuery = (params.authorizePath.indexOf('?') == -1) ? false : true, - appendChar = (authPathHasQuery) ? '&' : '?'; //if authorizePath has ? already append OAuth2 params + appendChar = (authPathHasQuery) ? '&' : '?', //if authorizePath has ? already append OAuth2 params + responseType = (params.responseType) ? encodeURIComponent(params.responseType) : ''; - url = params.site + + var url = params.site + params.authorizePath + - appendChar + 'response_type=' + params.responseType + '&' + + appendChar + 'response_type=' + responseType + '&' + 'client_id=' + encodeURIComponent(params.clientId) + '&' + 'redirect_uri=' + encodeURIComponent(params.redirectUri) + '&' + 'scope=' + oAuthScope + '&' + 'state=' + state; + if( params.nonce ) { + url = url + "&nonce=" + params.nonce; + } return url; }; - /* - * Returns the authorization URL - */ - - service.get = function() { - return url; - }; - - /* * Redirects the app to the authorization URL */ - service.redirect = function() { - window.location.replace(url); + service.redirect = function( overrides ) { + var targetLocation = this.get( overrides ); + window.location.replace(targetLocation); }; return service; From b7026018684fce0064e8287074c721de3b47da98 Mon Sep 17 00:00:00 2001 From: Massimiliano Sartoretto Date: Tue, 26 May 2015 21:17:20 +0200 Subject: [PATCH 060/107] Add jshint config file and remove it from gitignore --- .gitignore | 1 - .jshintrc | 23 +++++++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 .jshintrc diff --git a/.gitignore b/.gitignore index a98ab44..101d74e 100644 --- a/.gitignore +++ b/.gitignore @@ -29,7 +29,6 @@ docs app/bower_components .editorconfig .gitattributes -.jshintrc .tmp # Dev diff --git a/.jshintrc b/.jshintrc new file mode 100644 index 0000000..abc9c9f --- /dev/null +++ b/.jshintrc @@ -0,0 +1,23 @@ +{ + "node": true, + "browser": true, + "esnext": true, + "bitwise": true, + "camelcase": true, + "curly": true, + "eqeqeq": true, + "immed": true, + "indent": 2, + "latedef": true, + "newcap": true, + "noarg": true, + "quotmark": "single", + "undef": true, + "unused": true, + "strict": true, + "trailing": true, + "smarttabs": true, + "globals": { + "angular": false + } +} \ No newline at end of file From 538476e4f2f7907ffd4e075d2aac97d9f8273340 Mon Sep 17 00:00:00 2001 From: Massimiliano Sartoretto Date: Tue, 26 May 2015 22:42:39 +0200 Subject: [PATCH 061/107] Update changelog --- CHANGELOG.md | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f0b075..28936c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## 0.4.0 (May 26, 2015) + +* Fix jshint config file +* Token `expires_in` property is now optional + ## 0.3.10 (April 20, 2015) * Add Storage service @@ -22,42 +27,42 @@ ## 0.3.3 (November 25, 2014) -* Fixed Code method option added +* Add Fixed Code method option ## 0.3.2 (November 16, 2014) -* Authorization Code method option added +* Add Authorization Code method option ## 0.3.1 (November 3, 2014) * Replace $timeout with $interval #50 * Add broadcast “oath:profile” once profile is retrieved. #51 -* Added travis +* Add travis ## 0.3.0 (October 30, 2014) -* Solved bug on access token definition from hash +* Fix bug on access token definition from hash * Correctly running tests with E2E protractor ## 0.2.8 (August 27, 2014) -* Fixed `expries_at` not being set in some situations +* Fix `expries_at` not being set in some situations * Only use session storage when oAuth hash not in URL * Only remove oAuth2 tokens from hash ## 0.2.7 (August 26, 2014) -* Fixed `expires_at` not being set -* Fixed `expired()` calculation +* Fix `expires_at` not being set +* Fix `expired()` calculation ## 0.2.6 (August 14, 2014) -* Removed encoding for OAuth 2.0 scope. +* Remove encoding for OAuth 2.0 scope. ## 0.2.4 (August 13, 2014) -* Removed settings for HTML5 mode -* Added logic to fire the oauth:expired event when the token expires. Before it was raised +* Remove settings for HTML5 mode +* Add logic to fire the oauth:expired event when the token expires. Before it was raised only when the request was returning a 401. ## 0.2.2 (July 11, 2014) @@ -73,6 +78,6 @@ per https://github.com/andreareginato/oauth-ng/issues/16 ## 0.2.0 (June 1, 2014) -* Updated name from ng-oauth to oauth-ng +* Update name from ng-oauth to oauth-ng * New documentation site andreareginato.github.io/oauth-ng * Major refactoring From 13578c8e96773e517c323e8a8488a11ec212632c Mon Sep 17 00:00:00 2001 From: Massimiliano Sartoretto Date: Tue, 26 May 2015 22:43:18 +0200 Subject: [PATCH 062/107] Bump to version 0.4.0 --- bower.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bower.json b/bower.json index c1723a7..e91e172 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "oauth-ng", - "version": "0.3.10", + "version": "0.4.0", "main": [ "dist/oauth-ng.js", "dist/views/templates/default" diff --git a/package.json b/package.json index c5c6927..f0ff767 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "oauth-ng", - "version": "0.3.10", + "version": "0.4.0", "author": "Andrea Reginato ", "description": "AngularJS Directive for OAuth 2.0", "repository": { From 8e25c1735514857677e7fcf6a4def920af682a92 Mon Sep 17 00:00:00 2001 From: Massimiliano Sartoretto Date: Wed, 27 May 2015 15:31:59 +0200 Subject: [PATCH 063/107] Make the code jshint friendly --- .jshintrc | 2 +- Gruntfile.js | 3 +- app/scripts/app.js | 7 +- app/scripts/directives/oauth.js | 225 ++++++------ app/scripts/interceptors/oauth-interceptor.js | 7 +- app/scripts/services/access-token.js | 342 +++++++++--------- app/scripts/services/endpoint.js | 2 +- app/scripts/services/profile.js | 6 +- app/scripts/services/storage.js | 84 ++--- test/.jshintrc | 32 ++ test/spec/directives/oauth.js | 38 +- test/spec/services/access-token.js | 38 +- test/spec/services/endpoint.js | 29 +- test/spec/services/profile.js | 18 +- test/spec/services/storage.js | 10 +- 15 files changed, 440 insertions(+), 403 deletions(-) create mode 100644 test/.jshintrc diff --git a/.jshintrc b/.jshintrc index abc9c9f..c82708b 100644 --- a/.jshintrc +++ b/.jshintrc @@ -3,7 +3,7 @@ "browser": true, "esnext": true, "bitwise": true, - "camelcase": true, + "camelcase": false, "curly": true, "eqeqeq": true, "immed": true, diff --git a/Gruntfile.js b/Gruntfile.js index 51afe4b..ffd8782 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -23,7 +23,7 @@ module.exports = function (grunt) { }; try { - var component = require('./bower.json') + var component = require('./bower.json'); yeomanConfig.name = component.name || 'no-name'; yeomanConfig.version = component.version || '0.0.0.undefined'; } catch (e) {} @@ -114,6 +114,7 @@ module.exports = function (grunt) { ], test: { options: { + jasmine: true, jshintrc: 'test/.jshintrc' }, src: ['test/spec/{,*/}*.js'] diff --git a/app/scripts/app.js b/app/scripts/app.js index 699a08a..a746df7 100644 --- a/app/scripts/app.js +++ b/app/scripts/app.js @@ -1,16 +1,15 @@ 'use strict'; // App libraries -var app = angular.module('oauth', [ +angular.module('oauth', [ 'oauth.directive', // login directive 'oauth.accessToken', // access token service 'oauth.endpoint', // oauth endpoint service 'oauth.profile', // profile model 'oauth.storage', // storage 'oauth.interceptor' // bearer token interceptor -]); - -angular.module('oauth').config(['$locationProvider','$httpProvider', +]) + .config(['$locationProvider','$httpProvider', function($locationProvider, $httpProvider) { $httpProvider.interceptors.push('ExpiredInterceptor'); }]); diff --git a/app/scripts/directives/oauth.js b/app/scripts/directives/oauth.js index 7b1b206..7970f91 100644 --- a/app/scripts/directives/oauth.js +++ b/app/scripts/directives/oauth.js @@ -14,120 +14,129 @@ directives.directive('oauth', [ '$templateCache', function(AccessToken, Endpoint, Profile, Storage, $location, $rootScope, $compile, $http, $templateCache) { - var definition = { - restrict: 'AE', - replace: true, - scope: { - site: '@', // (required) set the oauth server host (e.g. http://oauth.example.com) - clientId: '@', // (required) client id - redirectUri: '@', // (required) client redirect uri - responseType: '@', // (optional) response type, defaults to token (use 'token' for implicit flow and 'code' for authorization code flow - scope: '@', // (optional) scope - profileUri: '@', // (optional) user profile uri (e.g http://example.com/me) - template: '@', // (optional) template to render (e.g views/templates/default.html) - text: '@', // (optional) login text - authorizePath: '@', // (optional) authorization url - state: '@', // (optional) An arbitrary unique string created by your app to guard against Cross-site Request Forgery - storage: '@' // (optional) Store token in 'sessionStorage' or 'localStorage', defaults to 'sessionStorage' - } - }; - - definition.link = function postLink(scope, element, attrs) { - scope.show = 'none'; - - scope.$watch('clientId', function(value) { init() }); - - var init = function() { - initAttributes(); // sets defaults - Storage.use(scope.storage);// set storage - compile(); // compiles the desired layout - Endpoint.set(scope); // sets the oauth authorization url - AccessToken.set(scope); // sets the access token object (if existing, from fragment or session) - initProfile(scope); // gets the profile resource (if existing the access token) - initView(); // sets the view (logged in or out) - }; - - var initAttributes = function() { - scope.authorizePath = scope.authorizePath || '/oauth/authorize'; - scope.tokenPath = scope.tokenPath || '/oauth/token'; - scope.template = scope.template || 'views/templates/default.html'; - scope.responseType = scope.responseType || 'token'; - scope.text = scope.text || 'Sign In'; - scope.state = scope.state || undefined; - scope.scope = scope.scope || undefined; - scope.storage = scope.storage || 'sessionStorage'; - }; - - var compile = function() { - $http.get(scope.template, { cache: $templateCache }).success(function(html) { - element.html(html); - $compile(element.contents())(scope); - }); - }; - - var initProfile = function(scope) { - var token = AccessToken.get(); - - if (token && token.access_token && scope.profileUri) { - Profile.find(scope.profileUri).success(function(response) { - scope.profile = response - }) + var definition = { + restrict: 'AE', + replace: true, + scope: { + site: '@', // (required) set the oauth server host (e.g. http://oauth.example.com) + clientId: '@', // (required) client id + redirectUri: '@', // (required) client redirect uri + responseType: '@', // (optional) response type, defaults to token (use 'token' for implicit flow and 'code' for authorization code flow + scope: '@', // (optional) scope + profileUri: '@', // (optional) user profile uri (e.g http://example.com/me) + template: '@', // (optional) template to render (e.g views/templates/default.html) + text: '@', // (optional) login text + authorizePath: '@', // (optional) authorization url + state: '@', // (optional) An arbitrary unique string created by your app to guard against Cross-site Request Forgery + storage: '@' // (optional) Store token in 'sessionStorage' or 'localStorage', defaults to 'sessionStorage' } }; - var initView = function() { - var token = AccessToken.get(); - - if (!token) { return loggedOut() } // without access token it's logged out - if (token.access_token) { return authorized() } // if there is the access token we are done - if (token.error) { return denied() } // if the request has been denied we fire the denied event - }; - - scope.login = function() { - Endpoint.redirect(); - }; + definition.link = function postLink(scope, element) { + scope.show = 'none'; - scope.logout = function() { - AccessToken.destroy(scope); - $rootScope.$broadcast('oauth:logout'); - loggedOut(); - }; - - scope.$on('oauth:expired', function() { - AccessToken.destroy(scope); - scope.show = 'logged-out'; - }); + scope.$watch('clientId', function() { + init(); + }); - // user is authorized - var authorized = function() { - $rootScope.$broadcast('oauth:authorized', AccessToken.get()); - scope.show = 'logged-in'; - }; + var init = function() { + initAttributes(); // sets defaults + Storage.use(scope.storage);// set storage + compile(); // compiles the desired layout + Endpoint.set(scope); // sets the oauth authorization url + AccessToken.set(scope); // sets the access token object (if existing, from fragment or session) + initProfile(scope); // gets the profile resource (if existing the access token) + initView(); // sets the view (logged in or out) + }; + + var initAttributes = function() { + scope.authorizePath = scope.authorizePath || '/oauth/authorize'; + scope.tokenPath = scope.tokenPath || '/oauth/token'; + scope.template = scope.template || 'views/templates/default.html'; + scope.responseType = scope.responseType || 'token'; + scope.text = scope.text || 'Sign In'; + scope.state = scope.state || undefined; + scope.scope = scope.scope || undefined; + scope.storage = scope.storage || 'sessionStorage'; + }; + + var compile = function() { + $http.get(scope.template, { cache: $templateCache }).success(function(html) { + element.html(html); + $compile(element.contents())(scope); + }); + }; + + var initProfile = function(scope) { + var token = AccessToken.get(); + + if (token && token.access_token && scope.profileUri) { + Profile.find(scope.profileUri).success(function(response) { + scope.profile = response; + }); + } + }; + + var initView = function() { + var token = AccessToken.get(); + + if (!token) { + return loggedOut(); // without access token it's logged out + } + if (token.access_token) { + return authorized(); // if there is the access token we are done + } + if (token.error) { + return denied(); // if the request has been denied we fire the denied event + } + }; + + scope.login = function() { + Endpoint.redirect(); + }; + + scope.logout = function() { + AccessToken.destroy(scope); + $rootScope.$broadcast('oauth:logout'); + loggedOut(); + }; + + scope.$on('oauth:expired', function() { + AccessToken.destroy(scope); + scope.show = 'logged-out'; + }); - // set the oauth directive to the logged-out status - var loggedOut = function() { - $rootScope.$broadcast('oauth:loggedOut'); - scope.show = 'logged-out'; - }; + // user is authorized + var authorized = function() { + $rootScope.$broadcast('oauth:authorized', AccessToken.get()); + scope.show = 'logged-in'; + }; + + // set the oauth directive to the logged-out status + var loggedOut = function() { + $rootScope.$broadcast('oauth:loggedOut'); + scope.show = 'logged-out'; + }; + + // set the oauth directive to the denied status + var denied = function() { + scope.show = 'denied'; + $rootScope.$broadcast('oauth:denied'); + }; + + // Updates the template at runtime + scope.$on('oauth:template:update', function(event, template) { + scope.template = template; + compile(scope); + }); - // set the oauth directive to the denied status - var denied = function() { - scope.show = 'denied'; - $rootScope.$broadcast('oauth:denied'); + // Hack to update the directive content on logout + // TODO think to a cleaner solution + scope.$on('$routeChangeSuccess', function () { + init(); + }); }; - // Updates the template at runtime - scope.$on('oauth:template:update', function(event, template) { - scope.template = template; - compile(scope); - }); - - // Hack to update the directive content on logout - // TODO think to a cleaner solution - scope.$on('$routeChangeSuccess', function () { - init(); - }); - }; - - return definition -}]); + return definition; + } +]); diff --git a/app/scripts/interceptors/oauth-interceptor.js b/app/scripts/interceptors/oauth-interceptor.js index 7759149..be3d8a9 100644 --- a/app/scripts/interceptors/oauth-interceptor.js +++ b/app/scripts/interceptors/oauth-interceptor.js @@ -2,21 +2,22 @@ var interceptorService = angular.module('oauth.interceptor', []); -interceptorService.factory('ExpiredInterceptor', ['Storage', '$rootScope', '$q', function (Storage, $rootScope, $q) { +interceptorService.factory('ExpiredInterceptor', ['Storage', '$rootScope', function (Storage, $rootScope) { var service = {}; service.request = function(config) { var token = Storage.get('token'); - if (token && expired(token)) + if (token && expired(token)) { $rootScope.$broadcast('oauth:expired', token); + } return config; }; var expired = function(token) { - return (token && token.expires_at && new Date(token.expires_at) < new Date()) + return (token && token.expires_at && new Date(token.expires_at) < new Date()); }; return service; diff --git a/app/scripts/services/access-token.js b/app/scripts/services/access-token.js index 85547a1..4836165 100644 --- a/app/scripts/services/access-token.js +++ b/app/scripts/services/access-token.js @@ -4,176 +4,174 @@ var accessTokenService = angular.module('oauth.accessToken', []); accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$location', '$interval', function(Storage, $rootScope, $location, $interval){ - var service = { - token: null - }, - oAuth2HashTokens = [ //per http://tools.ietf.org/html/rfc6749#section-4.2.2 - 'access_token', 'token_type', 'expires_in', 'scope', 'state', - 'error','error_description' - ]; - - /** - * Returns the access token. - */ - service.get = function(){ - return this.token; - }; - - /** - * Sets and returns the access token. It tries (in order) the following strategies: - * - takes the token from the fragment URI - * - takes the token from the sessionStorage - */ - service.set = function(){ - this.setTokenFromString($location.hash()); - - //If hash is present in URL always use it, cuz its coming from oAuth2 provider redirect - if(null === service.token){ - setTokenFromSession(); - } - - return this.token; - }; - - /** - * Delete the access token and remove the session. - * @returns {null} - */ - service.destroy = function(){ - Storage.delete('token'); - this.token = null; - return this.token; - }; - - - /** - * Tells if the access token is expired. - */ - service.expired = function(){ - return (this.token && this.token.expires_at && this.token.expires_at Date: Thu, 11 Jun 2015 23:27:41 +0200 Subject: [PATCH 064/107] Bump to v0.4.1 --- CHANGELOG.md | 4 ++++ bower.json | 2 +- dist/oauth-ng.js | 2 +- package.json | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 28936c9..2a26f98 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 0.4.1 (Jun 11, 2015) + +* Add OpenID support + ## 0.4.0 (May 26, 2015) * Fix jshint config file diff --git a/bower.json b/bower.json index e91e172..84d8454 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "oauth-ng", - "version": "0.4.0", + "version": "0.4.1", "main": [ "dist/oauth-ng.js", "dist/views/templates/default" diff --git a/dist/oauth-ng.js b/dist/oauth-ng.js index 5ac74bf..5780388 100644 --- a/dist/oauth-ng.js +++ b/dist/oauth-ng.js @@ -1,4 +1,4 @@ -/* oauth-ng - v0.3.10 - 2015-05-09 */ +/* oauth-ng - v0.4.1 - 2015-06-11 */ 'use strict'; diff --git a/package.json b/package.json index c907f1c..88bb66b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "oauth-ng", - "version": "0.4.0", + "version": "0.4.1", "author": "Andrea Reginato ", "description": "AngularJS Directive for OAuth 2.0", "repository": { From 30a83d89e966ba8b766ac8afe14baf5609a2deb3 Mon Sep 17 00:00:00 2001 From: Massimiliano Sartoretto Date: Fri, 19 Jun 2015 12:22:33 -0700 Subject: [PATCH 065/107] Fix #83 expiry definition --- app/scripts/services/access-token.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/scripts/services/access-token.js b/app/scripts/services/access-token.js index 4836165..31c8eb1 100644 --- a/app/scripts/services/access-token.js +++ b/app/scripts/services/access-token.js @@ -49,7 +49,7 @@ accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$location', * Tells if the access token is expired. */ service.expired = function(){ - return (this.token && this.token.expires_at && this.token.expires_at Date: Fri, 19 Jun 2015 12:26:37 -0700 Subject: [PATCH 066/107] Bump to v0.4.2 --- CHANGELOG.md | 5 + bower.json | 2 +- dist/oauth-ng.js | 615 ++++++++++++++++++++++++----------------------- package.json | 2 +- 4 files changed, 318 insertions(+), 306 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a26f98..d930874 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## 0.4.2 (Jun 19, 2015) + +* Make the code more JSHint friendly +* Fix expiry definition in the access token service + ## 0.4.1 (Jun 11, 2015) * Add OpenID support diff --git a/bower.json b/bower.json index 84d8454..2cf9b5f 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "oauth-ng", - "version": "0.4.1", + "version": "0.4.2", "main": [ "dist/oauth-ng.js", "dist/views/templates/default" diff --git a/dist/oauth-ng.js b/dist/oauth-ng.js index 5780388..a1e8d54 100644 --- a/dist/oauth-ng.js +++ b/dist/oauth-ng.js @@ -1,18 +1,17 @@ -/* oauth-ng - v0.4.1 - 2015-06-11 */ +/* oauth-ng - v0.4.2 - 2015-06-19 */ 'use strict'; // App libraries -var app = angular.module('oauth', [ +angular.module('oauth', [ 'oauth.directive', // login directive 'oauth.accessToken', // access token service 'oauth.endpoint', // oauth endpoint service 'oauth.profile', // profile model 'oauth.storage', // storage 'oauth.interceptor' // bearer token interceptor -]); - -angular.module('oauth').config(['$locationProvider','$httpProvider', +]) + .config(['$locationProvider','$httpProvider', function($locationProvider, $httpProvider) { $httpProvider.interceptors.push('ExpiredInterceptor'); }]); @@ -23,178 +22,176 @@ var accessTokenService = angular.module('oauth.accessToken', []); accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$location', '$interval', function(Storage, $rootScope, $location, $interval){ - var service = { - token: null - }, - oAuth2HashTokens = [ //per http://tools.ietf.org/html/rfc6749#section-4.2.2 - 'access_token', 'token_type', 'expires_in', 'scope', 'state', - 'error','error_description' - ]; - - /** - * Returns the access token. - */ - service.get = function(){ - return this.token; - }; + var service = { + token: null + }, + oAuth2HashTokens = [ //per http://tools.ietf.org/html/rfc6749#section-4.2.2 + 'access_token', 'token_type', 'expires_in', 'scope', 'state', + 'error','error_description' + ]; - /** - * Sets and returns the access token. It tries (in order) the following strategies: - * - takes the token from the fragment URI - * - takes the token from the sessionStorage - */ - service.set = function(){ - this.setTokenFromString($location.hash()); - - //If hash is present in URL always use it, cuz its coming from oAuth2 provider redirect - if(null === service.token){ - setTokenFromSession(); - } + /** + * Returns the access token. + */ + service.get = function(){ + return this.token; + }; - return this.token; - }; + /** + * Sets and returns the access token. It tries (in order) the following strategies: + * - takes the token from the fragment URI + * - takes the token from the sessionStorage + */ + service.set = function(){ + this.setTokenFromString($location.hash()); - /** - * Delete the access token and remove the session. - * @returns {null} - */ - service.destroy = function(){ - Storage.delete('token'); - this.token = null; - return this.token; - }; + //If hash is present in URL always use it, cuz its coming from oAuth2 provider redirect + if(null === service.token){ + setTokenFromSession(); + } + return this.token; + }; - /** - * Tells if the access token is expired. - */ - service.expired = function(){ - return (this.token && this.token.expires_at && this.token.expires_at", "description": "AngularJS Directive for OAuth 2.0", "repository": { From 7ecc4b71afa85177bbbc26c69840abfd70d6f120 Mon Sep 17 00:00:00 2001 From: Massimiliano Sartoretto Date: Thu, 13 Aug 2015 21:55:56 -0700 Subject: [PATCH 067/107] Update README.md --- README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b2c1cfb..c3cfe33 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,7 @@ # AngularJS directive for OAuth 2.0 [![Build Status](https://travis-ci.org/andreareginato/oauth-ng.svg?branch=master)](https://travis-ci.org/andreareginato/oauth-ng) -AngularJS directive for the [OAuth 2.0 Authorization code Flow](http://tools.ietf.org/html/rfc6749#section-1.3.1) -and the [OAuth 2.0 Implicit Flow](http://tools.ietf.org/html/rfc6749#section-1.3.2). +AngularJS directive for the [OAuth 2.0 Implicit Flow](http://tools.ietf.org/html/rfc6749#section-1.3.2). ## Documentation @@ -70,6 +69,7 @@ that can improve the project. Project created and released as open-source thanks to [Lelylan](http://lelylan.com). * [Andrea Reginato](http://twitter.com/andreareginato) +* [Massimiliano Sartoretto](http://twitter.com/___Sarto) ## Contributors @@ -81,6 +81,9 @@ for submitting patches. See [CHANGELOG](https://github.com/andreareginato/oauth-ng/blob/master/CHANGELOG.md) +## TODO +:white_medium_square: [OAuth 2.0 Authorization code Flow](http://tools.ietf.org/html/rfc6749#section-1.3.1) + ## Copyright Copyright (c) 2014 [Lelylan](http://lelylan.com). From 740f66875c7515d4537abb0cfdff953798770979 Mon Sep 17 00:00:00 2001 From: Pavel R Date: Sun, 16 Aug 2015 18:25:26 +0200 Subject: [PATCH 068/107] #91 prevent two ':expired' events to be fired when refreshing the token --- app/scripts/services/access-token.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/scripts/services/access-token.js b/app/scripts/services/access-token.js index 31c8eb1..1721867 100644 --- a/app/scripts/services/access-token.js +++ b/app/scripts/services/access-token.js @@ -152,7 +152,7 @@ accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$location', return; } var time = (new Date(service.token.expires_at))-(new Date()); - if(time){ + if(time && time > 0){ $interval(function(){ $rootScope.$broadcast('oauth:expired', service.token); }, time, 1); From 05c12bd7421976657c9a2e387cd5db2b5b719650 Mon Sep 17 00:00:00 2001 From: Pavel Rabetski Date: Thu, 20 Aug 2015 14:03:52 +0200 Subject: [PATCH 069/107] fix endpoint.js spec according to jshit --- test/spec/services/endpoint.js | 42 +++++++++++++++++----------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/test/spec/services/endpoint.js b/test/spec/services/endpoint.js index 850ca9b..f763b30 100644 --- a/test/spec/services/endpoint.js +++ b/test/spec/services/endpoint.js @@ -95,7 +95,7 @@ describe('Endpoint', function() { Endpoint.set(params); }); - describe( "without overrides", function(){ + describe( 'without overrides', function(){ beforeEach(function() { result = Endpoint.get(); }); @@ -105,16 +105,16 @@ describe('Endpoint', function() { }); }); - describe( "with state override", function(){ - it( "injects the state correct", function(){ + describe( 'with state override', function(){ + it( 'injects the state correct', function(){ var override = { state: 'testState' }; var result = Endpoint.get( override ); expect( result ).toEqual( uri + encodeURIComponent( override.state ) ); }); }); - describe( "with clientId override", function(){ - it( "injects the override", function(){ + describe( 'with clientId override', function(){ + it( 'injects the override', function(){ var override = { clientId: 'unicorn' }; var result = Endpoint.get( override ); var expectedUri = 'http://example.com/oauth/authorize?response_type=token&client_id=unicorn&redirect_uri=http%3A%2F%2Fexample.com%2Fredirect&scope=scope&state='; @@ -122,8 +122,8 @@ describe('Endpoint', function() { }); }); - describe( "with scope override", function(){ - it( "injects the override", function(){ + describe( 'with scope override', function(){ + it( 'injects the override', function(){ var override = { scope: 'stars' }; var result = Endpoint.get( override ); var expectedUri = 'http://example.com/oauth/authorize?response_type=token&client_id=client-id&redirect_uri=http%3A%2F%2Fexample.com%2Fredirect&scope=stars&state='; @@ -131,16 +131,16 @@ describe('Endpoint', function() { }); }); - describe( "with state override", function(){ - it( "injects the state correct", function(){ + describe( 'with state override', function(){ + it( 'injects the state correct', function(){ var override = { state: 'testState' }; var result = Endpoint.get( override ); expect( result ).toEqual( uri + encodeURIComponent( override.state ) ); }); }); - describe( "with responseType override", function(){ - it( "injects the correct repsonseType", function(){ + describe( 'with responseType override', function(){ + it( 'injects the correct repsonseType', function(){ var override = { responseType: 'id_token' }; var result = Endpoint.get( override ); var expectedUri = 'http://example.com/oauth/authorize?response_type=id_token&client_id=client-id&redirect_uri=http%3A%2F%2Fexample.com%2Fredirect&scope=scope&state='; @@ -148,8 +148,8 @@ describe('Endpoint', function() { }); }); - describe( "with site override", function(){ - it( "injects the correct site", function(){ + describe( 'with site override', function(){ + it( 'injects the correct site', function(){ var override = { site: 'https://invincible.test/' }; var result = Endpoint.get( override ); var expectedUri = 'https://invincible.test/oauth/authorize?response_type=token&client_id=client-id&redirect_uri=http%3A%2F%2Fexample.com%2Fredirect&scope=scope&state='; @@ -157,8 +157,8 @@ describe('Endpoint', function() { }); }); - describe( "with authorize path overrides", function(){ - it( "injects the correct authorize path", function(){ + describe( 'with authorize path overrides', function(){ + it( 'injects the correct authorize path', function(){ var override = { authorizePath: '/end/here' }; var result = Endpoint.get( override ); var expectedUri = 'http://example.com/end/here?response_type=token&client_id=client-id&redirect_uri=http%3A%2F%2Fexample.com%2Fredirect&scope=scope&state='; @@ -166,8 +166,8 @@ describe('Endpoint', function() { }); }); - describe( "given scope with spaces", function(){ - it( "correctly encodes the spaces", function(){ + describe( 'given scope with spaces', function(){ + it( 'correctly encodes the spaces', function(){ var override = { scope: 'read write profile openid' }; var result = Endpoint.get( override ); var expectedUri = 'http://example.com/oauth/authorize?response_type=token&client_id=client-id&redirect_uri=http%3A%2F%2Fexample.com%2Fredirect&scope=read%20write%20profile%20openid&state='; @@ -175,8 +175,8 @@ describe('Endpoint', function() { }); }); - describe( "on repsonse type with spaces", function(){ - it( "correctly encodes the spaces", function(){ + describe( 'on repsonse type with spaces', function(){ + it( 'correctly encodes the spaces', function(){ var override = { responseType: 'id_token token' }; var result = Endpoint.get( override ); var expectedUri = 'http://example.com/oauth/authorize?response_type=id_token%20token&client_id=client-id&redirect_uri=http%3A%2F%2Fexample.com%2Fredirect&scope=scope&state='; @@ -184,8 +184,8 @@ describe('Endpoint', function() { }); }); - describe( "when provided with a nonce", function(){ - it( "adds the nonce parameter", function(){ + describe( 'when provided with a nonce', function(){ + it( 'adds the nonce parameter', function(){ var override = { nonce: '987654321' }; var result = Endpoint.get( override ); var expectedUri = 'http://example.com/oauth/authorize?response_type=token&client_id=client-id&redirect_uri=http%3A%2F%2Fexample.com%2Fredirect&scope=scope&state=&nonce=987654321'; From f0cf8ddb95f610ba4fbdcdbb6bdf03101f4c0303 Mon Sep 17 00:00:00 2001 From: Eric Lundgren Date: Wed, 19 Aug 2015 08:50:09 -0700 Subject: [PATCH 070/107] Added ability to append auth token to protected resources Conflicts: app/scripts/services/access-token.js dist/oauth-ng.js --- app/index.html | 1 + app/scripts/app.js | 3 +- app/scripts/services/oauth-configuration.js | 38 +++++++++++++++++ dist/oauth-ng.js | 45 +++++++++++++++++++-- 4 files changed, 83 insertions(+), 4 deletions(-) create mode 100644 app/scripts/services/oauth-configuration.js diff --git a/app/index.html b/app/index.html index 1a3321f..1a20b68 100644 --- a/app/index.html +++ b/app/index.html @@ -49,6 +49,7 @@ + diff --git a/app/scripts/app.js b/app/scripts/app.js index a746df7..11fc728 100644 --- a/app/scripts/app.js +++ b/app/scripts/app.js @@ -7,7 +7,8 @@ angular.module('oauth', [ 'oauth.endpoint', // oauth endpoint service 'oauth.profile', // profile model 'oauth.storage', // storage - 'oauth.interceptor' // bearer token interceptor + 'oauth.interceptor', // bearer token interceptor + 'oauth.configuration' // token appender ]) .config(['$locationProvider','$httpProvider', function($locationProvider, $httpProvider) { diff --git a/app/scripts/services/oauth-configuration.js b/app/scripts/services/oauth-configuration.js new file mode 100644 index 0000000..54e848c --- /dev/null +++ b/app/scripts/services/oauth-configuration.js @@ -0,0 +1,38 @@ +'use strict'; + +var oauthConfigurationService = angular.module('oauth.configuration', []); + +oauthConfigurationService.provider('OAuthConfiguration', function() { + var _config = {}; + + this.init = function(config, httpProvider) { + _config.protectedResources = config.protectedResources || []; + httpProvider.interceptors.push('AuthInterceptor'); + }; + + this.$get = function() { + return { + getConfig: function() { + return _config; + } + }; + }; +}) +.factory('AuthInterceptor', function($q, $rootScope, OAuthConfiguration, AccessToken) { + return { + 'request': function(config) { + OAuthConfiguration.getConfig().protected_resources.forEach(function(resource) { + // If the url is one of the protected resources, we want to see if there's a token and then + // add the token if it exists. + if (config.url.indexOf(resource) > -1) { + var token = AccessToken.get(); + if (token) { + config.headers.Authorization = 'Bearer ' + token.access_token; + } + } + }); + + return config; + } + }; +}); \ No newline at end of file diff --git a/dist/oauth-ng.js b/dist/oauth-ng.js index a1e8d54..13c9e33 100644 --- a/dist/oauth-ng.js +++ b/dist/oauth-ng.js @@ -1,4 +1,4 @@ -/* oauth-ng - v0.4.2 - 2015-06-19 */ +/* oauth-ng - v0.4.2 - 2015-08-27 */ 'use strict'; @@ -9,7 +9,8 @@ angular.module('oauth', [ 'oauth.endpoint', // oauth endpoint service 'oauth.profile', // profile model 'oauth.storage', // storage - 'oauth.interceptor' // bearer token interceptor + 'oauth.interceptor', // bearer token interceptor + 'oauth.configuration' // token appender ]) .config(['$locationProvider','$httpProvider', function($locationProvider, $httpProvider) { @@ -170,7 +171,7 @@ accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$location', return; } var time = (new Date(service.token.expires_at))-(new Date()); - if(time){ + if(time && time > 0){ $interval(function(){ $rootScope.$broadcast('oauth:expired', service.token); }, time, 1); @@ -333,6 +334,44 @@ storageService.factory('Storage', ['$rootScope', '$sessionStorage', '$localStora }]); 'use strict'; +var oauthConfigurationService = angular.module('oauth.configuration', []); + +oauthConfigurationService.provider('OAuthConfiguration', function() { + var _config = {}; + + this.init = function(config, httpProvider) { + _config.protectedResources = config.protectedResources || []; + httpProvider.interceptors.push('AuthInterceptor'); + }; + + this.$get = function() { + return { + getConfig: function() { + return _config; + } + }; + }; +}) +.factory('AuthInterceptor', function($q, $rootScope, OAuthConfiguration, AccessToken) { + return { + 'request': function(config) { + OAuthConfiguration.getConfig().protected_resources.forEach(function(resource) { + // If the url is one of the protected resources, we want to see if there's a token and then + // add the token if it exists. + if (config.url.indexOf(resource) > -1) { + var token = AccessToken.get(); + if (token) { + config.headers.Authorization = 'Bearer ' + token.access_token; + } + } + }); + + return config; + } + }; +}); +'use strict'; + var interceptorService = angular.module('oauth.interceptor', []); interceptorService.factory('ExpiredInterceptor', ['Storage', '$rootScope', function (Storage, $rootScope) { From 11f78817b6bc83f849d735544b6bdee926338eea Mon Sep 17 00:00:00 2001 From: Eric Lundgren Date: Wed, 19 Aug 2015 10:14:48 -0700 Subject: [PATCH 071/107] Fixed protectedResource variable name --- app/scripts/services/oauth-configuration.js | 2 +- dist/oauth-ng.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/scripts/services/oauth-configuration.js b/app/scripts/services/oauth-configuration.js index 54e848c..765e2a8 100644 --- a/app/scripts/services/oauth-configuration.js +++ b/app/scripts/services/oauth-configuration.js @@ -21,7 +21,7 @@ oauthConfigurationService.provider('OAuthConfiguration', function() { .factory('AuthInterceptor', function($q, $rootScope, OAuthConfiguration, AccessToken) { return { 'request': function(config) { - OAuthConfiguration.getConfig().protected_resources.forEach(function(resource) { + OAuthConfiguration.getConfig().protectedResources.forEach(function(resource) { // If the url is one of the protected resources, we want to see if there's a token and then // add the token if it exists. if (config.url.indexOf(resource) > -1) { diff --git a/dist/oauth-ng.js b/dist/oauth-ng.js index 13c9e33..333070b 100644 --- a/dist/oauth-ng.js +++ b/dist/oauth-ng.js @@ -355,7 +355,7 @@ oauthConfigurationService.provider('OAuthConfiguration', function() { .factory('AuthInterceptor', function($q, $rootScope, OAuthConfiguration, AccessToken) { return { 'request': function(config) { - OAuthConfiguration.getConfig().protected_resources.forEach(function(resource) { + OAuthConfiguration.getConfig().protectedResources.forEach(function(resource) { // If the url is one of the protected resources, we want to see if there's a token and then // add the token if it exists. if (config.url.indexOf(resource) > -1) { From 62239db2836f84301b1517b77c8377fafb9c93e9 Mon Sep 17 00:00:00 2001 From: Eric Lundgren Date: Thu, 27 Aug 2015 11:25:23 -0700 Subject: [PATCH 072/107] Added some unit tests for OAuthConfigurationProvider --- app/scripts/services/oauth-configuration.js | 4 +- test/spec/services/oauth-configuration.js | 70 +++++++++++++++++++++ 2 files changed, 72 insertions(+), 2 deletions(-) create mode 100644 test/spec/services/oauth-configuration.js diff --git a/app/scripts/services/oauth-configuration.js b/app/scripts/services/oauth-configuration.js index 765e2a8..2cdc22a 100644 --- a/app/scripts/services/oauth-configuration.js +++ b/app/scripts/services/oauth-configuration.js @@ -18,7 +18,7 @@ oauthConfigurationService.provider('OAuthConfiguration', function() { }; }; }) -.factory('AuthInterceptor', function($q, $rootScope, OAuthConfiguration, AccessToken) { +.factory('AuthInterceptor', ['OAuthConfiguration', 'AccessToken', function(OAuthConfiguration, AccessToken) { return { 'request': function(config) { OAuthConfiguration.getConfig().protectedResources.forEach(function(resource) { @@ -35,4 +35,4 @@ oauthConfigurationService.provider('OAuthConfiguration', function() { return config; } }; -}); \ No newline at end of file +}]); \ No newline at end of file diff --git a/test/spec/services/oauth-configuration.js b/test/spec/services/oauth-configuration.js new file mode 100644 index 0000000..4ff6796 --- /dev/null +++ b/test/spec/services/oauth-configuration.js @@ -0,0 +1,70 @@ +'use strict'; + +describe('AuthInterceptor', function() { + var theOAuthConfigurationProvider, theOAuthConfiguration, OAuthConfiguration, AccessToken, AuthInterceptor, $location, result; + var protectedConfig, unprotectedConfig; + + beforeEach(module('oauth')); + + var expires_at = '2014-08-17T17:38:37.584Z'; + var fragment = 'access_token=token&token_type=bearer&expires_in=7200&state=/path&extra=stuff' + + + beforeEach(function() { + var fakeModule = angular.module('test.app.config', function(){}); + fakeModule.config(function(OAuthConfigurationProvider, $httpProvider) { + theOAuthConfigurationProvider = OAuthConfigurationProvider; + theOAuthConfigurationProvider.init({protectedResources:['http://api.protected/']}, $httpProvider); + theOAuthConfiguration = theOAuthConfigurationProvider.$get(); + }) + module('oauth', 'test.app.config'); + + inject(function() {}); + }) + + beforeEach(inject(function() { OAuthConfiguration = theOAuthConfiguration })); + beforeEach(inject(function($injector) { $location = $injector.get('$location'); })); + beforeEach(inject(function($injector) { AccessToken = $injector.get('AccessToken'); AccessToken.destroy(); } )); + beforeEach(inject(function($injector) { AuthInterceptor = $injector.get('AuthInterceptor'); })); + + beforeEach(function() { + protectedConfig = { url: 'http://api.protected/', headers: { 'Accept': 'application/json'} }; + unprotectedConfig = { url: 'http://api.unprotected/', headers: { 'Accept': 'application/json' } }; + }); + + describe('when the resource is protected', function() { + beforeEach(function() { + $location.hash(fragment); + AccessToken.set(); + result = AuthInterceptor.request(protectedConfig); + }); + + it('should have the token in the header', function() { + expect(result.headers.Authorization).toEqual('Bearer token'); + }); + }); + + describe('when the resource is unprotected', function() { + beforeEach(function() { + $location.hash(fragment); + AccessToken.set(); + result = AuthInterceptor.request(unprotectedConfig); + }); + + it('should not have the token in the header', function() { + expect(result.headers.Authorization).toBeUndefined(); + }); + }); + + describe('when the user is not logged in', function() { + beforeEach(function() { + AccessToken.destroy(); + result = AuthInterceptor.request(protectedConfig); + }); + + it('should not have anything in the authorization header', function() { + expect(result.headers.Authorization).toBeUndefined(); + }); + }); + +}) \ No newline at end of file From 246a13ce41d36cdaf892b2be5de2525b94c76d03 Mon Sep 17 00:00:00 2001 From: "ed.tyler" Date: Thu, 5 Nov 2015 09:07:29 +0000 Subject: [PATCH 073/107] added nonce parameter to directive --- app/scripts/directives/oauth.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/scripts/directives/oauth.js b/app/scripts/directives/oauth.js index 7970f91..798ce06 100644 --- a/app/scripts/directives/oauth.js +++ b/app/scripts/directives/oauth.js @@ -28,7 +28,8 @@ directives.directive('oauth', [ text: '@', // (optional) login text authorizePath: '@', // (optional) authorization url state: '@', // (optional) An arbitrary unique string created by your app to guard against Cross-site Request Forgery - storage: '@' // (optional) Store token in 'sessionStorage' or 'localStorage', defaults to 'sessionStorage' + storage: '@', // (optional) Store token in 'sessionStorage' or 'localStorage', defaults to 'sessionStorage' + nonce: '@' // (optional) Send nonce on auth request } }; From bbc15b9b58a16263c964995e1766e997f476f4a9 Mon Sep 17 00:00:00 2001 From: "ed.tyler" Date: Thu, 5 Nov 2015 12:35:02 +0000 Subject: [PATCH 074/107] Ran grunt build --- dist/oauth-ng.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/dist/oauth-ng.js b/dist/oauth-ng.js index 333070b..0240cf0 100644 --- a/dist/oauth-ng.js +++ b/dist/oauth-ng.js @@ -1,4 +1,4 @@ -/* oauth-ng - v0.4.2 - 2015-08-27 */ +/* oauth-ng - v0.4.2 - 2015-11-05 */ 'use strict'; @@ -352,7 +352,7 @@ oauthConfigurationService.provider('OAuthConfiguration', function() { }; }; }) -.factory('AuthInterceptor', function($q, $rootScope, OAuthConfiguration, AccessToken) { +.factory('AuthInterceptor', ['OAuthConfiguration', 'AccessToken', function(OAuthConfiguration, AccessToken) { return { 'request': function(config) { OAuthConfiguration.getConfig().protectedResources.forEach(function(resource) { @@ -369,7 +369,7 @@ oauthConfigurationService.provider('OAuthConfiguration', function() { return config; } }; -}); +}]); 'use strict'; var interceptorService = angular.module('oauth.interceptor', []); @@ -425,7 +425,8 @@ directives.directive('oauth', [ text: '@', // (optional) login text authorizePath: '@', // (optional) authorization url state: '@', // (optional) An arbitrary unique string created by your app to guard against Cross-site Request Forgery - storage: '@' // (optional) Store token in 'sessionStorage' or 'localStorage', defaults to 'sessionStorage' + storage: '@', // (optional) Store token in 'sessionStorage' or 'localStorage', defaults to 'sessionStorage' + nonce: '@' // (optional) Send nonce on auth request } }; From 485d4277f5b17275cd6c7df7d703ed38230a3c99 Mon Sep 17 00:00:00 2001 From: edtyl3r Date: Thu, 5 Nov 2015 13:24:13 +0000 Subject: [PATCH 075/107] Update bower.json --- bower.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bower.json b/bower.json index 2cf9b5f..bd90bbe 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "oauth-ng", - "version": "0.4.2", + "version": "0.4.3", "main": [ "dist/oauth-ng.js", "dist/views/templates/default" From 3b6d608583c76f5315a40716126f22e7f65d7455 Mon Sep 17 00:00:00 2001 From: "ed.tyler" Date: Fri, 27 Nov 2015 13:50:33 +0000 Subject: [PATCH 076/107] Added state routing --- app/scripts/directives/oauth.js | 4 ++++ dist/oauth-ng.js | 6 +++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/app/scripts/directives/oauth.js b/app/scripts/directives/oauth.js index 798ce06..b585dec 100644 --- a/app/scripts/directives/oauth.js +++ b/app/scripts/directives/oauth.js @@ -136,6 +136,10 @@ directives.directive('oauth', [ scope.$on('$routeChangeSuccess', function () { init(); }); + + scope.$on('$stateChangeSuccess', function () { + init(); + }); }; return definition; diff --git a/dist/oauth-ng.js b/dist/oauth-ng.js index 0240cf0..e88b169 100644 --- a/dist/oauth-ng.js +++ b/dist/oauth-ng.js @@ -1,4 +1,4 @@ -/* oauth-ng - v0.4.2 - 2015-11-05 */ +/* oauth-ng - v0.4.3 - 2015-11-27 */ 'use strict'; @@ -533,6 +533,10 @@ directives.directive('oauth', [ scope.$on('$routeChangeSuccess', function () { init(); }); + + scope.$on('$stateChangeSuccess', function () { + init(); + }); }; return definition; From 220987bbe2408628da7a33179d96e278d3ac03f2 Mon Sep 17 00:00:00 2001 From: Seth Davenport Date: Mon, 30 Nov 2015 17:58:30 -0500 Subject: [PATCH 077/107] Webpack Fix I'd like to be able to use this library from web pack by simply writing `import 'oauth-ng';` This does the job. --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 0c762c9..26b48a5 100644 --- a/package.json +++ b/package.json @@ -55,5 +55,6 @@ "postinstall": "bower install", "clean": "rm -rf node_modules dist app/bower_components", "test": "grunt test && grunt build" - } + }, + "main": "dist/oauth-ng.js" } From 27777a03b49d61b139018cc96d8a01a10cea46a4 Mon Sep 17 00:00:00 2001 From: Massimiliano Sartoretto Date: Tue, 1 Dec 2015 18:54:14 +0100 Subject: [PATCH 078/107] v0.4.4 --- bower.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bower.json b/bower.json index bd90bbe..5966d3d 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "oauth-ng", - "version": "0.4.3", + "version": "0.4.4", "main": [ "dist/oauth-ng.js", "dist/views/templates/default" From 90682e6c699ef210ceef5c889203c0c7e2d8ec9a Mon Sep 17 00:00:00 2001 From: Massimiliano Sartoretto Date: Tue, 1 Dec 2015 19:03:23 +0100 Subject: [PATCH 079/107] Bump to v0.4.4 --- CHANGELOG.md | 8 ++++++++ package.json | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d930874..02b0c2f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## 0.4.4 (Nov 19, 2015) + +* Webpack Fix + +## 0.4.3 (Nov 19, 2015) + +* Added nonce parameter to directive + ## 0.4.2 (Jun 19, 2015) * Make the code more JSHint friendly diff --git a/package.json b/package.json index 26b48a5..a9fe815 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "oauth-ng", - "version": "0.4.2", + "version": "0.4.4", "author": "Andrea Reginato ", "description": "AngularJS Directive for OAuth 2.0", "repository": { From ce5c4b0caf36c97dbbe9fdaa1d55ab456ec5627b Mon Sep 17 00:00:00 2001 From: Yuan Yao Date: Sat, 12 Dec 2015 21:08:10 -0800 Subject: [PATCH 080/107] Add support for OpenID Connect Implicit Flow --- app/index.html | 2 + app/scripts/app.js | 1 + app/scripts/directives/oauth.js | 10 +- app/scripts/services/access-token.js | 18 +- app/scripts/services/id-token.js | 260 ++++++++++++++++++++++++ bower.json | 3 +- dist/oauth-ng.js | 292 ++++++++++++++++++++++++++- karma.conf.js | 1 + test/spec/services/access-token.js | 70 ++++++- test/spec/services/id-token.js | 227 +++++++++++++++++++++ 10 files changed, 869 insertions(+), 15 deletions(-) create mode 100644 app/scripts/services/id-token.js create mode 100644 test/spec/services/id-token.js diff --git a/app/index.html b/app/index.html index 1a20b68..caad0b2 100644 --- a/app/index.html +++ b/app/index.html @@ -41,10 +41,12 @@ + + diff --git a/app/scripts/app.js b/app/scripts/app.js index 11fc728..d78449d 100644 --- a/app/scripts/app.js +++ b/app/scripts/app.js @@ -3,6 +3,7 @@ // App libraries angular.module('oauth', [ 'oauth.directive', // login directive + 'oauth.idToken', // id token service (only for OpenID Connect) 'oauth.accessToken', // access token service 'oauth.endpoint', // oauth endpoint service 'oauth.profile', // profile model diff --git a/app/scripts/directives/oauth.js b/app/scripts/directives/oauth.js index b585dec..e4563cf 100644 --- a/app/scripts/directives/oauth.js +++ b/app/scripts/directives/oauth.js @@ -3,6 +3,7 @@ var directives = angular.module('oauth.directive', []); directives.directive('oauth', [ + 'IdToken', 'AccessToken', 'Endpoint', 'Profile', @@ -12,7 +13,7 @@ directives.directive('oauth', [ '$compile', '$http', '$templateCache', - function(AccessToken, Endpoint, Profile, Storage, $location, $rootScope, $compile, $http, $templateCache) { + function(IdToken, AccessToken, Endpoint, Profile, Storage, $location, $rootScope, $compile, $http, $templateCache) { var definition = { restrict: 'AE', @@ -29,7 +30,11 @@ directives.directive('oauth', [ authorizePath: '@', // (optional) authorization url state: '@', // (optional) An arbitrary unique string created by your app to guard against Cross-site Request Forgery storage: '@', // (optional) Store token in 'sessionStorage' or 'localStorage', defaults to 'sessionStorage' - nonce: '@' // (optional) Send nonce on auth request + nonce: '@', // (optional) Send nonce on auth request + // OpenID Connect extras, more details in id-token.js: + issuer: '@', // (optional for OpenID Connect) issuer of the id_token, should match the 'iss' claim in id_token payload + subject: '@', // (optional for OpenID Connect) subject of the id_token, should match the 'sub' claim in id_token payload + pubKey: '@' // (optional for OpenID Connect) the public key(RSA public key or X509 certificate in PEM format) to verify the signature } }; @@ -45,6 +50,7 @@ directives.directive('oauth', [ Storage.use(scope.storage);// set storage compile(); // compiles the desired layout Endpoint.set(scope); // sets the oauth authorization url + IdToken.set(scope); AccessToken.set(scope); // sets the access token object (if existing, from fragment or session) initProfile(scope); // gets the profile resource (if existing the access token) initView(); // sets the view (logged in or out) diff --git a/app/scripts/services/access-token.js b/app/scripts/services/access-token.js index 1721867..97ec0f7 100644 --- a/app/scripts/services/access-token.js +++ b/app/scripts/services/access-token.js @@ -2,14 +2,17 @@ var accessTokenService = angular.module('oauth.accessToken', []); -accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$location', '$interval', function(Storage, $rootScope, $location, $interval){ +accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$location', '$interval', 'IdToken', function(Storage, $rootScope, $location, $interval, IdToken){ var service = { token: null }, - oAuth2HashTokens = [ //per http://tools.ietf.org/html/rfc6749#section-4.2.2 + hashFragmentKeys = [ + //Oauth2 keys per http://tools.ietf.org/html/rfc6749#section-4.2.2 'access_token', 'token_type', 'expires_in', 'scope', 'state', - 'error','error_description' + 'error','error_description', + //Additional OpenID Connect key per http://openid.net/specs/openid-connect-core-1_0.html#ImplicitAuthResponse + 'id_token' ]; /** @@ -113,6 +116,13 @@ accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$location', params[decodeURIComponent(m[1])] = decodeURIComponent(m[2]); } + // OpenID Connect + if (params.id_token && !params.error) { + IdToken.validateTokensAndPopulateClaims(params); + return params; + } + + // Oauth2 if(params.access_token || params.error){ return params; } @@ -164,7 +174,7 @@ accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$location', */ var removeFragment = function(){ var curHash = $location.hash(); - angular.forEach(oAuth2HashTokens,function(hashKey){ + angular.forEach(hashFragmentKeys,function(hashKey){ var re = new RegExp('&'+hashKey+'(=[^&]*)?|^'+hashKey+'(=[^&]*)?&?'); curHash = curHash.replace(re,''); }); diff --git a/app/scripts/services/id-token.js b/app/scripts/services/id-token.js new file mode 100644 index 0000000..a0fe74e --- /dev/null +++ b/app/scripts/services/id-token.js @@ -0,0 +1,260 @@ +'use strict'; + +var idTokenService = angular.module('oauth.idToken', []); + +idTokenService.factory('IdToken', ['Storage', function(Storage) { + + var service = { + issuer: null, + subject: null, + //clientId, should match 'aud' claim + clientId: null, + /* + The public key to verify the signature, supports: + 1.RSA public key in PEM string: e.g. "-----BEGIN PUBLIC KEY..." + 2.X509 certificate in PEM string: e.g. "-----BEGIN CERTIFICATE..." + 3.JWK (Json Web Key): e.g. {kty: "RSA", n: "0vx7...", e: "AQAB"} + + If not set, the id_token header should carry the key or the url to retrieve the key + */ + pubKey: null + }; + /** + * OidcException + * @param {string } message - The exception error message + * @constructor + */ + function OidcException(message) { + this.name = 'OidcException'; + this.message = message; + } + OidcException.prototype = new Error(); + OidcException.prototype.constructor = OidcException; + + /** + * For initialization + * @param scope + */ + service.set = function(scope) { + this.issuer = scope.issuer; + this.subject = scope.subject; + this.clientId = scope.clientId; + this.pubKey = scope.pubKey; + }; + + /** + * Validate id_token and access_token(if there's one) + * If validation passes, the id_token payload(claims) will be populated to 'params' + * Otherwise error will set to 'params' and tokens will be removed + * + * @param params + */ + service.validateTokensAndPopulateClaims = function(params) { + var valid = false; + var message = ''; + try { + valid = this.validateIdToken(params.id_token); + /* + if response_type is 'id_token token', then we will get both id_token and access_token, + access_token needs to be validated as well + */ + if (valid && params.access_token) { + valid = this.validateAccessToken(params.id_token, params.access_token); + } + } catch (error) { + message = error.message; + } + + if (valid) { + params.id_token_claims = getIdTokenPayload(params.id_token); + } else { + params.id_token = null; + params.access_token = null; + params.error = 'Failed to validate token:' + message; + } + }; + + + /** + * Validates the id_token + * @param {String} idToken The id_token + * @returns {boolean} True if all the check passes, False otherwise + */ + service.validateIdToken = function(idToken) { + return this.verifyIdTokenSig(idToken) && this.verifyIdTokenInfo(idToken); + }; + + /** + * Validate access_token based on the 'alg' and 'at_hash' value of the id_token header + * per spec: http://openid.net/specs/openid-connect-core-1_0.html#ImplicitTokenValidation + * + * @param idToken The id_token + * @param accessToken The access_token + * @returns {boolean} true if validation passes + */ + service.validateAccessToken = function(idToken, accessToken) { + var header = getJsonObject(getIdTokenParts(idToken)[0]); + if (header.at_hash) { + var shalevel = header.alg.substr(2); + if (shalevel !== '256' && shalevel !== '384' && shalevel !== '512') { + throw new OidcException('Unsupported hash algorithm, expecting sha256, sha384, or sha512'); + } + var md = new KJUR.crypto.MessageDigest({'alg':'sha'+ shalevel, 'prov':'cryptojs'}); + //hex representation of the hash + var hexStr = md.digestString(accessToken); + //take first 128bits and base64url encoding it + var expected = hextob64u(hexStr.substring(0, 32)); + + return expected === header.at_hash; + } else { + return true; + } + }; + + /** + * Verifies the ID Token signature using the specified public key + * The id_token header can optionally carry public key or the url to retrieve the public key + * Otherwise will use the public key configured using 'pubKey' + * + * Supports only RSA signatures ['RS256', 'RS384', 'RS512'] + * @param {string}idToken The ID Token string + * @returns {boolean} Indicates whether the signature is valid or not + * @throws {OidcException} + */ + service.verifyIdTokenSig = function (idToken) { + + var idtParts = getIdTokenParts(idToken); + var header = getJsonObject(idtParts[0]); + + if(!header.alg || header.alg.substr(0, 2) !== 'RS') { + throw new OidcException('Unsupported JWS signature algorithm ' + header.alg); + } + + var matchedPubKey = null; + + if (header.jwk) { + //Take the JWK if it comes with the id_token + matchedPubKey = header.jwk; + if (matchedPubKey.kid && header.kid && matchedPubKey.kid !== header.kid) { + throw new OidcException('Json Wek Key ID not match'); + } + /* + TODO: Support for "jku" (JWK Set URL), "x5u" (X.509 URL), "x5c" (X.509 Certificate Chain) parameter to get key + per http://tools.ietf.org/html/draft-ietf-jose-json-web-signature-26#page-9 + */ + } else { + //Use configured public key + matchedPubKey = this.pubKey; + } + + if(!matchedPubKey) { + throw new OidcException('No public key found to verify signature'); + } + + return rsaVerifyJWS(idToken, matchedPubKey, header.alg); + }; + + /** + * Validates the information in the ID Token against configuration + * @param {string} idtoken The ID Token string + * @returns {boolean} Validity of the ID Token + * @throws {OidcException} + */ + service.verifyIdTokenInfo = function(idtoken) { + var valid = false; + + if (idtoken) { + var idtParts = getIdTokenParts(idtoken); + var payload = getJsonObject(idtParts[1]); + if (payload) { + var now = new Date() / 1000; + if (payload.iat > now + 60) + throw new OidcException('ID Token issued time is later than current time'); + + if (payload.exp < now ) + throw new OidcException('ID Token expired'); + + if (now < payload.ntb) + throw new OidcException('ID Token is invalid before '+ payload.ntb); + + if (payload.iss && this.issuer && payload.iss !== this.issuer) + throw new OidcException('Invalid issuer ' + payload.iss + ' != ' + this.issuer); + + if (payload.sub && this.subject && payload.sub !== this.subject) + throw new OidcException('Invalid subject ' + payload.sub + ' != ' + this.subject); + + if (payload.aud) { + if (payload.aud instanceof Array && !KJUR.jws.JWS.inArray(this.clientId, payload.aud)) { + throw new OidcException('Client not in intended audience:' + payload.aud); + } + if (typeof payload.aud === 'string' && payload.aud !== this.clientId) { + throw new OidcException('Invalid audience ' + payload.aud + ' != ' + this.clientId); + } + } + + //TODO: nonce support ? probably need to redo current nonce support + //if(payload['nonce'] != sessionStorage['nonce']) + // throw new OidcException('invalid nonce'); + valid = true; + } else + throw new OidcException('Unable to parse JWS payload'); + } + return valid; + }; + + /** + * Verifies the JWS string using the JWK + * @param {string} jws The JWS string + * @param {object} pubKey The public key that will be used to verify the signature + * @param {string} alg The algorithm string. Expecting 'RS256', 'RS384', or 'RS512' + * @returns {boolean} Validity of the JWS signature + * @throws {OidcException} + */ + var rsaVerifyJWS = function (jws, pubKey, alg) { + /* + convert various public key format to RSAKey object + see @KEYUTIL.getKey for a full list of supported input format + */ + var rsaKey = KEYUTIL.getKey(pubKey); + + return KJUR.jws.JWS.verify(jws, rsaKey, [alg]); + }; + + /** + * Splits the ID Token string into the individual JWS parts + * @param {string} id_token ID Token + * @returns {Array} An array of the JWS compact serialization components (header, payload, signature) + */ + var getIdTokenParts = function (id_token) { + var jws = new KJUR.jws.JWS(); + jws.parseJWS(id_token); + return [jws.parsedJWS.headS, jws.parsedJWS.payloadS, jws.parsedJWS.si]; + }; + + /** + * Get the contents of the ID Token payload as an JSON object + * @param {string} id_token ID Token + * @returns {object} The ID Token payload JSON object + */ + var getIdTokenPayload = function (id_token) { + var parts = getIdTokenParts(id_token); + if(parts) + return getJsonObject(parts[1]); + }; + + /** + * Get the JSON object from the JSON string + * @param {string} jsonS JSON string + * @returns {object|null} JSON object or null + */ + var getJsonObject = function (jsonS) { + var jws = KJUR.jws.JWS; + if(jws.isSafeJSONString(jsonS)) { + return jws.readSafeJSONString(jsonS); + } + return null; + }; + + return service; + +}]); diff --git a/bower.json b/bower.json index 5966d3d..01c4748 100644 --- a/bower.json +++ b/bower.json @@ -15,7 +15,8 @@ ], "dependencies": { "angular": "~1.3.12", - "ngstorage": "~0.3.0" + "ngstorage": "~0.3.0", + "jsrsasign": "~5.0.5" }, "devDependencies": { "json3": "~3.2.4", diff --git a/dist/oauth-ng.js b/dist/oauth-ng.js index e88b169..f6a4bdb 100644 --- a/dist/oauth-ng.js +++ b/dist/oauth-ng.js @@ -1,10 +1,11 @@ -/* oauth-ng - v0.4.3 - 2015-11-27 */ +/* oauth-ng - v0.4.4 - 2015-12-12 */ 'use strict'; // App libraries angular.module('oauth', [ 'oauth.directive', // login directive + 'oauth.idToken', // id token service (only for OpenID Connect) 'oauth.accessToken', // access token service 'oauth.endpoint', // oauth endpoint service 'oauth.profile', // profile model @@ -19,16 +20,280 @@ angular.module('oauth', [ 'use strict'; +var idTokenService = angular.module('oauth.idToken', []); + +idTokenService.factory('IdToken', ['Storage', function(Storage) { + + var service = { + issuer: null, + subject: null, + //clientId, should match 'aud' claim + clientId: null, + /* + The public key to verify the signature, supports: + 1.RSA public key in PEM string: e.g. "-----BEGIN PUBLIC KEY..." + 2.X509 certificate in PEM string: e.g. "-----BEGIN CERTIFICATE..." + 3.JWK (Json Web Key): e.g. {kty: "RSA", n: "0vx7...", e: "AQAB"} + + If not set, the id_token header should carry the key or the url to retrieve the key + */ + pubKey: null + }; + /** + * OidcException + * @param {string } message - The exception error message + * @constructor + */ + function OidcException(message) { + this.name = 'OidcException'; + this.message = message; + } + OidcException.prototype = new Error(); + OidcException.prototype.constructor = OidcException; + + /** + * For initialization + * @param scope + */ + service.set = function(scope) { + this.issuer = scope.issuer; + this.subject = scope.subject; + this.clientId = scope.clientId; + this.pubKey = scope.pubKey; + }; + + /** + * Validate id_token and access_token(if there's one) + * If validation passes, the id_token payload(claims) will be populated to 'params' + * Otherwise error will set to 'params' and tokens will be removed + * + * @param params + */ + service.validateTokensAndPopulateClaims = function(params) { + var valid = false; + var message = ''; + try { + valid = this.validateIdToken(params.id_token); + /* + if response_type is 'id_token token', then we will get both id_token and access_token, + access_token needs to be validated as well + */ + if (valid && params.access_token) { + valid = this.validateAccessToken(params.id_token, params.access_token); + } + } catch (error) { + message = error.message; + } + + if (valid) { + params.id_token_claims = getIdTokenPayload(params.id_token); + } else { + params.id_token = null; + params.access_token = null; + params.error = 'Failed to validate token:' + message; + } + }; + + + /** + * Validates the id_token + * @param {String} idToken The id_token + * @returns {boolean} True if all the check passes, False otherwise + */ + service.validateIdToken = function(idToken) { + return this.verifyIdTokenSig(idToken) && this.verifyIdTokenInfo(idToken); + }; + + /** + * Validate access_token based on the 'alg' and 'at_hash' value of the id_token header + * per spec: http://openid.net/specs/openid-connect-core-1_0.html#ImplicitTokenValidation + * + * @param idToken The id_token + * @param accessToken The access_token + * @returns {boolean} true if validation passes + */ + service.validateAccessToken = function(idToken, accessToken) { + var header = getJsonObject(getIdTokenParts(idToken)[0]); + if (header.at_hash) { + var shalevel = header.alg.substr(2); + if (shalevel !== '256' && shalevel !== '384' && shalevel !== '512') { + throw new OidcException('Unsupported hash algorithm, expecting sha256, sha384, or sha512'); + } + var md = new KJUR.crypto.MessageDigest({'alg':'sha'+ shalevel, 'prov':'cryptojs'}); + //hex representation of the hash + var hexStr = md.digestString(accessToken); + //take first 128bits and base64url encoding it + var expected = hextob64u(hexStr.substring(0, 32)); + + return expected === header.at_hash; + } else { + return true; + } + }; + + /** + * Verifies the ID Token signature using the specified public key + * The id_token header can optionally carry public key or the url to retrieve the public key + * Otherwise will use the public key configured using 'pubKey' + * + * Supports only RSA signatures ['RS256', 'RS384', 'RS512'] + * @param {string}idToken The ID Token string + * @returns {boolean} Indicates whether the signature is valid or not + * @throws {OidcException} + */ + service.verifyIdTokenSig = function (idToken) { + + var idtParts = getIdTokenParts(idToken); + var header = getJsonObject(idtParts[0]); + + if(!header.alg || header.alg.substr(0, 2) !== 'RS') { + throw new OidcException('Unsupported JWS signature algorithm ' + header.alg); + } + + var matchedPubKey = null; + + if (header.jwk) { + //Take the JWK if it comes with the id_token + matchedPubKey = header.jwk; + if (matchedPubKey.kid && header.kid && matchedPubKey.kid !== header.kid) { + throw new OidcException('Json Wek Key ID not match'); + } + /* + TODO: Support for "jku" (JWK Set URL), "x5u" (X.509 URL), "x5c" (X.509 Certificate Chain) parameter to get key + per http://tools.ietf.org/html/draft-ietf-jose-json-web-signature-26#page-9 + */ + } else { + //Use configured public key + matchedPubKey = this.pubKey; + } + + if(!matchedPubKey) { + throw new OidcException('No public key found to verify signature'); + } + + return rsaVerifyJWS(idToken, matchedPubKey, header.alg); + }; + + /** + * Validates the information in the ID Token against configuration + * @param {string} idtoken The ID Token string + * @returns {boolean} Validity of the ID Token + * @throws {OidcException} + */ + service.verifyIdTokenInfo = function(idtoken) { + var valid = false; + + if (idtoken) { + var idtParts = getIdTokenParts(idtoken); + var payload = getJsonObject(idtParts[1]); + if (payload) { + var now = new Date() / 1000; + if (payload.iat > now + 60) + throw new OidcException('ID Token issued time is later than current time'); + + if (payload.exp < now ) + throw new OidcException('ID Token expired'); + + if (now < payload.ntb) + throw new OidcException('ID Token is invalid before '+ payload.ntb); + + if (payload.iss && this.issuer && payload.iss !== this.issuer) + throw new OidcException('Invalid issuer ' + payload.iss + ' != ' + this.issuer); + + if (payload.sub && this.subject && payload.sub !== this.subject) + throw new OidcException('Invalid subject ' + payload.sub + ' != ' + this.subject); + + if (payload.aud) { + if (payload.aud instanceof Array && !KJUR.jws.JWS.inArray(this.clientId, payload.aud)) { + throw new OidcException('Client not in intended audience:' + payload.aud); + } + if (typeof payload.aud === 'string' && payload.aud !== this.clientId) { + throw new OidcException('Invalid audience ' + payload.aud + ' != ' + this.clientId); + } + } + + //TODO: nonce support ? probably need to redo current nonce support + //if(payload['nonce'] != sessionStorage['nonce']) + // throw new OidcException('invalid nonce'); + valid = true; + } else + throw new OidcException('Unable to parse JWS payload'); + } + return valid; + }; + + /** + * Verifies the JWS string using the JWK + * @param {string} jws The JWS string + * @param {object} pubKey The public key that will be used to verify the signature + * @param {string} alg The algorithm string. Expecting 'RS256', 'RS384', or 'RS512' + * @returns {boolean} Validity of the JWS signature + * @throws {OidcException} + */ + var rsaVerifyJWS = function (jws, pubKey, alg) { + /* + convert various public key format to RSAKey object + see @KEYUTIL.getKey for a full list of supported input format + */ + var rsaKey = KEYUTIL.getKey(pubKey); + + return KJUR.jws.JWS.verify(jws, rsaKey, [alg]); + }; + + /** + * Splits the ID Token string into the individual JWS parts + * @param {string} id_token ID Token + * @returns {Array} An array of the JWS compact serialization components (header, payload, signature) + */ + var getIdTokenParts = function (id_token) { + var jws = new KJUR.jws.JWS(); + jws.parseJWS(id_token); + return [jws.parsedJWS.headS, jws.parsedJWS.payloadS, jws.parsedJWS.si]; + }; + + /** + * Get the contents of the ID Token payload as an JSON object + * @param {string} id_token ID Token + * @returns {object} The ID Token payload JSON object + */ + var getIdTokenPayload = function (id_token) { + var parts = getIdTokenParts(id_token); + if(parts) + return getJsonObject(parts[1]); + }; + + /** + * Get the JSON object from the JSON string + * @param {string} jsonS JSON string + * @returns {object|null} JSON object or null + */ + var getJsonObject = function (jsonS) { + var jws = KJUR.jws.JWS; + if(jws.isSafeJSONString(jsonS)) { + return jws.readSafeJSONString(jsonS); + } + return null; + }; + + return service; + +}]); + +'use strict'; + var accessTokenService = angular.module('oauth.accessToken', []); -accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$location', '$interval', function(Storage, $rootScope, $location, $interval){ +accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$location', '$interval', 'IdToken', function(Storage, $rootScope, $location, $interval, IdToken){ var service = { token: null }, - oAuth2HashTokens = [ //per http://tools.ietf.org/html/rfc6749#section-4.2.2 + hashFragmentKeys = [ + //Oauth2 keys per http://tools.ietf.org/html/rfc6749#section-4.2.2 'access_token', 'token_type', 'expires_in', 'scope', 'state', - 'error','error_description' + 'error','error_description', + //Additional OpenID Connect key per http://openid.net/specs/openid-connect-core-1_0.html#ImplicitAuthResponse + 'id_token' ]; /** @@ -132,6 +397,13 @@ accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$location', params[decodeURIComponent(m[1])] = decodeURIComponent(m[2]); } + // OpenID Connect + if (params.id_token && !params.error) { + IdToken.validateTokensAndPopulateClaims(params); + return params; + } + + // Oauth2 if(params.access_token || params.error){ return params; } @@ -183,7 +455,7 @@ accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$location', */ var removeFragment = function(){ var curHash = $location.hash(); - angular.forEach(oAuth2HashTokens,function(hashKey){ + angular.forEach(hashFragmentKeys,function(hashKey){ var re = new RegExp('&'+hashKey+'(=[^&]*)?|^'+hashKey+'(=[^&]*)?&?'); curHash = curHash.replace(re,''); }); @@ -400,6 +672,7 @@ interceptorService.factory('ExpiredInterceptor', ['Storage', '$rootScope', funct var directives = angular.module('oauth.directive', []); directives.directive('oauth', [ + 'IdToken', 'AccessToken', 'Endpoint', 'Profile', @@ -409,7 +682,7 @@ directives.directive('oauth', [ '$compile', '$http', '$templateCache', - function(AccessToken, Endpoint, Profile, Storage, $location, $rootScope, $compile, $http, $templateCache) { + function(IdToken, AccessToken, Endpoint, Profile, Storage, $location, $rootScope, $compile, $http, $templateCache) { var definition = { restrict: 'AE', @@ -426,7 +699,11 @@ directives.directive('oauth', [ authorizePath: '@', // (optional) authorization url state: '@', // (optional) An arbitrary unique string created by your app to guard against Cross-site Request Forgery storage: '@', // (optional) Store token in 'sessionStorage' or 'localStorage', defaults to 'sessionStorage' - nonce: '@' // (optional) Send nonce on auth request + nonce: '@', // (optional) Send nonce on auth request + // OpenID Connect extras, more details in id-token.js: + issuer: '@', // (optional for OpenID Connect) issuer of the id_token, should match the 'iss' claim in id_token payload + subject: '@', // (optional for OpenID Connect) subject of the id_token, should match the 'sub' claim in id_token payload + pubKey: '@' // (optional for OpenID Connect) the public key(RSA public key or X509 certificate in PEM format) to verify the signature } }; @@ -442,6 +719,7 @@ directives.directive('oauth', [ Storage.use(scope.storage);// set storage compile(); // compiles the desired layout Endpoint.set(scope); // sets the oauth authorization url + IdToken.set(scope); AccessToken.set(scope); // sets the access token object (if existing, from fragment or session) initProfile(scope); // gets the profile resource (if existing the access token) initView(); // sets the view (logged in or out) diff --git a/karma.conf.js b/karma.conf.js index b66d4e9..5495d37 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -16,6 +16,7 @@ module.exports = function(config) { 'app/bower_components/angular-mocks/angular-mocks.js', 'app/bower_components/ngstorage/ngStorage.js', 'app/bower_components/timecop/timecop-0.1.1.js', + 'app/bower_components/jsrsasign/jsrsasign-latest-all-min.js', 'app/scripts/*.js', 'app/scripts/**/*.js', 'app/views/**/*.html', diff --git a/test/spec/services/access-token.js b/test/spec/services/access-token.js index 5439f54..4a80d34 100644 --- a/test/spec/services/access-token.js +++ b/test/spec/services/access-token.js @@ -2,11 +2,13 @@ describe('AccessToken', function() { - var result, $location, Storage, AccessToken, date; + var result, $location, Storage, IdToken, AccessToken, date; var fragment = 'access_token=token&token_type=bearer&expires_in=7200&state=/path&extra=stuff'; var fragmentForever = 'access_token=token&token_type=bearer&state=/path&extra=stuff'; var fragmentImmediate = 'access_token=token&token_type=bearer&expires_in=0&state=/path&extra=stuff'; + var fragmentWithIdToken = 'access_token=token&token_type=bearer'+ + '&id_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ&expires_in=3600'; var denied = 'error=access_denied&error_description=error'; var expires_at = '2014-08-17T17:38:37.584Z'; var token = { access_token: 'token', token_type: 'bearer', expires_in: 7200, state: '/path', expires_at: expires_at }; @@ -15,6 +17,7 @@ describe('AccessToken', function() { beforeEach(inject(function($injector) { $location = $injector.get('$location'); })); beforeEach(inject(function($injector) { Storage = $injector.get('Storage'); })); + beforeEach(inject(function($injector) { IdToken = $injector.get('IdToken'); })); beforeEach(inject(function($injector) { AccessToken = $injector.get('AccessToken'); })); @@ -135,6 +138,71 @@ describe('AccessToken', function() { expect(result.error).toBe('access_denied'); }); }); + + describe('with id token and access token in the fragment', function() { + beforeEach(function() { + $location.hash(fragmentWithIdToken); + }); + + describe('when all validation passes', function() { + beforeEach(function() { + //specific validation mechanism are tested in id-token spec + spyOn(IdToken, 'validateIdToken').and.returnValue(true); + spyOn(IdToken, 'validateAccessToken').and.returnValue(true); + result = AccessToken.set(); + }); + + it('sets the access token', function() { + expect(result.access_token).toEqual('token'); + }); + + it('sets the id token', function() { + expect(result.id_token).toEqual('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ'); + }); + + it('populates id token claims', function() { + expect(result.id_token_claims).toBeDefined(); + expect(result.id_token_claims.name).toEqual('John Doe'); + }); + }); + + describe('when id token validation fails', function(){ + beforeEach(function() { + //specific validation mechanism are tested in id-token spec + spyOn(IdToken, 'validateIdToken').and.returnValue(false); + spyOn(IdToken, 'validateAccessToken').and.returnValue(true); + result = AccessToken.set(); + }); + + it('erases access token and id token', function() { + expect(result.access_token).toEqual(null); + expect(result.id_token).toEqual(null); + }); + + it('sets the error', function() { + expect(result.error).toEqual('Failed to validate token:'); + }) + }); + + describe('when access token validation fails', function(){ + beforeEach(function() { + //specific validation mechanism are tested in id-token spec + spyOn(IdToken, 'validateIdToken').and.returnValue(true); + spyOn(IdToken, 'validateAccessToken').and.returnValue(false); + result = AccessToken.set(); + }); + + it('erases access token and id token', function() { + expect(result.access_token).toEqual(null); + expect(result.id_token).toEqual(null); + }); + + it('sets the error', function() { + expect(result.error).toEqual('Failed to validate token:'); + }) + }); + + }) }); diff --git a/test/spec/services/id-token.js b/test/spec/services/id-token.js new file mode 100644 index 0000000..b3e7643 --- /dev/null +++ b/test/spec/services/id-token.js @@ -0,0 +1,227 @@ +describe('IdToken', function() { + + var Storage, IdToken; + + var publicKeyString; + var validIdToken, invalidIdToken; + var validAccessToken; + + beforeEach(module('oauth')); + + beforeEach(inject(function ($injector) { + Storage = $injector.get('Storage'); + })); + beforeEach(inject(function ($injector) { + IdToken = $injector.get('IdToken'); + })); + + beforeEach(function () { + /** + * http://kjur.github.io/jsjws/tool_jwt.html generated sample id_token, signed by default private key + * The public key is shown as below + */ + publicKeyString = + "-----BEGIN PUBLIC KEY-----\n" + + "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA33TqqLR3eeUmDtHS89qF\n" + + "3p4MP7Wfqt2Zjj3lZjLjjCGDvwr9cJNlNDiuKboODgUiT4ZdPWbOiMAfDcDzlOxA\n" + + "04DDnEFGAf+kDQiNSe2ZtqC7bnIc8+KSG/qOGQIVaay4Ucr6ovDkykO5Hxn7OU7s\n" + + "Jp9TP9H0JH8zMQA6YzijYH9LsupTerrY3U6zyihVEDXXOv08vBHk50BMFJbE9iwF\n" + + "wnxCsU5+UZUZYw87Uu0n4LPFS9BT8tUIvAfnRXIEWCha3KbFWmdZQZlyrFw0buUE\n" + + "f0YN3/Q0auBkdbDR/ES2PbgKTJdkjc/rEeM0TxvOUf7HuUNOhrtAVEN1D5uuxE1W\n" + + "SwIDAQAB" + + "-----END PUBLIC KEY-----\n"; + }); + + describe('validate an id_token with both signature and claims', function() { + beforeEach(function () { + /* + Valid token with RS256, expires at 20251231235959Z UTC + https://jwt.io has a debugger that can help view the id_token's header and payload + + e.g. The header of following token is { "alg": "RS256", "typ": "JWT" } + The body of the following token is: + { + "iss": "oidc", + "sub": "oauth-ng-client", + "nbf": 1449267385, + "exp": 1767225599, + "iat": 1449267385, + "jti": "id123456", + "typ": "https://example.com/register" + } + */ + validIdToken = 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9' + + '.eyJpc3MiOiJvaWRjIiwic3ViIjoib2F1dGgtbmctY2xpZW50IiwibmJmIjoxNDQ5MjY3Mzg1LCJleHAiOjE3NjcyMjU1OTksImlhdCI6MTQ0OTI2NzM4NSwianRpIjoiaWQxMjM0NTYiLCJ0eXAiOiJodHRwczovL2V4YW1wbGUuY29tL3JlZ2lzdGVyIn0' + + '.MXBbWkr1Sf6KRn11IgEXyVg5g5VVUOSyLhTglgL8fI13aGf6SquVy0ZNn7ajTym5a_fJHPWLlgpvo-v98xuMBC9cLH_NN3ocrZAQkkW19G4AVY-LsOURp0t9JzVEb-pEe8Zps8O7Mumj0qSlr-4Dnyb3UMqdwZTcSgUTrbdyf6Qa7KHA0myANLDs2T8ctlSEptgVHPj8Zy9tk9UUlDZgsU4KoEpanDt7c1GzQJu7KEI3iJYlVEwDgMqu0EWn64aaP-w1OKZAyHbJWdMwun7i9edLonQ37M67Mb8ox6-cx8fxS3s3h6b3jRS5L0RACFVtB9lF4f_0yPVBwcTBhzYBOg'; + + IdToken.set({ + issuer: 'oidc', + clientId: 'oauth-ng-client', + pubKey: publicKeyString + }); + }); + + it('with success', function () { + expect(IdToken.validateIdToken(validIdToken)).toEqual(true); + }); + + }); + + describe('verify id_token signature with algorithm', function () { + + describe('RS256', function () { + + beforeEach(function () { + + //Valid token with RS256, expires at 20251231235959Z UTC + validIdToken = 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9' + + '.eyJpc3MiOiJvaWRjLXJzLTI1NiIsInN1YiI6Im9hdXRoLW5nLWNsaWVudCIsIm5iZiI6MTQ0OTQ0OTE0NCwiZXhwIjoxNzY3MjI1NTk5LCJpYXQiOjE0NDk0NDkxNDQsImp0aSI6ImlkMTIzNDU2IiwidHlwIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9yZWdpc3RlciJ9' + + '.lpxeRY_IqsvgWLj6H2ghre8dBBtsSF-bnjWHtVvhQIuerztMQOX20CCqtVFGScIZcI4gHxtEZGauF-sX3zwaLuqPtzORjaBiH0vV6C-3ZyqZrCU_n-TozKAwpSYyyHQpJ-xKdGRaOdd7_4vDtaFBWyHLXp1hbYvMftkPCvGjO25GppGQ7MjxCnd7IAPn0obXx2lZr1q0hHT7532O5dlmsPHTyrTvrSupTOVH3CZe3ZghM6R_mlagyfRh1Pf2cdRQkXJ0gEHf4GYpBbz-E3YfCyxcvQRPzfKnpLGH16M1_jM0mc3z5zVsegi62NNr79B8hExG5OtXfDMvws4LDfps2A'; + + IdToken.set({ + issuer: 'oidc-rs-256', + clientId: 'oauth-ng-client', + pubKey: publicKeyString + }); + }); + + it('validate token successfully', function () { + expect(IdToken.verifyIdTokenSig(validIdToken)).toEqual(true); + }); + + }); + + describe('RS384', function () { + + beforeEach(function() { + //Valid token with RS384, expires at 20251231235959Z UTC + validIdToken = 'eyJhbGciOiJSUzM4NCIsInR5cCI6IkpXVCJ9' + + '.eyJpc3MiOiJvaWRjLXJzLTM4NCIsInN1YiI6Im9hdXRoLW5nLWNsaWVudCIsIm5iZiI6MTQ0OTQ0NTM3NSwiZXhwIjoxNzY3MjI1NTk5LCJpYXQiOjE0NDk0NDUzNzUsImp0aSI6ImlkMTIzNDU2IiwidHlwIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9yZWdpc3RlciJ9' + + '.cDSfORFIZ28nUgvbdJ0wwe-JtZOy5J40--xYfJBPbwRJz02EscFlRD-xNqRB9eEsVVK4shW9AtF4yfp3Sa_jcjziGlQNwpRzFhnCqrADupMNNhK-z1SmuxgG_zfP7plXVAhg1IJ671w43I2PmXQw5wAKpAMwun4J-mxHP7ZV6__z_hxv4QclONHrk23_ebHJXi8W4q7B7n-amQQZ-kKQf8OblZIX9kAF58WIhyA5ZNqXGZ_hmcDKUVlBpgiurpD8u429NwrlauowHCQI_zMKlaEzJvH5qNhXLNbFgLmhrQFYo_VW48ZjHygmAkuuKt0jioR0dUeYirTGq-xEBcOpnw'; + + IdToken.set({ + issuer: 'oidc-rs-384', + clientId: 'oauth-ng-client', + pubKey: publicKeyString + }); + + }); + + it('validate token successfully', function () { + expect(IdToken.verifyIdTokenSig(validIdToken)).toEqual(true); + }); + + }); + + describe('RS512', function () { + + beforeEach(function() { + //Valid token with RS512, expires at 20251231235959Z UTC + validIdToken = 'eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9' + + '.eyJpc3MiOiJvaWRjLXJzLTUxMiIsInN1YiI6Im9hdXRoLW5nLWNsaWVudCIsIm5iZiI6MTQ0OTQ0NzQyMiwiZXhwIjoxNzY3MjI1NTk5LCJpYXQiOjE0NDk0NDc0MjIsImp0aSI6ImlkMTIzNDU2IiwidHlwIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9yZWdpc3RlciJ9' + + '.BAqggV91_YwQmnKty-qHzi61WgUpLue7gFWDe72VzHzcQhYu1STLduBxtr3qtsl6XzVDL6N0AmYfpFwMQQ8dRMaLLsnx6wg7Mi9nqCkDTL1Px5biL9AM4C3S32N6iJ4nFyJgUiFJ4RWG9f-78k4PG51xvSCkA-2TbODU1KsXRnc3o9SrQKw8pWnjmxNIfDtfzkxEdBlePWuknZGeaJBlR4hBRrxH1GnNDVW3aeuLJl4y1IOIbUxsnNW8HgAm6KpoCVAbPN7YzQPfDEIQgaNSS_i7Nkuq9Rno_6ivfqxs3QCiEqHJkAh8W2J3N8iPpRrCW03oQp2sGvmRTxxvxuxZbw' + + IdToken.set({ + issuer: 'oidc-rs-512', + clientId: 'oauth-ng-client', + pubKey: publicKeyString + }); + + }); + + it('validate token successfully', function () { + expect(IdToken.verifyIdTokenSig(validIdToken)).toEqual(true); + }); + + }); + + }); + + + describe('validate access_token with id_token header information', function () { + + beforeEach(function() { + /* + Sample id_token and access_token pair (corresponds to response_type = 'id_token token' + Get more examples at google playground: https://developers.google.com/oauthplayground/ + */ + validIdToken = 'eyJhbGciOiJSUzI1NiIsImtpZCI6IjJhODc0MjBlY2YxNGU5MzRmOWY5MDRhMDE0NzY4MTMyMDNiMzk5NGIifQ' + + '.eyJpc3MiOiJhY2NvdW50cy5nb29nbGUuY29tIiwic3ViIjoiMTEwMTY5NDg0NDc0Mzg2Mjc2MzM0IiwiYXpwIjoiNDA3NDA4NzE4MTkyLmFwcHMuZ29vZ2xldXNlcmNvbnRlbnQuY29tIiwiYXRfaGFzaCI6ImFVQWtKRy11Nng0UlRXdUlMV3ktQ0EiLCJhdWQiOiI0MDc0MDg3MTgxOTIuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJpYXQiOjE0MzIwODI4NzgsImV4cCI6MTQzMjA4NjQ3OH0' + + '.xSwhf4KvEztFFhVj4YdgKFOC8aPEoLAAZcXDWIh6YBXpfjzfnwYhaQgsmCofzOl53yirpbj5h7Om5570yzlUziP5TYNIqrA3Nyaj60-ZyXY2JMIBWYYMr3SRyhXdW0Dp71tZ5IaxMFlS8fc0MhSx55ZNrCV-3qmkTLeTTY1_4Jc'; + validAccessToken = 'ya29.eQETFbFOkAs8nWHcmYXKwEi0Zz46NfsrUU_KuQLOLTwWS40y6Fb99aVzEXC0U14m61lcPMIr1hEIBA'; + }); + + it('should succeed', function() { + expect(IdToken.validateAccessToken(validIdToken, validAccessToken)).toEqual(true); + }) + }); + + + describe('detect false id_token with', function () { + + describe('wrong issuer', function () { + + beforeEach(function () { + //Id token with issuer as 'oidc-foo' + invalidIdToken = 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9' + + '.eyJpc3MiOiJvaWRjLWZvbyIsInN1YiI6Im9hdXRoLW5nLWNsaWVudCIsIm5iZiI6MTQ0OTQ0OTYyNCwiZXhwIjoxNzY3MjI1NTk5LCJpYXQiOjE0NDk0NDk2MjQsImp0aSI6ImlkMTIzNDU2IiwidHlwIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9yZWdpc3RlciJ9' + + '.UCDvdOtXzXeDReumQsj-WXEgUIs_PyTdVt94n52hZT9q3Rfs4jFkx64n2tGa0SAzAwkQz8UhBn9omYg7c_A9Q8eYwbOLzSq8QUcH6adXME80c7ychmHsy4T8wXRhKExbSThs37Rgq38Z6mkodqYxxdGJw4xoiR3yPij2bXwT6Knes6nXEWYnhPosiLxOhzIIH7-LRPRFVd3aad0cm9TRkNzkEyZ4j2QPtNsKur80sJ0qrEFp-unjoyg59GMNF8yatt8d1hgNgnWIMSuzwRq4U4Da2Q7QMKadhArqNY1mDZJl3duS8No57RMPYipq2y8DVEqKzE2ie-jNs1fmB67hqQ'; + + IdToken.set({ + issuer: 'oidc', + clientId: 'oauth-ng-client', + pubKey: publicKeyString + }); + }); + + it('should throw exception', function () { + expect(function(){ IdToken.verifyIdTokenInfo(invalidIdToken) }).toThrowError(/Invalid issuer/); + }); + + }); + + describe('future issue time', function () { + + beforeEach(function () { + //Id token issued in the future (20251231235959Z UTC) + invalidIdToken = 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9' + + '.eyJpc3MiOiJvaWRjLWZvbyIsInN1YiI6Im9hdXRoLW5nLWNsaWVudCIsIm5iZiI6MTQ0OTQ1MDk5NCwiZXhwIjoxNzY3MjI1NTk5LCJpYXQiOjE3NjcyMjU1OTksImp0aSI6ImlkMTIzNDU2IiwidHlwIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9yZWdpc3RlciJ9' + + '.d7NrttJitzksr_wJzfvNQJhKTUa2Lp0YIl3B-FNUROMZ4nEGqW385ZwcIZlv1A2BlwbiozZBZ7rpiHHb3yAyfUToL8JD8hzfVurboc63Vp3qpHEMNzLIuWD4AUcYeuBIGz_gIT2sNeltjqJTPFUNm5FPRIs4O-a5b-13rosxI5UhQ7m6MLCUJ_U7w5Jxl5Dei2MUM3dF9ugI5UC17YFsAqWeAnddT2m9TPQGvTS8G42iuEOKxLIBkqE9SCRhcpRy66DWKNi8yyroLMIM9UOiyh2ODrI2sBn1TVa9b6-XkDGwDZdlbc2AWiGLFD2KeoBFYKV03aHhoWL2J9UFs08O8Q'; + + IdToken.set({ + issuer: 'oidc', + clientId: 'oauth-ng-client', + pubKey: publicKeyString + }); + }); + + it('should throw exception', function () { + expect(function(){ IdToken.verifyIdTokenInfo(invalidIdToken) }).toThrowError(/issued time is later than current time/); + }); + + }); + + describe('expired token', function () { + + beforeEach(function () { + //Expired id token + invalidIdToken = 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9' + + '.eyJpc3MiOiJvaWRjLWZvbyIsInN1YiI6Im9hdXRoLW5nLWNsaWVudCIsIm5iZiI6MTQ0OTQ1MTIxMywiZXhwIjoxNDQ5NDUxMjEzLCJpYXQiOjE0NDk0NTEyMTMsImp0aSI6ImlkMTIzNDU2IiwidHlwIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9yZWdpc3RlciJ9' + + '.PAUYJX7v7xDRAVaIUNME7VxK5j2GFDE19bMaa6I-jFa5Lw5f0WlXBOa2WXYqynPymtC0-UMTdUeZ4V_mA07ubTNKyyyqNelr-kpGvM3NzIZpHokEibQVF3JeK1pH_pqnC_MYHePZMiejCkSSPMvC1_lvPOMiMfEhqWvqh58aw7v8q9a9OQYsTlQU_q_rq4mTvDkv9gjU8qKqFInLKIU1TZn4tnslFroW70kvOndz8MHOmXCyQOLbyDW9NHgJXCCCxXwEmo00LjxDHQOSC5uMK9mkix513AqZ8Gaj2QB7-4m6rCK23TiffGgIIlLzPq2RPSBbHGv-K5S_lR_Qh8STGA'; + + IdToken.set({ + issuer: 'oidc', + clientId: 'oauth-ng-client', + pubKey: publicKeyString + }); + }); + + it('should throw exception', function () { + expect(function(){ IdToken.verifyIdTokenInfo(invalidIdToken) }).toThrowError(/ID Token expired/); + }); + + }); + }); + + +}); From 31b74676ee2efc20b871f6b0342c81b5e8445164 Mon Sep 17 00:00:00 2001 From: Massimiliano Sartoretto Date: Wed, 16 Dec 2015 17:10:11 +0100 Subject: [PATCH 081/107] Bump to v0.4.5 --- CHANGELOG.md | 4 ++++ bower.json | 2 +- package.json | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 02b0c2f..accc2f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 0.4.5 (Dec 16, 2015) + +* Add OpenID support + ## 0.4.4 (Nov 19, 2015) * Webpack Fix diff --git a/bower.json b/bower.json index 01c4748..fa54049 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "oauth-ng", - "version": "0.4.4", + "version": "0.4.5", "main": [ "dist/oauth-ng.js", "dist/views/templates/default" diff --git a/package.json b/package.json index a9fe815..601a423 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "oauth-ng", - "version": "0.4.4", + "version": "0.4.5", "author": "Andrea Reginato ", "description": "AngularJS Directive for OAuth 2.0", "repository": { From 8c84707b7778d5119e8c8118ab7610e6825758fa Mon Sep 17 00:00:00 2001 From: Wesley Lancel Date: Sat, 19 Dec 2015 18:45:10 +0100 Subject: [PATCH 082/107] Fixes #80 Make sure the interval isn't set when the expiry date is bigger than the max value for setInterval. --- app/scripts/services/access-token.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/scripts/services/access-token.js b/app/scripts/services/access-token.js index 97ec0f7..38bb58c 100644 --- a/app/scripts/services/access-token.js +++ b/app/scripts/services/access-token.js @@ -162,7 +162,7 @@ accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$location', return; } var time = (new Date(service.token.expires_at))-(new Date()); - if(time && time > 0){ + if(time && time > 0 && time <= 2147483647){ $interval(function(){ $rootScope.$broadcast('oauth:expired', service.token); }, time, 1); From 02135b7fdb9293a7fa53c2787c067b4a219ee2ef Mon Sep 17 00:00:00 2001 From: Andrea Reginato Date: Thu, 24 Dec 2015 09:47:02 +0100 Subject: [PATCH 083/107] Moved repo into angularjs-oauth organisation --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index c3cfe33..39a29e9 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ AngularJS directive for the [OAuth 2.0 Implicit Flow](http://tools.ietf.org/html ## Documentation -[![oauth-ng](http://i.imgur.com/C0xCJcr.png)](https://andreareginato.github.com/oauth-ng) +[![oauth-ng](http://i.imgur.com/C0xCJcr.png)](https://angularjs-oauth.github.com/oauth-ng) ## Contributing @@ -54,14 +54,14 @@ Follow [github](https://github.com/styleguide/) guidelines. ### Feedback -Use the [issue tracker](http://github.com/andreareginato/oauth-ng/issues) for bugs. +Use the [issue tracker](http://github.com/angularjs-oauth/oauth-ng/issues) for bugs. [Mail](mailto:andrea.reginato@gmail.com) or [Tweet](http://twitter.com/andreareginato) us for any idea that can improve the project. ### Links -* [GIT Repository](http://github.com/andreareginato/oauth-ng) -* [Website](https://andreareginato.github.com/oauth-ng) +* [GIT Repository](http://github.com/angularjs-oauth/oauth-ng) +* [Website](https://angularjs-oauth.github.com/oauth-ng) ## Authors @@ -74,12 +74,12 @@ Project created and released as open-source thanks to [Lelylan](http://lelylan.c ## Contributors -Special thanks to all [contributors](https://github.com/andreareginato/oauth-ng/contributors) +Special thanks to all [contributors](https://github.com/angularjs-oauth/oauth-ng/contributors) for submitting patches. ## Changelog -See [CHANGELOG](https://github.com/andreareginato/oauth-ng/blob/master/CHANGELOG.md) +See [CHANGELOG](https://github.com/angularjs-oauth/oauth-ng/blob/master/CHANGELOG.md) ## TODO :white_medium_square: [OAuth 2.0 Authorization code Flow](http://tools.ietf.org/html/rfc6749#section-1.3.1) @@ -87,4 +87,4 @@ See [CHANGELOG](https://github.com/andreareginato/oauth-ng/blob/master/CHANGELOG ## Copyright Copyright (c) 2014 [Lelylan](http://lelylan.com). -See [LICENSE](https://github.com/andreareginato/oauth-ng/blob/master/LICENSE.md) for details. +See [LICENSE](https://github.com/angularjs-oauth/oauth-ng/blob/master/LICENSE.md) for details. From 043faef373f7ea65207f7cce2e1bdce1c6290b5f Mon Sep 17 00:00:00 2001 From: Andrea Reginato Date: Thu, 24 Dec 2015 09:48:30 +0100 Subject: [PATCH 084/107] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 39a29e9..19470d8 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ AngularJS directive for the [OAuth 2.0 Implicit Flow](http://tools.ietf.org/html ## Documentation -[![oauth-ng](http://i.imgur.com/C0xCJcr.png)](https://angularjs-oauth.github.com/oauth-ng) +[![oauth-ng](http://i.imgur.com/C0xCJcr.png)](https://angularjs-oauth.github.io/oauth-ng) ## Contributing @@ -61,7 +61,7 @@ that can improve the project. ### Links * [GIT Repository](http://github.com/angularjs-oauth/oauth-ng) -* [Website](https://angularjs-oauth.github.com/oauth-ng) +* [Website](https://angularjs-oauth.github.io/oauth-ng) ## Authors From 4375815cb0f3b1a1e6221072af803ffa0d5d2f1e Mon Sep 17 00:00:00 2001 From: Yuan Yao Date: Wed, 6 Jan 2016 21:38:50 -0800 Subject: [PATCH 085/107] Fix JWK format support of OIDC public key --- app/scripts/services/id-token.js | 8 ++++---- dist/oauth-ng.js | 12 ++++++------ test/spec/services/id-token.js | 21 +++++++++++++++++++++ 3 files changed, 31 insertions(+), 10 deletions(-) diff --git a/app/scripts/services/id-token.js b/app/scripts/services/id-token.js index a0fe74e..8d3107d 100644 --- a/app/scripts/services/id-token.js +++ b/app/scripts/services/id-token.js @@ -136,15 +136,15 @@ idTokenService.factory('IdToken', ['Storage', function(Storage) { //Take the JWK if it comes with the id_token matchedPubKey = header.jwk; if (matchedPubKey.kid && header.kid && matchedPubKey.kid !== header.kid) { - throw new OidcException('Json Wek Key ID not match'); + throw new OidcException('Json Web Key ID not match'); } /* TODO: Support for "jku" (JWK Set URL), "x5u" (X.509 URL), "x5c" (X.509 Certificate Chain) parameter to get key per http://tools.ietf.org/html/draft-ietf-jose-json-web-signature-26#page-9 */ - } else { - //Use configured public key - matchedPubKey = this.pubKey; + } else { //Use configured public key + var jwk = getJsonObject(this.pubKey); + matchedPubKey = jwk ? jwk : this.pubKey; //JWK or PEM } if(!matchedPubKey) { diff --git a/dist/oauth-ng.js b/dist/oauth-ng.js index f6a4bdb..41b1399 100644 --- a/dist/oauth-ng.js +++ b/dist/oauth-ng.js @@ -1,4 +1,4 @@ -/* oauth-ng - v0.4.4 - 2015-12-12 */ +/* oauth-ng - v0.4.5 - 2016-01-06 */ 'use strict'; @@ -156,15 +156,15 @@ idTokenService.factory('IdToken', ['Storage', function(Storage) { //Take the JWK if it comes with the id_token matchedPubKey = header.jwk; if (matchedPubKey.kid && header.kid && matchedPubKey.kid !== header.kid) { - throw new OidcException('Json Wek Key ID not match'); + throw new OidcException('Json Web Key ID not match'); } /* TODO: Support for "jku" (JWK Set URL), "x5u" (X.509 URL), "x5c" (X.509 Certificate Chain) parameter to get key per http://tools.ietf.org/html/draft-ietf-jose-json-web-signature-26#page-9 */ - } else { - //Use configured public key - matchedPubKey = this.pubKey; + } else { //Use configured public key + var jwk = getJsonObject(this.pubKey); + matchedPubKey = jwk ? jwk : this.pubKey; //JWK or PEM } if(!matchedPubKey) { @@ -443,7 +443,7 @@ accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$location', return; } var time = (new Date(service.token.expires_at))-(new Date()); - if(time && time > 0){ + if(time && time > 0 && time <= 2147483647){ $interval(function(){ $rootScope.$broadcast('oauth:expired', service.token); }, time, 1); diff --git a/test/spec/services/id-token.js b/test/spec/services/id-token.js index b3e7643..11d72c3 100644 --- a/test/spec/services/id-token.js +++ b/test/spec/services/id-token.js @@ -137,6 +137,27 @@ describe('IdToken', function() { }); + describe('verify id_token using JWK(Json Web Key) format key', function() { + beforeEach(function () { + //The same public key, but using a JWK format + var jwkString = '{"kty":"RSA","n":"33TqqLR3eeUmDtHS89qF3p4MP7Wfqt2Zjj3lZjLjjCGDvwr9cJNlNDiuKboODgUiT4ZdPWbOiMAfDcDzlOxA04DDnEFGAf-kDQiNSe2ZtqC7bnIc8-KSG_qOGQIVaay4Ucr6ovDkykO5Hxn7OU7sJp9TP9H0JH8zMQA6YzijYH9LsupTerrY3U6zyihVEDXXOv08vBHk50BMFJbE9iwFwnxCsU5-UZUZYw87Uu0n4LPFS9BT8tUIvAfnRXIEWCha3KbFWmdZQZlyrFw0buUEf0YN3_Q0auBkdbDR_ES2PbgKTJdkjc_rEeM0TxvOUf7HuUNOhrtAVEN1D5uuxE1WSw","e":"AQAB"}'; + //Valid token with RS256, expires at 20251231235959Z UTC + validIdToken = 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9' + + '.eyJpc3MiOiJvaWRjLXJzLTI1NiIsInN1YiI6Im9hdXRoLW5nLWNsaWVudCIsIm5iZiI6MTQ0OTQ0OTE0NCwiZXhwIjoxNzY3MjI1NTk5LCJpYXQiOjE0NDk0NDkxNDQsImp0aSI6ImlkMTIzNDU2IiwidHlwIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9yZWdpc3RlciJ9' + + '.lpxeRY_IqsvgWLj6H2ghre8dBBtsSF-bnjWHtVvhQIuerztMQOX20CCqtVFGScIZcI4gHxtEZGauF-sX3zwaLuqPtzORjaBiH0vV6C-3ZyqZrCU_n-TozKAwpSYyyHQpJ-xKdGRaOdd7_4vDtaFBWyHLXp1hbYvMftkPCvGjO25GppGQ7MjxCnd7IAPn0obXx2lZr1q0hHT7532O5dlmsPHTyrTvrSupTOVH3CZe3ZghM6R_mlagyfRh1Pf2cdRQkXJ0gEHf4GYpBbz-E3YfCyxcvQRPzfKnpLGH16M1_jM0mc3z5zVsegi62NNr79B8hExG5OtXfDMvws4LDfps2A'; + + IdToken.set({ + issuer: 'oidc-rs-256', + clientId: 'oauth-ng-client', + pubKey: jwkString + }); + }); + + it('validate token successfully', function () { + expect(IdToken.verifyIdTokenSig(validIdToken)).toEqual(true); + }); + + }); describe('validate access_token with id_token header information', function () { From d22ac89c9acc0d9b3b3e368d0d42e6a158c8f629 Mon Sep 17 00:00:00 2001 From: Massimiliano Sartoretto Date: Thu, 7 Jan 2016 12:51:09 +0100 Subject: [PATCH 086/107] Bump to v0.4.6 --- CHANGELOG.md | 6 +++++- README.md | 2 +- bower.json | 2 +- package.json | 2 +- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index accc2f1..cc9f4d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 0.4.6 (Jan 07, 2016) + +* Fix JWK format support of OIDC public key + ## 0.4.5 (Dec 16, 2015) * Add OpenID support @@ -15,7 +19,7 @@ ## 0.4.2 (Jun 19, 2015) * Make the code more JSHint friendly -* Fix expiry definition in the access token service +* Fix expiry definition in the access token service ## 0.4.1 (Jun 11, 2015) diff --git a/README.md b/README.md index 19470d8..bd45c18 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ Please also update `gh-pages` branch with documentation when applicable. ### OAuth 2.0 supported grant types -We support both [OAuth 2.0 Authorization code Flow](http://tools.ietf.org/html/rfc6749#section-1.3.1) +We support both [OAuth 2.0 Authorization code Flow](http://tools.ietf.org/html/rfc6749#section-1.3.1) and the [OAuth 2.0 Implicit Flow](http://tools.ietf.org/html/rfc6749#section-1.3.2). #### Authorization code flow diff --git a/bower.json b/bower.json index fa54049..9f290d0 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "oauth-ng", - "version": "0.4.5", + "version": "0.4.6", "main": [ "dist/oauth-ng.js", "dist/views/templates/default" diff --git a/package.json b/package.json index 601a423..4f6b4cc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "oauth-ng", - "version": "0.4.5", + "version": "0.4.6", "author": "Andrea Reginato ", "description": "AngularJS Directive for OAuth 2.0", "repository": { From d55dbb6b773716a5d4ff89f90759f13f420f1e9a Mon Sep 17 00:00:00 2001 From: bramski Date: Tue, 1 Dec 2015 13:17:16 -0800 Subject: [PATCH 087/107] Get the logoutURI. Check Validity of the token. Broadcast validity. --- app/scripts/directives/oauth.js | 51 +++++--- app/scripts/services/access-token.js | 19 ++- app/scripts/services/endpoint.js | 96 +++++++++++---- dist/oauth-ng.js | 168 ++++++++++++++++++++------- 4 files changed, 249 insertions(+), 85 deletions(-) diff --git a/app/scripts/directives/oauth.js b/app/scripts/directives/oauth.js index e4563cf..35e9ed5 100644 --- a/app/scripts/directives/oauth.js +++ b/app/scripts/directives/oauth.js @@ -34,7 +34,9 @@ directives.directive('oauth', [ // OpenID Connect extras, more details in id-token.js: issuer: '@', // (optional for OpenID Connect) issuer of the id_token, should match the 'iss' claim in id_token payload subject: '@', // (optional for OpenID Connect) subject of the id_token, should match the 'sub' claim in id_token payload - pubKey: '@' // (optional for OpenID Connect) the public key(RSA public key or X509 certificate in PEM format) to verify the signature + pubKey: '@', // (optional for OpenID Connect) the public key(RSA public key or X509 certificate in PEM format) to verify the signature + logoutPath: '@', // (optional) A url to go to at logout + sessionPath: '@' // (optional) A url to use to check the validity of the current token. } }; @@ -54,6 +56,7 @@ directives.directive('oauth', [ AccessToken.set(scope); // sets the access token object (if existing, from fragment or session) initProfile(scope); // gets the profile resource (if existing the access token) initView(); // sets the view (logged in or out) + checkValidity(); // ensure the validity of the current token }; var initAttributes = function() { @@ -84,34 +87,36 @@ directives.directive('oauth', [ } }; - var initView = function() { + var initView = function () { var token = AccessToken.get(); if (!token) { - return loggedOut(); // without access token it's logged out - } + return loggedOut(); + } // without access token it's logged out + if (AccessToken.expired()) { + return expired(); + } // with a token, but it's expired if (token.access_token) { - return authorized(); // if there is the access token we are done - } + return authorized(); + } // if there is the access token we are done if (token.error) { - return denied(); // if the request has been denied we fire the denied event - } + return denied(); + } // if the request has been denied we fire the denied event }; - scope.login = function() { + scope.login = function () { Endpoint.redirect(); }; - scope.logout = function() { + scope.logout = function () { AccessToken.destroy(scope); $rootScope.$broadcast('oauth:logout'); - loggedOut(); + Endpoint.logout(); + $rootScope.$broadcast('oauth:loggedOut'); + scope.show = 'logged-out'; }; - scope.$on('oauth:expired', function() { - AccessToken.destroy(scope); - scope.show = 'logged-out'; - }); + scope.$on('oauth:expired',expired); // user is authorized var authorized = function() { @@ -119,10 +124,9 @@ directives.directive('oauth', [ scope.show = 'logged-in'; }; - // set the oauth directive to the logged-out status - var loggedOut = function() { - $rootScope.$broadcast('oauth:loggedOut'); - scope.show = 'logged-out'; + var expired = function() { + $rootScope.$broadcast('oauth:expired'); + scope.logout(); }; // set the oauth directive to the denied status @@ -131,6 +135,15 @@ directives.directive('oauth', [ $rootScope.$broadcast('oauth:denied'); }; + var checkValidity = function() { + Endpoint.checkValidity().then(function() { + $rootScope.$broadcast('oauth:valid'); + }).catch(function(){ + $rootScope.$broadcast('oauth:invalid'); + scope.logout(); + }); + }; + // Updates the template at runtime scope.$on('oauth:template:update', function(event, template) { scope.template = template; diff --git a/app/scripts/services/access-token.js b/app/scripts/services/access-token.js index 38bb58c..6b4c0dd 100644 --- a/app/scripts/services/access-token.js +++ b/app/scripts/services/access-token.js @@ -14,6 +14,7 @@ accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$location', //Additional OpenID Connect key per http://openid.net/specs/openid-connect-core-1_0.html#ImplicitAuthResponse 'id_token' ]; + var expiresAtEvent = null; /** * Returns the access token. @@ -73,6 +74,14 @@ accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$location', } }; + /** + * updates the expiration of the token + */ + service.updateExpiry = function(newExpiresIn){ + this.token.expires_in = newExpiresIn; + setExpiresAt(); + }; + /* * * * * * * * * * * PRIVATE METHODS * * * * * * * * * * */ @@ -161,14 +170,22 @@ accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$location', if (typeof(service.token.expires_at) === 'undefined' || service.token.expires_at === null) { return; } + cancelExpiresAtEvent(); var time = (new Date(service.token.expires_at))-(new Date()); if(time && time > 0 && time <= 2147483647){ - $interval(function(){ + expiresAtEvent = $interval(function(){ $rootScope.$broadcast('oauth:expired', service.token); }, time, 1); } }; + var cancelExpiresAtEvent = function() { + if(expiresAtEvent) { + $timeout.cancel(expiresAtEvent); + expiresAtEvent = undefined; + } + }; + /** * Remove the oAuth2 pieces from the hash fragment */ diff --git a/app/scripts/services/endpoint.js b/app/scripts/services/endpoint.js index 883e744..fc5d84a 100644 --- a/app/scripts/services/endpoint.js +++ b/app/scripts/services/endpoint.js @@ -2,10 +2,30 @@ var endpointClient = angular.module('oauth.endpoint', []); -endpointClient.factory('Endpoint', function() { +endpointClient.factory('Endpoint', function($rootScope, AccessToken, $q, $http) { var service = {}; + var buildOauthUrl = function (path, params) { + var oAuthScope = (params.scope) ? params.scope : '', + state = (params.state) ? encodeURIComponent(params.state) : '', + authPathHasQuery = (params.authorizePath.indexOf('?') == -1) ? false : true, + appendChar = (authPathHasQuery) ? '&' : '?', //if authorizePath has ? already append OAuth2 params + nonceParam = (params.nonce) ? '&nonce=' + params.nonce : ''; + + return params.site + + path + + appendChar + 'response_type=token&' + + 'client_id=' + encodeURIComponent(params.clientId) + '&' + + 'redirect_uri=' + encodeURIComponent(params.redirectUri) + '&' + + 'scope=' + oAuthScope + '&' + + 'state=' + state + nonceParam; + }; + + var extendValidity = function (tokenInfo) { + AccessToken.updateExpiry(tokenInfo.expires); + }; + /* * Defines the authorization URL */ @@ -19,36 +39,68 @@ endpointClient.factory('Endpoint', function() { * Returns the authorization URL */ - service.get = function( overrides ) { + service.get = function(overrides) { var params = angular.extend( {}, service.config, overrides); - var oAuthScope = (params.scope) ? encodeURIComponent(params.scope) : '', - state = (params.state) ? encodeURIComponent(params.state) : '', - authPathHasQuery = (params.authorizePath.indexOf('?') === -1) ? false : true, - appendChar = (authPathHasQuery) ? '&' : '?', //if authorizePath has ? already append OAuth2 params - responseType = (params.responseType) ? encodeURIComponent(params.responseType) : ''; - - var url = params.site + - params.authorizePath + - appendChar + 'response_type=' + responseType + '&' + - 'client_id=' + encodeURIComponent(params.clientId) + '&' + - 'redirect_uri=' + encodeURIComponent(params.redirectUri) + '&' + - 'scope=' + oAuthScope + '&' + - 'state=' + state; - - if( params.nonce ) { - url = url + '&nonce=' + params.nonce; - } - return url; + return buildOauthUrl(params.authorizePath, params); }; /* * Redirects the app to the authorization URL */ - service.redirect = function( overrides ) { - var targetLocation = this.get( overrides ); + service.redirect = function(overrides) { + var targetLocation = this.get(overrides); + $rootScope.$broadcast('oauth:logging-in'); window.location.replace(targetLocation); }; + /* + * Alias for 'redirect' + */ + service.login = function() { + service.redirect(); + }; + + /* + * Check the validity of the token if a session path is available + */ + service.checkValidity = function() { + var params = service.config; + if( params.sessionPath ) { + var token = AccessToken.get().access_token; + if( !token ) { + return $q.reject("No token configured"); + } + var path = params.site + params.sessionPath + "?token=" + token; + return $http.get(path).then( function(httpResponse) { + var tokenInfo = httpResponse.data; + if(tokenInfo.valid) { + extendValidity(tokenInfo); + return true; + } else { + return $q.reject("Server replied: token is invalid."); + } + }); + } else { + return $q.reject("You must give a :session-path param in order to validate the token.") + } + }; + + /* + * Destroys the session, sends the user to the logout url if set. + * First broadcasts 'logging-out' and then 'logout' when finished. + */ + + service.logout = function() { + var params = service.config; + AccessToken.destroy(); + $rootScope.$broadcast('oauth:logging-out'); + if( params.logoutPath ) { + window.location.replace(buildOauthUrl(params.logOutPath, params)); + } else { + $rootScope.$broadcast('oauth:logout'); + } + }; + return service; }); diff --git a/dist/oauth-ng.js b/dist/oauth-ng.js index 41b1399..a55d454 100644 --- a/dist/oauth-ng.js +++ b/dist/oauth-ng.js @@ -1,4 +1,4 @@ -/* oauth-ng - v0.4.5 - 2016-01-06 */ +/* oauth-ng - v0.4.6 - 2016-02-09 */ 'use strict'; @@ -295,6 +295,7 @@ accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$location', //Additional OpenID Connect key per http://openid.net/specs/openid-connect-core-1_0.html#ImplicitAuthResponse 'id_token' ]; + var expiresAtEvent = null; /** * Returns the access token. @@ -354,6 +355,14 @@ accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$location', } }; + /** + * updates the expiration of the token + */ + service.updateExpiry = function(newExpiresIn){ + this.token.expires_in = newExpiresIn; + setExpiresAt(); + }; + /* * * * * * * * * * * PRIVATE METHODS * * * * * * * * * * */ @@ -442,14 +451,22 @@ accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$location', if (typeof(service.token.expires_at) === 'undefined' || service.token.expires_at === null) { return; } + cancelExpiresAtEvent(); var time = (new Date(service.token.expires_at))-(new Date()); if(time && time > 0 && time <= 2147483647){ - $interval(function(){ + expiresAtEvent = $interval(function(){ $rootScope.$broadcast('oauth:expired', service.token); }, time, 1); } }; + var cancelExpiresAtEvent = function() { + if(expiresAtEvent) { + $timeout.cancel(expiresAtEvent); + expiresAtEvent = undefined; + } + }; + /** * Remove the oAuth2 pieces from the hash fragment */ @@ -471,10 +488,30 @@ accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$location', var endpointClient = angular.module('oauth.endpoint', []); -endpointClient.factory('Endpoint', function() { +endpointClient.factory('Endpoint', function($rootScope, AccessToken, $q, $http) { var service = {}; + var buildOauthUrl = function (path, params) { + var oAuthScope = (params.scope) ? params.scope : '', + state = (params.state) ? encodeURIComponent(params.state) : '', + authPathHasQuery = (params.authorizePath.indexOf('?') == -1) ? false : true, + appendChar = (authPathHasQuery) ? '&' : '?', //if authorizePath has ? already append OAuth2 params + nonceParam = (params.nonce) ? '&nonce=' + params.nonce : ''; + + return params.site + + path + + appendChar + 'response_type=token&' + + 'client_id=' + encodeURIComponent(params.clientId) + '&' + + 'redirect_uri=' + encodeURIComponent(params.redirectUri) + '&' + + 'scope=' + oAuthScope + '&' + + 'state=' + state + nonceParam; + }; + + var extendValidity = function (tokenInfo) { + AccessToken.updateExpiry(tokenInfo.expires); + }; + /* * Defines the authorization URL */ @@ -488,37 +525,69 @@ endpointClient.factory('Endpoint', function() { * Returns the authorization URL */ - service.get = function( overrides ) { + service.get = function(overrides) { var params = angular.extend( {}, service.config, overrides); - var oAuthScope = (params.scope) ? encodeURIComponent(params.scope) : '', - state = (params.state) ? encodeURIComponent(params.state) : '', - authPathHasQuery = (params.authorizePath.indexOf('?') === -1) ? false : true, - appendChar = (authPathHasQuery) ? '&' : '?', //if authorizePath has ? already append OAuth2 params - responseType = (params.responseType) ? encodeURIComponent(params.responseType) : ''; - - var url = params.site + - params.authorizePath + - appendChar + 'response_type=' + responseType + '&' + - 'client_id=' + encodeURIComponent(params.clientId) + '&' + - 'redirect_uri=' + encodeURIComponent(params.redirectUri) + '&' + - 'scope=' + oAuthScope + '&' + - 'state=' + state; - - if( params.nonce ) { - url = url + '&nonce=' + params.nonce; - } - return url; + return buildOauthUrl(params.authorizePath, params); }; /* * Redirects the app to the authorization URL */ - service.redirect = function( overrides ) { - var targetLocation = this.get( overrides ); + service.redirect = function(overrides) { + var targetLocation = this.get(overrides); + $rootScope.$broadcast('oauth:logging-in'); window.location.replace(targetLocation); }; + /* + * Alias for 'redirect' + */ + service.login = function() { + service.redirect(); + }; + + /* + * Check the validity of the token if a session path is available + */ + service.checkValidity = function() { + var params = service.config; + if( params.sessionPath ) { + var token = AccessToken.get().access_token; + if( !token ) { + return $q.reject("No token configured"); + } + var path = params.site + params.sessionPath + "?token=" + token; + return $http.get(path).then( function(httpResponse) { + var tokenInfo = httpResponse.data; + if(tokenInfo.valid) { + extendValidity(tokenInfo); + return true; + } else { + return $q.reject("Server replied: token is invalid."); + } + }); + } else { + return $q.reject("You must give a :session-path param in order to validate the token.") + } + }; + + /* + * Destroys the session, sends the user to the logout url if set. + * First broadcasts 'logging-out' and then 'logout' when finished. + */ + + service.logout = function() { + var params = service.config; + AccessToken.destroy(); + $rootScope.$broadcast('oauth:logging-out'); + if( params.logoutPath ) { + window.location.replace(buildOauthUrl(params.logOutPath, params)); + } else { + $rootScope.$broadcast('oauth:logout'); + } + }; + return service; }); @@ -703,7 +772,9 @@ directives.directive('oauth', [ // OpenID Connect extras, more details in id-token.js: issuer: '@', // (optional for OpenID Connect) issuer of the id_token, should match the 'iss' claim in id_token payload subject: '@', // (optional for OpenID Connect) subject of the id_token, should match the 'sub' claim in id_token payload - pubKey: '@' // (optional for OpenID Connect) the public key(RSA public key or X509 certificate in PEM format) to verify the signature + pubKey: '@', // (optional for OpenID Connect) the public key(RSA public key or X509 certificate in PEM format) to verify the signature + logoutPath: '@', // (optional) A url to go to at logout + sessionPath: '@' // (optional) A url to use to check the validity of the current token. } }; @@ -723,6 +794,7 @@ directives.directive('oauth', [ AccessToken.set(scope); // sets the access token object (if existing, from fragment or session) initProfile(scope); // gets the profile resource (if existing the access token) initView(); // sets the view (logged in or out) + checkValidity(); // ensure the validity of the current token }; var initAttributes = function() { @@ -753,34 +825,36 @@ directives.directive('oauth', [ } }; - var initView = function() { + var initView = function () { var token = AccessToken.get(); if (!token) { - return loggedOut(); // without access token it's logged out - } + return loggedOut(); + } // without access token it's logged out + if (AccessToken.expired()) { + return expired(); + } // with a token, but it's expired if (token.access_token) { - return authorized(); // if there is the access token we are done - } + return authorized(); + } // if there is the access token we are done if (token.error) { - return denied(); // if the request has been denied we fire the denied event - } + return denied(); + } // if the request has been denied we fire the denied event }; - scope.login = function() { + scope.login = function () { Endpoint.redirect(); }; - scope.logout = function() { + scope.logout = function () { AccessToken.destroy(scope); $rootScope.$broadcast('oauth:logout'); - loggedOut(); + Endpoint.logout(); + $rootScope.$broadcast('oauth:loggedOut'); + scope.show = 'logged-out'; }; - scope.$on('oauth:expired', function() { - AccessToken.destroy(scope); - scope.show = 'logged-out'; - }); + scope.$on('oauth:expired',expired); // user is authorized var authorized = function() { @@ -788,10 +862,9 @@ directives.directive('oauth', [ scope.show = 'logged-in'; }; - // set the oauth directive to the logged-out status - var loggedOut = function() { - $rootScope.$broadcast('oauth:loggedOut'); - scope.show = 'logged-out'; + var expired = function() { + $rootScope.$broadcast('oauth:expired'); + scope.logout(); }; // set the oauth directive to the denied status @@ -800,6 +873,15 @@ directives.directive('oauth', [ $rootScope.$broadcast('oauth:denied'); }; + var checkValidity = function() { + Endpoint.checkValidity().then(function() { + $rootScope.$broadcast('oauth:valid'); + }).catch(function(){ + $rootScope.$broadcast('oauth:invalid'); + scope.logout(); + }); + }; + // Updates the template at runtime scope.$on('oauth:template:update', function(event, template) { scope.template = template; From 7a67c44cc2e1bfd55d30dcbd5f54dd8ae7adb81f Mon Sep 17 00:00:00 2001 From: bramski Date: Wed, 10 Feb 2016 12:31:48 -0800 Subject: [PATCH 088/107] Add tests. --- app/scripts/directives/oauth.js | 9 +- app/scripts/services/endpoint.js | 14 +-- bower.json | 3 + test/spec/directives/oauth.js | 190 ++++++++++++++++++----------- test/spec/services/access-token.js | 18 +++ 5 files changed, 148 insertions(+), 86 deletions(-) diff --git a/app/scripts/directives/oauth.js b/app/scripts/directives/oauth.js index 35e9ed5..0d11fee 100644 --- a/app/scripts/directives/oauth.js +++ b/app/scripts/directives/oauth.js @@ -91,7 +91,7 @@ directives.directive('oauth', [ var token = AccessToken.get(); if (!token) { - return loggedOut(); + return scope.logout(); } // without access token it's logged out if (AccessToken.expired()) { return expired(); @@ -109,8 +109,6 @@ directives.directive('oauth', [ }; scope.logout = function () { - AccessToken.destroy(scope); - $rootScope.$broadcast('oauth:logout'); Endpoint.logout(); $rootScope.$broadcast('oauth:loggedOut'); scope.show = 'logged-out'; @@ -138,9 +136,8 @@ directives.directive('oauth', [ var checkValidity = function() { Endpoint.checkValidity().then(function() { $rootScope.$broadcast('oauth:valid'); - }).catch(function(){ - $rootScope.$broadcast('oauth:invalid'); - scope.logout(); + }).catch(function(message){ + $rootScope.$broadcast('oauth:invalid', message); }); }; diff --git a/app/scripts/services/endpoint.js b/app/scripts/services/endpoint.js index fc5d84a..4e17f30 100644 --- a/app/scripts/services/endpoint.js +++ b/app/scripts/services/endpoint.js @@ -7,15 +7,16 @@ endpointClient.factory('Endpoint', function($rootScope, AccessToken, $q, $http) var service = {}; var buildOauthUrl = function (path, params) { - var oAuthScope = (params.scope) ? params.scope : '', + var oAuthScope = (params.scope) ? encodeURIComponent(params.scope) : '', state = (params.state) ? encodeURIComponent(params.state) : '', authPathHasQuery = (params.authorizePath.indexOf('?') == -1) ? false : true, appendChar = (authPathHasQuery) ? '&' : '?', //if authorizePath has ? already append OAuth2 params - nonceParam = (params.nonce) ? '&nonce=' + params.nonce : ''; + nonceParam = (params.nonce) ? '&nonce=' + params.nonce : '', + responseType = encodeURIComponent(params.responseType); return params.site + path + - appendChar + 'response_type=token&' + + appendChar + 'response_type=' + responseType + '&' + 'client_id=' + encodeURIComponent(params.clientId) + '&' + 'redirect_uri=' + encodeURIComponent(params.redirectUri) + '&' + 'scope=' + oAuthScope + '&' + @@ -67,11 +68,11 @@ endpointClient.factory('Endpoint', function($rootScope, AccessToken, $q, $http) service.checkValidity = function() { var params = service.config; if( params.sessionPath ) { - var token = AccessToken.get().access_token; + var token = AccessToken.get(); if( !token ) { return $q.reject("No token configured"); } - var path = params.site + params.sessionPath + "?token=" + token; + var path = params.site + params.sessionPath + "?token=" + token.access_token; return $http.get(path).then( function(httpResponse) { var tokenInfo = httpResponse.data; if(tokenInfo.valid) { @@ -97,9 +98,8 @@ endpointClient.factory('Endpoint', function($rootScope, AccessToken, $q, $http) $rootScope.$broadcast('oauth:logging-out'); if( params.logoutPath ) { window.location.replace(buildOauthUrl(params.logOutPath, params)); - } else { - $rootScope.$broadcast('oauth:logout'); } + $rootScope.$broadcast('oauth:logout'); }; return service; diff --git a/bower.json b/bower.json index 9f290d0..fca2b04 100644 --- a/bower.json +++ b/bower.json @@ -25,5 +25,8 @@ "bootstrap-sass": "~3.0.2", "jquery": "~1.9.1", "angular-mocks": "~1.3.12" + }, + "resolutions": { + "angular": "1.3.20" } } diff --git a/test/spec/directives/oauth.js b/test/spec/directives/oauth.js index e631ebf..13da42a 100644 --- a/test/spec/directives/oauth.js +++ b/test/spec/directives/oauth.js @@ -2,12 +2,16 @@ describe('oauth', function() { - var $rootScope, $location, Storage, $httpBackend, $compile, AccessToken, Endpoint, element, scope, result, callback; + var $rootScope, $location, Storage, $httpBackend, $compile, AccessToken, Endpoint, element, scope, result, callback, + validCallback, invalidCallback; var fragment = 'access_token=token&token_type=bearer&expires_in=7200&state=/path'; var denied = 'error=access_denied&error_description=error'; var headers = { 'Accept': 'application/json, text/plain, */*', 'Authorization': 'Bearer token' }; var profile = { id: '1', full_name: 'Alice Wonderland', email: 'alice@example.com' }; + var session = { valid: true , expires: ((new Date()).getTime() + 356*24*60*60) }; + var invalidSession = { valid: false }; + var invalidProfile = { valid: false }; beforeEach(module('oauth')); beforeEach(module('templates')); @@ -27,7 +31,10 @@ describe('oauth', function() { 'client="client-id"' + 'redirect="http://example.com/redirect"' + 'scope="scope"' + - 'profile-uri="http://example.com/me">Sign In' + + 'profile-uri="http://example.com/me"' + + 'session-path="/session"' + + '>Sign In' + + '' + '' ); }); @@ -42,120 +49,149 @@ describe('oauth', function() { beforeEach(function() { callback = jasmine.createSpy('callback'); + validCallback = jasmine.createSpy('validCallback'); + $rootScope.$on('oauth:valid', validCallback); }); beforeEach(function() { $location.hash(fragment); }); - beforeEach(function() { - $httpBackend.whenGET('http://example.com/me', headers).respond(profile); - }); - - beforeEach(function() { - $rootScope.$on('oauth:authorized', callback); - }); - - beforeEach(function() { - $rootScope.$on('oauth:login', callback); - }); - - beforeEach(function() { - compile($rootScope, $compile); - }); - - it('shows the link "Logout #{profile.email}"', function() { - $rootScope.$apply(); - $httpBackend.flush(); - result = element.find('.logged-in').text(); - expect(result).toBe('Logout alice@example.com'); - }); - - it('removes the fragment', function() { - expect($location.hash()).toBe(''); - }); + describe('invalid session', function () { + beforeEach(function () { + invalidCallback = jasmine.createSpy('invalidCallback'); + $httpBackend.whenGET('http://example.com/me', headers).respond(invalidProfile); + $httpBackend.whenGET('http://example.com/session?token=token').respond(invalidSession); + $rootScope.$on('oauth:invalid', invalidCallback); + }); - it('shows the logout link', function() { - expect(element.find('.logged-out').attr('class')).toMatch('ng-hide'); - expect(element.find('.logged-in').attr('class')).not.toMatch('ng-hide'); - }); + beforeEach(function () { + compile($rootScope, $compile); + }); - it('fires the oauth:login and oauth:authorized event', function() { - AccessToken.get(); - expect(callback.calls.count()).toBe(2); + it('hits the session and is invalid', function () { + $httpBackend.flush(); + expect(invalidCallback.calls.count()).toBe(1); + }); }); + describe('valid session', function () { - describe('when refreshes the page', function() { - - beforeEach(function() { - callback = jasmine.createSpy('callback'); + beforeEach(function () { + $httpBackend.whenGET('http://example.com/me', headers).respond(profile); + $httpBackend.whenGET('http://example.com/session?token=token').respond(session); }); - beforeEach(function() { + beforeEach(function () { $rootScope.$on('oauth:authorized', callback); }); - beforeEach(function() { - $location.path('/'); + beforeEach(function () { + $rootScope.$on('oauth:login', callback); }); - beforeEach(function() { + beforeEach(function () { compile($rootScope, $compile); }); - it('keeps being logged in', function() { + it('shows the link "Logout #{profile.email}"', function () { $rootScope.$apply(); $httpBackend.flush(); result = element.find('.logged-in').text(); expect(result).toBe('Logout alice@example.com'); }); - it('shows the logout link', function() { + it('removes the fragment', function () { + expect($location.hash()).toBe(''); + }); + + it('shows the logout link', function () { expect(element.find('.logged-out').attr('class')).toMatch('ng-hide'); expect(element.find('.logged-in').attr('class')).not.toMatch('ng-hide'); }); - it('fires the oauth:authorized event', function() { - var event = jasmine.any(Object); - var token = AccessToken.get(); - expect(callback).toHaveBeenCalledWith(event, token); + it('fires the oauth:login and oauth:authorized event', function () { + AccessToken.get(); + expect(callback.calls.count()).toBe(2); }); - it('does not fire the oauth:login event', function() { - AccessToken.get(); - expect(callback.calls.count()).toBe(1); + it('hits the session and is valid', function () { + $httpBackend.flush(); + expect(validCallback.calls.count()).toBe(1); }); - }); - describe('when logs out', function() { + describe('when refreshes the page', function () { - beforeEach(function() { - callback = jasmine.createSpy('callback'); - }); + beforeEach(function () { + callback = jasmine.createSpy('callback'); + }); - beforeEach(function() { - $rootScope.$on('oauth:logout', callback); - }); + beforeEach(function () { + $rootScope.$on('oauth:authorized', callback); + }); - beforeEach(function() { - $rootScope.$on('oauth:loggedOut', callback); - }); + beforeEach(function () { + $location.path('/'); + }); - beforeEach(function() { - element.find('.logged-in').click(); - }); + beforeEach(function () { + compile($rootScope, $compile); + }); + + it('keeps being logged in', function () { + $rootScope.$apply(); + $httpBackend.flush(); + result = element.find('.logged-in').text(); + expect(result).toBe('Logout alice@example.com'); + }); - it('shows the login link', function() { - expect(element.find('.logged-out').attr('class')).not.toMatch('ng-hide'); - expect(element.find('.logged-in').attr('class')).toMatch('ng-hide'); + it('shows the logout link', function () { + expect(element.find('.logged-out').attr('class')).toMatch('ng-hide'); + expect(element.find('.logged-in').attr('class')).not.toMatch('ng-hide'); + }); + + it('fires the oauth:authorized event', function () { + var event = jasmine.any(Object); + var token = AccessToken.get(); + expect(callback).toHaveBeenCalledWith(event, token); + }); + + it('does not fire the oauth:login event', function () { + AccessToken.get(); + expect(callback.calls.count()).toBe(1); + }); }); - it('fires the oauth:logout and oauth:loggedOut event', function() { - var event = jasmine.any(Object); - expect(callback).toHaveBeenCalledWith(event); - expect(callback.calls.count()).toBe(2); + + describe('when logs out', function () { + + beforeEach(function () { + callback = jasmine.createSpy('callback'); + }); + + beforeEach(function () { + $rootScope.$on('oauth:logout', callback); + }); + + beforeEach(function () { + $rootScope.$on('oauth:loggedOut', callback); + }); + + beforeEach(function () { + element.find('.logged-in').click(); + }); + + it('shows the login link', function () { + expect(element.find('.logged-out').attr('class')).not.toMatch('ng-hide'); + expect(element.find('.logged-in').attr('class')).toMatch('ng-hide'); + }); + + it('fires the oauth:logout and oauth:loggedOut event', function () { + var event = jasmine.any(Object); + expect(callback).toHaveBeenCalledWith(event); + expect(callback.calls.count()).toBe(2); + }); }); }); }); @@ -165,6 +201,9 @@ describe('oauth', function() { beforeEach(function() { callback = jasmine.createSpy('callback'); + invalidCallback = jasmine.createSpy('invalidCallback'); + $httpBackend.whenGET('http://example.com/session?token=token').respond(invalidSession); + $rootScope.$on('oauth:invalid', invalidCallback); }); beforeEach(function() { @@ -206,6 +245,10 @@ describe('oauth', function() { it('does not fire the oauth:logout event', function() { expect(callback.calls.count()).toBe(1); }); + + it('fires the oauth:invalid event', function () { + expect(invalidCallback.calls.count()).toBe(1); + }); }); @@ -213,6 +256,7 @@ describe('oauth', function() { beforeEach(function() { callback = jasmine.createSpy('callback'); + $httpBackend.whenGET('http://example.com/session?token=undefined').respond(invalidSession); }); beforeEach(function() { diff --git a/test/spec/services/access-token.js b/test/spec/services/access-token.js index 4a80d34..e8d9fcd 100644 --- a/test/spec/services/access-token.js +++ b/test/spec/services/access-token.js @@ -11,6 +11,7 @@ describe('AccessToken', function() { '&id_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ&expires_in=3600'; var denied = 'error=access_denied&error_description=error'; var expires_at = '2014-08-17T17:38:37.584Z'; + var newExpiresIn = 9600; var token = { access_token: 'token', token_type: 'bearer', expires_in: 7200, state: '/path', expires_at: expires_at }; beforeEach(module('oauth')); @@ -297,6 +298,23 @@ describe('AccessToken', function() { }); }); + describe('#updateExpiry', function () { + beforeEach(function () { + $location.hash(''); + Storage.set('token', token); + AccessToken.set(); + }); + + + it('updates the expiry to a new time', function () { + AccessToken.updateExpiry(newExpiresIn); + expect(AccessToken.expired()).toBeFalsy(); + var newExpiresAt = new Date(); + newExpiresAt.setSeconds(newExpiresAt.getSeconds() + newExpiresIn - 60); + expect(AccessToken.get().expires_at).toEqual(newExpiresAt); + }); + }); + describe('#sessionExpired', function() { describe('with the access token stored in the session', function() { From b93f82e72bb99c09b5dcefebf84d338eabb9425f Mon Sep 17 00:00:00 2001 From: bramski Date: Wed, 10 Feb 2016 12:32:17 -0800 Subject: [PATCH 089/107] Upgrade file. --- dist/oauth-ng.js | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/dist/oauth-ng.js b/dist/oauth-ng.js index a55d454..2bdf37c 100644 --- a/dist/oauth-ng.js +++ b/dist/oauth-ng.js @@ -1,4 +1,4 @@ -/* oauth-ng - v0.4.6 - 2016-02-09 */ +/* oauth-ng - v0.4.6 - 2016-02-10 */ 'use strict'; @@ -493,15 +493,16 @@ endpointClient.factory('Endpoint', function($rootScope, AccessToken, $q, $http) var service = {}; var buildOauthUrl = function (path, params) { - var oAuthScope = (params.scope) ? params.scope : '', + var oAuthScope = (params.scope) ? encodeURIComponent(params.scope) : '', state = (params.state) ? encodeURIComponent(params.state) : '', authPathHasQuery = (params.authorizePath.indexOf('?') == -1) ? false : true, appendChar = (authPathHasQuery) ? '&' : '?', //if authorizePath has ? already append OAuth2 params - nonceParam = (params.nonce) ? '&nonce=' + params.nonce : ''; + nonceParam = (params.nonce) ? '&nonce=' + params.nonce : '', + responseType = encodeURIComponent(params.responseType); return params.site + path + - appendChar + 'response_type=token&' + + appendChar + 'response_type=' + responseType + '&' + 'client_id=' + encodeURIComponent(params.clientId) + '&' + 'redirect_uri=' + encodeURIComponent(params.redirectUri) + '&' + 'scope=' + oAuthScope + '&' + @@ -553,11 +554,11 @@ endpointClient.factory('Endpoint', function($rootScope, AccessToken, $q, $http) service.checkValidity = function() { var params = service.config; if( params.sessionPath ) { - var token = AccessToken.get().access_token; + var token = AccessToken.get(); if( !token ) { return $q.reject("No token configured"); } - var path = params.site + params.sessionPath + "?token=" + token; + var path = params.site + params.sessionPath + "?token=" + token.access_token; return $http.get(path).then( function(httpResponse) { var tokenInfo = httpResponse.data; if(tokenInfo.valid) { @@ -583,9 +584,8 @@ endpointClient.factory('Endpoint', function($rootScope, AccessToken, $q, $http) $rootScope.$broadcast('oauth:logging-out'); if( params.logoutPath ) { window.location.replace(buildOauthUrl(params.logOutPath, params)); - } else { - $rootScope.$broadcast('oauth:logout'); } + $rootScope.$broadcast('oauth:logout'); }; return service; @@ -829,7 +829,7 @@ directives.directive('oauth', [ var token = AccessToken.get(); if (!token) { - return loggedOut(); + return scope.logout(); } // without access token it's logged out if (AccessToken.expired()) { return expired(); @@ -847,8 +847,6 @@ directives.directive('oauth', [ }; scope.logout = function () { - AccessToken.destroy(scope); - $rootScope.$broadcast('oauth:logout'); Endpoint.logout(); $rootScope.$broadcast('oauth:loggedOut'); scope.show = 'logged-out'; @@ -876,9 +874,8 @@ directives.directive('oauth', [ var checkValidity = function() { Endpoint.checkValidity().then(function() { $rootScope.$broadcast('oauth:valid'); - }).catch(function(){ - $rootScope.$broadcast('oauth:invalid'); - scope.logout(); + }).catch(function(message){ + $rootScope.$broadcast('oauth:invalid', message); }); }; From 398b5e8b514eb5f9dc4e238f10f488ad7219c51e Mon Sep 17 00:00:00 2001 From: Massimiliano Sartoretto Date: Thu, 11 Feb 2016 21:28:47 +0100 Subject: [PATCH 090/107] Bump to v0.4.7 --- bower.json | 2 +- dist/oauth-ng.js | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bower.json b/bower.json index fca2b04..71e7bdc 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "oauth-ng", - "version": "0.4.6", + "version": "0.4.7", "main": [ "dist/oauth-ng.js", "dist/views/templates/default" diff --git a/dist/oauth-ng.js b/dist/oauth-ng.js index 2bdf37c..3f960f2 100644 --- a/dist/oauth-ng.js +++ b/dist/oauth-ng.js @@ -1,4 +1,4 @@ -/* oauth-ng - v0.4.6 - 2016-02-10 */ +/* oauth-ng - v0.4.7 - 2016-02-11 */ 'use strict'; diff --git a/package.json b/package.json index 4f6b4cc..60cff70 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "oauth-ng", - "version": "0.4.6", + "version": "0.4.7", "author": "Andrea Reginato ", "description": "AngularJS Directive for OAuth 2.0", "repository": { From 6d776e7498d1ff8074f31e93c1e6ccfcb5fda1b4 Mon Sep 17 00:00:00 2001 From: Massimiliano Sartoretto Date: Thu, 11 Feb 2016 21:30:25 +0100 Subject: [PATCH 091/107] Update CHANGELOG --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cc9f4d0..d863ebe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## 0.4.7 (Feb 11, 2016) + +* Add logout capability +* Add session validity checking feature + ## 0.4.6 (Jan 07, 2016) * Fix JWK format support of OIDC public key From 4fba6afb95cdb1c2916496add8bd9325fc2ebdfc Mon Sep 17 00:00:00 2001 From: bramski Date: Thu, 11 Feb 2016 16:26:21 -0800 Subject: [PATCH 092/107] Fix small error in the way that the logout redirect works. Fix the major problem with the state change success. --- app/scripts/directives/oauth.js | 12 ++++++++---- app/scripts/services/endpoint.js | 2 +- dist/oauth-ng.js | 14 +++++++++----- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/app/scripts/directives/oauth.js b/app/scripts/directives/oauth.js index 0d11fee..185e87d 100644 --- a/app/scripts/directives/oauth.js +++ b/app/scripts/directives/oauth.js @@ -13,7 +13,8 @@ directives.directive('oauth', [ '$compile', '$http', '$templateCache', - function(IdToken, AccessToken, Endpoint, Profile, Storage, $location, $rootScope, $compile, $http, $templateCache) { + '$timeout', + function(IdToken, AccessToken, Endpoint, Profile, Storage, $location, $rootScope, $compile, $http, $templateCache, $timeout) { var definition = { restrict: 'AE', @@ -141,6 +142,10 @@ directives.directive('oauth', [ }); }; + var refreshDirective = function () { + scope.$apply(); + }; + // Updates the template at runtime scope.$on('oauth:template:update', function(event, template) { scope.template = template; @@ -148,13 +153,12 @@ directives.directive('oauth', [ }); // Hack to update the directive content on logout - // TODO think to a cleaner solution scope.$on('$routeChangeSuccess', function () { - init(); + $timeout(refreshDirective); }); scope.$on('$stateChangeSuccess', function () { - init(); + $timeout(refreshDirective); }); }; diff --git a/app/scripts/services/endpoint.js b/app/scripts/services/endpoint.js index 4e17f30..4d9905f 100644 --- a/app/scripts/services/endpoint.js +++ b/app/scripts/services/endpoint.js @@ -97,7 +97,7 @@ endpointClient.factory('Endpoint', function($rootScope, AccessToken, $q, $http) AccessToken.destroy(); $rootScope.$broadcast('oauth:logging-out'); if( params.logoutPath ) { - window.location.replace(buildOauthUrl(params.logOutPath, params)); + window.location.replace(buildOauthUrl(params.logoutPath, params)); } $rootScope.$broadcast('oauth:logout'); }; diff --git a/dist/oauth-ng.js b/dist/oauth-ng.js index 3f960f2..d0b4390 100644 --- a/dist/oauth-ng.js +++ b/dist/oauth-ng.js @@ -583,7 +583,7 @@ endpointClient.factory('Endpoint', function($rootScope, AccessToken, $q, $http) AccessToken.destroy(); $rootScope.$broadcast('oauth:logging-out'); if( params.logoutPath ) { - window.location.replace(buildOauthUrl(params.logOutPath, params)); + window.location.replace(buildOauthUrl(params.logoutPath, params)); } $rootScope.$broadcast('oauth:logout'); }; @@ -751,7 +751,8 @@ directives.directive('oauth', [ '$compile', '$http', '$templateCache', - function(IdToken, AccessToken, Endpoint, Profile, Storage, $location, $rootScope, $compile, $http, $templateCache) { + '$timeout', + function(IdToken, AccessToken, Endpoint, Profile, Storage, $location, $rootScope, $compile, $http, $templateCache, $timeout) { var definition = { restrict: 'AE', @@ -879,6 +880,10 @@ directives.directive('oauth', [ }); }; + var refreshDirective = function () { + scope.$apply(); + }; + // Updates the template at runtime scope.$on('oauth:template:update', function(event, template) { scope.template = template; @@ -886,13 +891,12 @@ directives.directive('oauth', [ }); // Hack to update the directive content on logout - // TODO think to a cleaner solution scope.$on('$routeChangeSuccess', function () { - init(); + $timeout(refreshDirective); }); scope.$on('$stateChangeSuccess', function () { - init(); + $timeout(refreshDirective); }); }; From 3b862efc74087d6b64516ca7ad4dd6c23028c155 Mon Sep 17 00:00:00 2001 From: Massimiliano Sartoretto Date: Fri, 12 Feb 2016 13:53:42 +0100 Subject: [PATCH 093/107] Bump to v0.4.8 --- CHANGELOG.md | 4 ++++ bower.json | 2 +- dist/oauth-ng.js | 2 +- package.json | 4 ++-- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d863ebe..b2b3e03 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 0.4.8 (Feb 12, 2016) + +* Fixed issues related to state change + ## 0.4.7 (Feb 11, 2016) * Add logout capability diff --git a/bower.json b/bower.json index 71e7bdc..fd606a8 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "oauth-ng", - "version": "0.4.7", + "version": "0.4.8", "main": [ "dist/oauth-ng.js", "dist/views/templates/default" diff --git a/dist/oauth-ng.js b/dist/oauth-ng.js index d0b4390..821f91d 100644 --- a/dist/oauth-ng.js +++ b/dist/oauth-ng.js @@ -1,4 +1,4 @@ -/* oauth-ng - v0.4.7 - 2016-02-11 */ +/* oauth-ng - v0.4.8 - 2016-02-12 */ 'use strict'; diff --git a/package.json b/package.json index 60cff70..6054591 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,11 @@ { "name": "oauth-ng", - "version": "0.4.7", + "version": "0.4.8", "author": "Andrea Reginato ", "description": "AngularJS Directive for OAuth 2.0", "repository": { "type": "git", - "url": "https://github.com/andreareginato/oauth-ng" + "url": "https://github.com/angularjs-oauth/oauth-ng" }, "dependencies": {}, "devDependencies": { From b87738c62010b23ef34ee3a510a32f129a6776d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Mosmann?= Date: Fri, 12 Feb 2016 19:12:09 +0000 Subject: [PATCH 094/107] Add explicit annotation to Endpoint factory --- app/scripts/services/endpoint.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/scripts/services/endpoint.js b/app/scripts/services/endpoint.js index 4d9905f..6db8df6 100644 --- a/app/scripts/services/endpoint.js +++ b/app/scripts/services/endpoint.js @@ -2,7 +2,7 @@ var endpointClient = angular.module('oauth.endpoint', []); -endpointClient.factory('Endpoint', function($rootScope, AccessToken, $q, $http) { +endpointClient.factory('Endpoint', ['$rootScope', 'AccessToken', '$q', '$http', function($rootScope, AccessToken, $q, $http) { var service = {}; @@ -103,4 +103,4 @@ endpointClient.factory('Endpoint', function($rootScope, AccessToken, $q, $http) }; return service; -}); +}]); From 769aabaa06a63acfc379a7597b21510b9ce6844e Mon Sep 17 00:00:00 2001 From: Benjamin Walford Date: Fri, 4 Mar 2016 11:59:19 -0700 Subject: [PATCH 095/107] added missing dependency to AccessToken service --- app/scripts/services/access-token.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/scripts/services/access-token.js b/app/scripts/services/access-token.js index 6b4c0dd..7e34d9b 100644 --- a/app/scripts/services/access-token.js +++ b/app/scripts/services/access-token.js @@ -2,7 +2,7 @@ var accessTokenService = angular.module('oauth.accessToken', []); -accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$location', '$interval', 'IdToken', function(Storage, $rootScope, $location, $interval, IdToken){ +accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$location', '$interval', '$timeout', 'IdToken', function(Storage, $rootScope, $location, $interval, $timeout, IdToken){ var service = { token: null From 0f8345b20209482f921e03e6089368228cb57b09 Mon Sep 17 00:00:00 2001 From: Massimiliano Sartoretto Date: Sun, 13 Mar 2016 19:10:18 +0100 Subject: [PATCH 096/107] Bump to v0.4.9 --- bower.json | 2 +- dist/oauth-ng.js | 8 ++++---- package.json | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/bower.json b/bower.json index fd606a8..f7023b7 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "oauth-ng", - "version": "0.4.8", + "version": "0.4.9", "main": [ "dist/oauth-ng.js", "dist/views/templates/default" diff --git a/dist/oauth-ng.js b/dist/oauth-ng.js index 821f91d..60d57cc 100644 --- a/dist/oauth-ng.js +++ b/dist/oauth-ng.js @@ -1,4 +1,4 @@ -/* oauth-ng - v0.4.8 - 2016-02-12 */ +/* oauth-ng - v0.4.9 - 2016-03-13 */ 'use strict'; @@ -283,7 +283,7 @@ idTokenService.factory('IdToken', ['Storage', function(Storage) { var accessTokenService = angular.module('oauth.accessToken', []); -accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$location', '$interval', 'IdToken', function(Storage, $rootScope, $location, $interval, IdToken){ +accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$location', '$interval', '$timeout', 'IdToken', function(Storage, $rootScope, $location, $interval, $timeout, IdToken){ var service = { token: null @@ -488,7 +488,7 @@ accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$location', var endpointClient = angular.module('oauth.endpoint', []); -endpointClient.factory('Endpoint', function($rootScope, AccessToken, $q, $http) { +endpointClient.factory('Endpoint', ['$rootScope', 'AccessToken', '$q', '$http', function($rootScope, AccessToken, $q, $http) { var service = {}; @@ -589,7 +589,7 @@ endpointClient.factory('Endpoint', function($rootScope, AccessToken, $q, $http) }; return service; -}); +}]); 'use strict'; diff --git a/package.json b/package.json index 6054591..29b0ffe 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "oauth-ng", - "version": "0.4.8", + "version": "0.4.9", "author": "Andrea Reginato ", "description": "AngularJS Directive for OAuth 2.0", "repository": { From faef2b5d57ff0066e4808beb1c7ee39b7691f798 Mon Sep 17 00:00:00 2001 From: Massimiliano Sartoretto Date: Sun, 13 Mar 2016 19:11:36 +0100 Subject: [PATCH 097/107] Update CHANGELOG --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b2b3e03..0418960 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## 0.4.9 (Mar 13, 2016) + +* Add DI explicit annotation to Endpoint factory +* Add missing dependency to Access Token service + ## 0.4.8 (Feb 12, 2016) * Fixed issues related to state change From 585c9aa60322510f9c00df8fdbd8d826ade9df40 Mon Sep 17 00:00:00 2001 From: jimmytheneutrino Date: Wed, 20 Jan 2016 22:50:29 +0200 Subject: [PATCH 098/107] support for loading keys from .well-known/openid-configuration, fixes #129 --- app/index.html | 1 + app/scripts/app.js | 1 + app/scripts/directives/oauth.js | 17 +++-- app/scripts/services/id-token.js | 17 ++++- app/scripts/services/oidc-config.js | 62 +++++++++++++++++++ dist/oauth-ng.js | 96 ++++++++++++++++++++++++++--- test/spec/services/oidc-config.js | 87 ++++++++++++++++++++++++++ 7 files changed, 262 insertions(+), 19 deletions(-) create mode 100644 app/scripts/services/oidc-config.js create mode 100644 test/spec/services/oidc-config.js diff --git a/app/index.html b/app/index.html index caad0b2..6b1ce97 100644 --- a/app/index.html +++ b/app/index.html @@ -47,6 +47,7 @@ + diff --git a/app/scripts/app.js b/app/scripts/app.js index d78449d..2d70a1e 100644 --- a/app/scripts/app.js +++ b/app/scripts/app.js @@ -4,6 +4,7 @@ angular.module('oauth', [ 'oauth.directive', // login directive 'oauth.idToken', // id token service (only for OpenID Connect) + 'oauth.oidcConfig', // for loading OIDC configuration from .well-known/openid-configuration endpoint 'oauth.accessToken', // access token service 'oauth.endpoint', // oauth endpoint service 'oauth.profile', // profile model diff --git a/app/scripts/directives/oauth.js b/app/scripts/directives/oauth.js index 185e87d..2f96edf 100644 --- a/app/scripts/directives/oauth.js +++ b/app/scripts/directives/oauth.js @@ -8,13 +8,14 @@ directives.directive('oauth', [ 'Endpoint', 'Profile', 'Storage', + 'OidcConfig', '$location', '$rootScope', '$compile', '$http', '$templateCache', '$timeout', - function(IdToken, AccessToken, Endpoint, Profile, Storage, $location, $rootScope, $compile, $http, $templateCache, $timeout) { + function(IdToken, AccessToken, Endpoint, Profile, Storage, OidcConfig, $location, $rootScope, $compile, $http, $templateCache, $timeout) { var definition = { restrict: 'AE', @@ -36,6 +37,7 @@ directives.directive('oauth', [ issuer: '@', // (optional for OpenID Connect) issuer of the id_token, should match the 'iss' claim in id_token payload subject: '@', // (optional for OpenID Connect) subject of the id_token, should match the 'sub' claim in id_token payload pubKey: '@', // (optional for OpenID Connect) the public key(RSA public key or X509 certificate in PEM format) to verify the signature + wellKnown: '@', // (optional for OpenID Connect) whether to load public key according to .well-known/openid-configuration endpoint logoutPath: '@', // (optional) A url to go to at logout sessionPath: '@' // (optional) A url to use to check the validity of the current token. } @@ -53,11 +55,14 @@ directives.directive('oauth', [ Storage.use(scope.storage);// set storage compile(); // compiles the desired layout Endpoint.set(scope); // sets the oauth authorization url - IdToken.set(scope); - AccessToken.set(scope); // sets the access token object (if existing, from fragment or session) - initProfile(scope); // gets the profile resource (if existing the access token) - initView(); // sets the view (logged in or out) - checkValidity(); // ensure the validity of the current token + OidcConfig.load(scope) // loads OIDC configuration from .well-known/openid-configuration if necessary + .then(function() { + IdToken.set(scope); + AccessToken.set(scope); // sets the access token object (if existing, from fragment or session) + initProfile(scope); // gets the profile resource (if existing the access token) + initView(); // sets the view (logged in or out) + checkValidity(); // ensure the validity of the current token + }); }; var initAttributes = function() { diff --git a/app/scripts/services/id-token.js b/app/scripts/services/id-token.js index 8d3107d..d6fada3 100644 --- a/app/scripts/services/id-token.js +++ b/app/scripts/services/id-token.js @@ -142,9 +142,20 @@ idTokenService.factory('IdToken', ['Storage', function(Storage) { TODO: Support for "jku" (JWK Set URL), "x5u" (X.509 URL), "x5c" (X.509 Certificate Chain) parameter to get key per http://tools.ietf.org/html/draft-ietf-jose-json-web-signature-26#page-9 */ - } else { //Use configured public key - var jwk = getJsonObject(this.pubKey); - matchedPubKey = jwk ? jwk : this.pubKey; //JWK or PEM + } else { + //Try to load the key from .well-known configuration + var oidcConfig = Storage.get('oidcConfig'); + if (angular.isDefined(oidcConfig) && oidcConfig.jwks && oidcConfig.jwks.keys) { + oidcConfig.jwks.keys.forEach(function(key) { + if (key.kid === header.kid) { + matchedPubKey = key; + } + }); + } else { + //Use configured public key + var jwk = getJsonObject(this.pubKey); + matchedPubKey = jwk ? jwk : this.pubKey; //JWK or PEM + } } if(!matchedPubKey) { diff --git a/app/scripts/services/oidc-config.js b/app/scripts/services/oidc-config.js new file mode 100644 index 0000000..a3020ad --- /dev/null +++ b/app/scripts/services/oidc-config.js @@ -0,0 +1,62 @@ +(function() { + 'use strict'; + + angular.module('oauth.oidcConfig', []) + .factory('OidcConfig', ['Storage', '$http', '$q', OidcConfig]); + + function OidcConfig(Storage, $http, $q) { + var cache = null; + return { + load: load + }; + + function load(scope) { + if (scope.issuer && scope.wellKnown && scope.wellKnown !== "false") { + var promise = loadConfig(scope.issuer); + if (scope.wellKnown === "sync") { + return promise; + } + } + return $q.when(1); + } + + function loadConfig(iss) { + if (cache === null) { + cache = Storage.get('oidcConfig'); + } + if (angular.isDefined(cache)) { + return $q.when(cache); + } else { + return loadOpenidConfiguration(iss) + .then(saveCache) + .then(loadJwks) + .then(saveCache); + } + } + + function saveCache(o) { + Storage.set('oidcConfig', cache); + return o; + } + + function joinPath(x,y) { + return x + (x.charAt(x.length - 1) === '/' ? '' : '/') + y; + } + + function loadOpenidConfiguration(iss) { + return $http.get(joinPath(iss, ".well-known/openid-configuration")).then(function(res) { + return cache = res.data; + }); + } + + function loadJwks(oidcConf) { + if (oidcConf.jwks_uri) { + return $http.get(oidcConf.jwks_uri).then(function(res) { + return oidcConf.jwks = res.data; + }); + } else { + return $q.reject("No jwks_uri found."); + } + } + } +})(); diff --git a/dist/oauth-ng.js b/dist/oauth-ng.js index 60d57cc..d0a3325 100644 --- a/dist/oauth-ng.js +++ b/dist/oauth-ng.js @@ -1,4 +1,4 @@ -/* oauth-ng - v0.4.9 - 2016-03-13 */ +/* oauth-ng - v0.4.9 - 2016-03-24 */ 'use strict'; @@ -6,6 +6,7 @@ angular.module('oauth', [ 'oauth.directive', // login directive 'oauth.idToken', // id token service (only for OpenID Connect) + 'oauth.oidcConfig', // for loading OIDC configuration from .well-known/openid-configuration endpoint 'oauth.accessToken', // access token service 'oauth.endpoint', // oauth endpoint service 'oauth.profile', // profile model @@ -162,9 +163,20 @@ idTokenService.factory('IdToken', ['Storage', function(Storage) { TODO: Support for "jku" (JWK Set URL), "x5u" (X.509 URL), "x5c" (X.509 Certificate Chain) parameter to get key per http://tools.ietf.org/html/draft-ietf-jose-json-web-signature-26#page-9 */ - } else { //Use configured public key - var jwk = getJsonObject(this.pubKey); - matchedPubKey = jwk ? jwk : this.pubKey; //JWK or PEM + } else { + //Try to load the key from .well-known configuration + var oidcConfig = Storage.get('oidcConfig'); + if (angular.isDefined(oidcConfig) && oidcConfig.jwks && oidcConfig.jwks.keys) { + oidcConfig.jwks.keys.forEach(function(key) { + if (key.kid === header.kid) { + matchedPubKey = key; + } + }); + } else { + //Use configured public key + var jwk = getJsonObject(this.pubKey); + matchedPubKey = jwk ? jwk : this.pubKey; //JWK or PEM + } } if(!matchedPubKey) { @@ -279,6 +291,65 @@ idTokenService.factory('IdToken', ['Storage', function(Storage) { }]); +(function() { + 'use strict'; + + angular.module('oauth.oidcConfig', []) + .factory('OidcConfig', ['Storage', '$http', '$q', OidcConfig]); + + function OidcConfig(Storage, $http, $q) { + var cache = null; + return { + load: load + }; + + function load(scope) { + if (scope.issuer && scope.wellKnown && scope.wellKnown !== "false") { + var promise = loadConfig(scope.issuer); + if (scope.wellKnown === "sync") { + return promise; + } + } + return $q.resolve(1); + } + + function loadConfig(iss) { + if (cache === null) { + cache = Storage.get('oidcConfig'); + } + if (angular.isDefined(cache)) { + return $q.resolve(cache); + } else { + return loadOpenidConfiguration(iss) + .then(saveCache) + .then(loadJwks) + .then(saveCache); + } + } + + function saveCache(o) { + Storage.set('oidcConfig', cache); + return o; + } + + function loadOpenidConfiguration(iss) { + return $http.get(iss + ".well-known/openid-configuration").then(function(res) { + return cache = res.data; + }); + } + + function loadJwks(oidcConf) { + if (oidcConf.jwks_uri) { + return $http.get(oidcConf.jwks_uri).then(function(res) { + return oidcConf.jwks = res.data; + }); + } else { + return $q.reject("No jwks_uri found."); + } + } + } +})(); + 'use strict'; var accessTokenService = angular.module('oauth.accessToken', []); @@ -746,13 +817,14 @@ directives.directive('oauth', [ 'Endpoint', 'Profile', 'Storage', + 'OidcConfig', '$location', '$rootScope', '$compile', '$http', '$templateCache', '$timeout', - function(IdToken, AccessToken, Endpoint, Profile, Storage, $location, $rootScope, $compile, $http, $templateCache, $timeout) { + function(IdToken, AccessToken, Endpoint, Profile, Storage, OidcConfig, $location, $rootScope, $compile, $http, $templateCache, $timeout) { var definition = { restrict: 'AE', @@ -774,6 +846,7 @@ directives.directive('oauth', [ issuer: '@', // (optional for OpenID Connect) issuer of the id_token, should match the 'iss' claim in id_token payload subject: '@', // (optional for OpenID Connect) subject of the id_token, should match the 'sub' claim in id_token payload pubKey: '@', // (optional for OpenID Connect) the public key(RSA public key or X509 certificate in PEM format) to verify the signature + wellKnown: '@', // (optional for OpenID Connect) whether to load public key according to .well-known/openid-configuration endpoint logoutPath: '@', // (optional) A url to go to at logout sessionPath: '@' // (optional) A url to use to check the validity of the current token. } @@ -791,11 +864,14 @@ directives.directive('oauth', [ Storage.use(scope.storage);// set storage compile(); // compiles the desired layout Endpoint.set(scope); // sets the oauth authorization url - IdToken.set(scope); - AccessToken.set(scope); // sets the access token object (if existing, from fragment or session) - initProfile(scope); // gets the profile resource (if existing the access token) - initView(); // sets the view (logged in or out) - checkValidity(); // ensure the validity of the current token + OidcConfig.load(scope) // loads OIDC configuration from .well-known/openid-configuration if necessary + .then(function() { + IdToken.set(scope); + AccessToken.set(scope); // sets the access token object (if existing, from fragment or session) + initProfile(scope); // gets the profile resource (if existing the access token) + initView(); // sets the view (logged in or out) + checkValidity(); // ensure the validity of the current token + }); }; var initAttributes = function() { diff --git a/test/spec/services/oidc-config.js b/test/spec/services/oidc-config.js new file mode 100644 index 0000000..22c76cc --- /dev/null +++ b/test/spec/services/oidc-config.js @@ -0,0 +1,87 @@ +describe('IdToken', function() { + + var IdToken, OidcConfig; + + var publicKeyModulus, publicKeyExponent; + var validIdToken; + var $httpBackend; + + beforeEach(module('oauth')); + + beforeEach(inject(function ($injector) { + IdToken = $injector.get('IdToken'); + })); + + beforeEach(function () { + /** + * http://kjur.github.io/jsjws/tool_jwt.html generated sample id_token, signed by default private key + * The public key modulus and exponent are as below. This is the same public key as in id-token.js test. + */ + publicKeyModulus = '33TqqLR3eeUmDtHS89qF3p4MP7Wfqt2Zjj3lZjLjjCGDvwr9cJNlN' + + 'DiuKboODgUiT4ZdPWbOiMAfDcDzlOxA04DDnEFGAf-kDQiNSe2Ztq' + + 'C7bnIc8-KSG_qOGQIVaay4Ucr6ovDkykO5Hxn7OU7sJp9TP9H0JH8' + + 'zMQA6YzijYH9LsupTerrY3U6zyihVEDXXOv08vBHk50BMFJbE9iwF' + + 'wnxCsU5-UZUZYw87Uu0n4LPFS9BT8tUIvAfnRXIEWCha3KbFWmdZQ' + + 'ZlyrFw0buUEf0YN3_Q0auBkdbDR_ES2PbgKTJdkjc_rEeM0TxvOUf' + + '7HuUNOhrtAVEN1D5uuxE1WSw'; + publicKeyExponent = 'AQAB'; + }); + + beforeEach(inject(function ($injector) { + OidcConfig = $injector.get('OidcConfig'); + $httpBackend = $injector.get('$httpBackend'); + $httpBackend.when('GET', 'oidc/.well-known/openid-configuration') + .respond({jwks_uri: "oidc/jwks_uri"}); + $httpBackend.when('GET', 'oidc/jwks_uri') + .respond({ + keys: [{ + kty: 'RSA', + n: publicKeyModulus, + e: publicKeyExponent, + kid: 'rsa1' + } + ] + }); + })); + + + describe('validate an id_token when pubkey is loaded from .well-known configuration', function() { + beforeEach(function () { + /* + Valid token with RS256, expires at 20251231235959Z UTC + https://jwt.io has a debugger that can help view the id_token's header and payload + + e.g. The header of following token is { "alg": "RS256", "typ": "JWT", "kid": "rsa1" } + The body of the following token is: + { + "iss": "oidc", + "sub": "oauth-ng-client", + "nbf": 1449267385, + "exp": 1767225599, + "iat": 1449267385, + "jti": "id123456", + "typ": "https://example.com/register" + } + */ + validIdToken = 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6InJzYTEifQ' + + '.eyJpc3MiOiJvaWRjIiwic3ViIjoib2F1dGgtbmctY2xpZW50IiwibmJmIjoxNDQ5MjY3Mzg1LCJleHAiOjE3NjcyMjU1OTksImlhdCI6MTQ0OTI2NzM4NSwianRpIjoiaWQxMjM0NTYiLCJ0eXAiOiJodHRwczovL2V4YW1wbGUuY29tL3JlZ2lzdGVyIn0' + + '.J39k8LK_lu7xYvW_eU-MAI3jtgQEdkpJOlx4ZX_WZ3TUyY-9GG0xLUusteDcy3UGIVxangwonaZ7311WKKz9OwjU1ePivMLXayiP2bL6srIUu8PvOOIcf8oPt8HGv-TLb1zPmYPx3XniekKUEnFAxMGedobcX0wg9tZnnkM11T4qQPcTjDhKB9bNlih9yRHR-6OkKZN4Q_by7EJAPJti22L0dTCW81A_9J5OMXoe0k6fScGfc0Wspsc7CpBN9ZAmTUdHGe8IP5L4leM0pOud6M0gzcIhixR1OMm6qj7ZyvJxgZ48h7Fln3CHyz3LGoHBTQWWDf3ufTzl3sGvippc1w'; + + var scope = { + issuer: 'oidc', + clientId: 'oauth-ng-client', + wellKnown: 'sync' + }; + + OidcConfig.load(scope).then(function(){ + IdToken.set(scope); + }) + $httpBackend.flush(); + }); + + it('with success', function () { + expect(IdToken.validateIdToken(validIdToken)).toEqual(true); + }); + + }); +}); From ac997397de7364642a28f3cdd8e1aff5cebc36c1 Mon Sep 17 00:00:00 2001 From: jimmytheneutrino Date: Mon, 28 Mar 2016 18:36:59 +0300 Subject: [PATCH 099/107] added error logging for oidc config loading --- app/scripts/services/oidc-config.js | 16 ++++++++++++---- dist/oauth-ng.js | 26 +++++++++++++++++++------- 2 files changed, 31 insertions(+), 11 deletions(-) diff --git a/app/scripts/services/oidc-config.js b/app/scripts/services/oidc-config.js index a3020ad..f3e73e9 100644 --- a/app/scripts/services/oidc-config.js +++ b/app/scripts/services/oidc-config.js @@ -2,9 +2,9 @@ 'use strict'; angular.module('oauth.oidcConfig', []) - .factory('OidcConfig', ['Storage', '$http', '$q', OidcConfig]); + .factory('OidcConfig', ['Storage', '$http', '$q', '$log', OidcConfig]); - function OidcConfig(Storage, $http, $q) { + function OidcConfig(Storage, $http, $q, $log) { var cache = null; return { load: load @@ -30,10 +30,15 @@ return loadOpenidConfiguration(iss) .then(saveCache) .then(loadJwks) - .then(saveCache); + .then(saveCache, errorLogger); } } + function errorLogger(err) { + $log.error("Could not load OIDC config:", err); + return $q.reject(err); + } + function saveCache(o) { Storage.set('oidcConfig', cache); return o; @@ -44,8 +49,11 @@ } function loadOpenidConfiguration(iss) { - return $http.get(joinPath(iss, ".well-known/openid-configuration")).then(function(res) { + var configUri = joinPath(iss, ".well-known/openid-configuration"); + return $http.get(configUri).then(function(res) { return cache = res.data; + }, function(err) { + return $q.reject("Could not get config info from " + configUri + ' . Check the availability of this url.'); }); } diff --git a/dist/oauth-ng.js b/dist/oauth-ng.js index d0a3325..c11c2a6 100644 --- a/dist/oauth-ng.js +++ b/dist/oauth-ng.js @@ -1,4 +1,4 @@ -/* oauth-ng - v0.4.9 - 2016-03-24 */ +/* oauth-ng - v0.4.9 - 2016-03-28 */ 'use strict'; @@ -295,9 +295,9 @@ idTokenService.factory('IdToken', ['Storage', function(Storage) { 'use strict'; angular.module('oauth.oidcConfig', []) - .factory('OidcConfig', ['Storage', '$http', '$q', OidcConfig]); + .factory('OidcConfig', ['Storage', '$http', '$q', '$log', OidcConfig]); - function OidcConfig(Storage, $http, $q) { + function OidcConfig(Storage, $http, $q, $log) { var cache = null; return { load: load @@ -310,7 +310,7 @@ idTokenService.factory('IdToken', ['Storage', function(Storage) { return promise; } } - return $q.resolve(1); + return $q.when(1); } function loadConfig(iss) { @@ -318,23 +318,35 @@ idTokenService.factory('IdToken', ['Storage', function(Storage) { cache = Storage.get('oidcConfig'); } if (angular.isDefined(cache)) { - return $q.resolve(cache); + return $q.when(cache); } else { return loadOpenidConfiguration(iss) .then(saveCache) .then(loadJwks) - .then(saveCache); + .then(saveCache, errorLogger); } } + function errorLogger(err) { + $log.error("Could not load OIDC config:", err); + return $q.reject(err); + } + function saveCache(o) { Storage.set('oidcConfig', cache); return o; } + function joinPath(x,y) { + return x + (x.charAt(x.length - 1) === '/' ? '' : '/') + y; + } + function loadOpenidConfiguration(iss) { - return $http.get(iss + ".well-known/openid-configuration").then(function(res) { + var configUri = joinPath(iss, ".well-known/openid-configuration"); + return $http.get(configUri).then(function(res) { return cache = res.data; + }, function(err) { + return $q.reject("Could not get config info from " + configUri + ' . Check the availability of this url.'); }); } From 79596a6bec4a4f9981fb7ec8167daadec93aedcd Mon Sep 17 00:00:00 2001 From: Massimiliano Sartoretto Date: Mon, 25 Apr 2016 13:53:40 +0200 Subject: [PATCH 100/107] Bump to v0.4.10 --- bower.json | 2 +- dist/oauth-ng.js | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bower.json b/bower.json index f7023b7..c690710 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "oauth-ng", - "version": "0.4.9", + "version": "0.4.10", "main": [ "dist/oauth-ng.js", "dist/views/templates/default" diff --git a/dist/oauth-ng.js b/dist/oauth-ng.js index c11c2a6..a61b214 100644 --- a/dist/oauth-ng.js +++ b/dist/oauth-ng.js @@ -1,4 +1,4 @@ -/* oauth-ng - v0.4.9 - 2016-03-28 */ +/* oauth-ng - v0.4.10 - 2016-04-25 */ 'use strict'; diff --git a/package.json b/package.json index 29b0ffe..453e5bf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "oauth-ng", - "version": "0.4.9", + "version": "0.4.10", "author": "Andrea Reginato ", "description": "AngularJS Directive for OAuth 2.0", "repository": { From 3dc35bd3a731f68ccb42b0a80066ba56c497ba7e Mon Sep 17 00:00:00 2001 From: Massimiliano Sartoretto Date: Mon, 25 Apr 2016 13:56:27 +0200 Subject: [PATCH 101/107] Update CHANGELOG --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0418960..5cf1d51 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 0.4.10 (Apr 25, 2016) + +* Support for loading keys from `.well-known/openid-configuration` + ## 0.4.9 (Mar 13, 2016) * Add DI explicit annotation to Endpoint factory From 2067209e4beee19265dc5ca718d06f8fea338101 Mon Sep 17 00:00:00 2001 From: Piotr Banasik Date: Wed, 25 May 2016 14:36:29 -0700 Subject: [PATCH 102/107] Switching token == nil -> Login instead of logout (should make SSO situations work more seamlessly) --- app/scripts/directives/oauth.js | 4 ++-- dist/oauth-ng.js | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/scripts/directives/oauth.js b/app/scripts/directives/oauth.js index 2f96edf..a5d9690 100644 --- a/app/scripts/directives/oauth.js +++ b/app/scripts/directives/oauth.js @@ -97,8 +97,8 @@ directives.directive('oauth', [ var token = AccessToken.get(); if (!token) { - return scope.logout(); - } // without access token it's logged out + return scope.login(); + } // without access token it's logged out, so we attempt to log in if (AccessToken.expired()) { return expired(); } // with a token, but it's expired diff --git a/dist/oauth-ng.js b/dist/oauth-ng.js index a61b214..785f8fa 100644 --- a/dist/oauth-ng.js +++ b/dist/oauth-ng.js @@ -1,4 +1,4 @@ -/* oauth-ng - v0.4.10 - 2016-04-25 */ +/* oauth-ng - v0.4.10 - 2016-05-25 */ 'use strict'; @@ -918,8 +918,8 @@ directives.directive('oauth', [ var token = AccessToken.get(); if (!token) { - return scope.logout(); - } // without access token it's logged out + return scope.login(); + } // without access token it's logged out, so we attempt to log in if (AccessToken.expired()) { return expired(); } // with a token, but it's expired From 8e2c7234fcc0040f502ed704cc97cc6d98d3b32c Mon Sep 17 00:00:00 2001 From: bramski Date: Mon, 20 Jun 2016 11:47:45 -0700 Subject: [PATCH 103/107] Remove bower install so that oauth-ng can work in npm via webpack in heroku. --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index 453e5bf..8dea385 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,6 @@ "node": ">=0.8.0" }, "scripts": { - "postinstall": "bower install", "clean": "rm -rf node_modules dist app/bower_components", "test": "grunt test && grunt build" }, From 35bf57d1c4e03a744c4ebf6861643cb8c8d69faa Mon Sep 17 00:00:00 2001 From: Alfsig Date: Mon, 12 Dec 2016 16:39:13 +0100 Subject: [PATCH 104/107] Update oauth-ng.js --- dist/oauth-ng.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/dist/oauth-ng.js b/dist/oauth-ng.js index 785f8fa..3ad2269 100644 --- a/dist/oauth-ng.js +++ b/dist/oauth-ng.js @@ -684,8 +684,8 @@ profileClient.factory('Profile', ['$http', 'AccessToken', '$rootScope', function service.find = function(uri) { var promise = $http.get(uri, { headers: headers() }); - promise.success(function(response) { - profile = response; + promise.then(function(response) { + profile = response.data; $rootScope.$broadcast('oauth:profile', profile); }); return promise; @@ -898,8 +898,8 @@ directives.directive('oauth', [ }; var compile = function() { - $http.get(scope.template, { cache: $templateCache }).success(function(html) { - element.html(html); + $http.get(scope.template, { cache: $templateCache }).then(function(html) { + element.html(html.data); $compile(element.contents())(scope); }); }; @@ -908,8 +908,8 @@ directives.directive('oauth', [ var token = AccessToken.get(); if (token && token.access_token && scope.profileUri) { - Profile.find(scope.profileUri).success(function(response) { - scope.profile = response; + Profile.find(scope.profileUri).then(function(response) { + scope.profile = response.data; }); } }; From 1710d1852ff307465c77832a369b32ae3c9999e4 Mon Sep 17 00:00:00 2001 From: Alfsig Date: Tue, 7 Mar 2017 10:01:06 +0100 Subject: [PATCH 105/107] Update profile.js --- app/scripts/services/profile.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/scripts/services/profile.js b/app/scripts/services/profile.js index cc8a22a..4d257fc 100644 --- a/app/scripts/services/profile.js +++ b/app/scripts/services/profile.js @@ -8,8 +8,8 @@ profileClient.factory('Profile', ['$http', 'AccessToken', '$rootScope', function service.find = function(uri) { var promise = $http.get(uri, { headers: headers() }); - promise.success(function(response) { - profile = response; + promise.then(function(response) { + profile = response.data; $rootScope.$broadcast('oauth:profile', profile); }); return promise; From 636ef3a619ba8dfd854b3cd58b056d7eeacb4a47 Mon Sep 17 00:00:00 2001 From: Alfsig Date: Tue, 7 Mar 2017 10:02:29 +0100 Subject: [PATCH 106/107] Update oauth.js --- app/scripts/directives/oauth.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/scripts/directives/oauth.js b/app/scripts/directives/oauth.js index a5d9690..ce2ff26 100644 --- a/app/scripts/directives/oauth.js +++ b/app/scripts/directives/oauth.js @@ -77,8 +77,8 @@ directives.directive('oauth', [ }; var compile = function() { - $http.get(scope.template, { cache: $templateCache }).success(function(html) { - element.html(html); + $http.get(scope.template, { cache: $templateCache }).then(function(html) { + element.html(html.data); $compile(element.contents())(scope); }); }; @@ -87,8 +87,8 @@ directives.directive('oauth', [ var token = AccessToken.get(); if (token && token.access_token && scope.profileUri) { - Profile.find(scope.profileUri).success(function(response) { - scope.profile = response; + Profile.find(scope.profileUri).then(function(response) { + scope.profile = response.data; }); } }; From bf09177f20dab763207b8e11c982cc3e685af16a Mon Sep 17 00:00:00 2001 From: Alfsig Date: Tue, 7 Mar 2017 10:03:06 +0100 Subject: [PATCH 107/107] Update profile.js --- test/spec/services/profile.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/spec/services/profile.js b/test/spec/services/profile.js index 0c195c4..dea4bed 100644 --- a/test/spec/services/profile.js +++ b/test/spec/services/profile.js @@ -45,7 +45,7 @@ describe('Profile', function() { }); it('gets the resource', inject(function(Profile) { - Profile.find(params.profileUri).success(function(response) { result = response; }); + Profile.find(params.profileUri).then(function(response) { result = response.data; }); $rootScope.$apply(); $httpBackend.flush(); expect(result.name).toEqual('Alice');